feat: release v1.0.2 LeKiwi 语言控制与网站嵌入
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled

集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。

更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
This commit is contained in:
2026-09-24 15:29:49 +08:00
parent f3a8a38acd
commit 0d986f60bd
93 changed files with 5166 additions and 1094 deletions
@@ -1,103 +1,44 @@
import { expect, test } from '@playwright/test';
import { resolve } from 'node:path';
import { readFile, writeFile } from 'node:fs/promises';
import { writeFile } from 'node:fs/promises';
import { subscriptionOperation } from './fixtures/subscriptionSession';
import type {} from '../physics/agent';
test.use({ trace: 'off', video: 'off' });
test('显式 opt-in:已人工登录的 ChatGPT 订阅 + Jev 主视口物理验收与退出', async ({
page,
}, info) => {
test.skip(
process.env.LEKIWI_LIVE_SUBSCRIPTION !== '1',
'需要用户在隔离官方页面完成登录;不自动发起登录或使用全局账号',
);
test('显式 opt-in:旧 v1 隔离订阅 API 兼容,非新版语言面板功能', async ({ page }, info) => {
test.skip(process.env.LEKIWI_LIVE_SUBSCRIPTION !== '1', '需人工完成隔离官方登录;不使用全局账号');
const session = await subscriptionOperation('connect');
const token = String(session.token);
const connection = { address: String(session.address), token: String(session.token) };
try {
expect(session.llmProtocol).toBe('codex');
const before = await subscriptionOperation('status');
expect(before.loggedIn).toBe(true);
const limitsBefore = await subscriptionOperation('limits');
await page.goto('/');
await page
.locator('input[type="file"]')
.first()
.setInputFiles(resolve('build/lekiwi/lekiwi-v1.zip'));
const dialog = page.getByRole('dialog', { name: '配置 URDF 仿真组件' });
await expect(dialog).toBeVisible({ timeout: 30000 });
await dialog.getByLabel('URDF 机器人 profile').selectOption('lekiwi-v1');
await dialog.getByRole('button', { name: '转换并加载' }).click();
await expect(page.getByRole('button', { name: '▶ 播放' })).toBeEnabled({ timeout: 60000 });
await page.getByRole('tab', { name: '控制台', exact: true }).click();
await page.getByRole('button', { name: 'LeKiwi 智能抓放', exact: true }).click();
const panel = page.getByLabel('LeKiwi 智能抓放任务', { exact: true });
await panel.getByText('模型连接与官方订阅登录', { exact: true }).click();
await panel.getByLabel('本机模型服务', { exact: true }).fill(String(session.address));
await panel.getByLabel('本机服务令牌', { exact: true }).fill(token);
await panel.getByRole('button', { name: '连接本机模型服务', exact: true }).click();
await expect(panel).toContainText('本机桥已连接');
await expect(panel.getByLabel('LLM 协议', { exact: true })).toHaveValue('codex');
await expect(panel.getByLabel('LLM API Key', { exact: true })).toHaveCount(0);
await panel.getByText(/ChatGPT 订阅 · 官方/).click();
await panel.getByRole('button', { name: '刷新账号', exact: true }).click();
await expect(panel).toContainText('ChatGPT 已登录(仅本次服务会话)');
await panel.getByRole('button', { name: '查询官方型号', exact: true }).click();
await expect(panel.getByLabel('LLM 型号', { exact: true })).toHaveValue(
String(session.llmModel),
expect((await subscriptionOperation('status')).loggedIn).toBe(true);
await page.goto('/physics/agent.html');
await page.waitForFunction(() => Boolean(window.lekiwiAgent));
await page.evaluate(
(url) => window.lekiwiAgent.boot(url),
`/@fs${resolve('build/lekiwi/lekiwi-v1.zip')}`,
);
await expect(panel.getByLabel('JEV 型号', { exact: true })).toHaveValue('typesafe/jev-1.13');
await panel.getByText('模型连接与官方订阅登录', { exact: true }).click();
await panel.getByLabel('智能任务决策模式').selectOption('api');
await panel.getByRole('button', { name: '开始任务(真实 API)', exact: true }).click();
await expect(panel.getByTestId('agent-task-state')).toContainText(
/物理验收成功|失败 \/ 安全停止|已取消/,
{ timeout: 240000 },
);
const download = page.waitForEvent('download');
await panel.getByRole('button', { name: '导出任务证据', exact: true }).click();
const data = JSON.parse(await readFile((await (await download).path())!, 'utf8'));
const after = await subscriptionOperation('status');
const evidence = {
source: 'real-chatgpt-subscription-plus-openrouter-jev',
before,
after,
limitsBefore,
...data,
};
const encoded = JSON.stringify(evidence, null, 2);
if (encoded.includes(token) || encoded.includes('auth.openai.com/'))
throw new Error('任务证据包含认证信息');
await writeFile(info.outputPath('subscription-physical-evidence.json'), encoded);
console.log('真实订阅回合', {
state: data.state,
error: data.error,
model: session.llmModel,
llm: data.llmCalls,
jev: data.jevCalls,
simTime: data.sample.time,
transported: data.transported,
});
const data = await page.evaluate((c) => window.lekiwiAgent.task(0, c), connection);
const encoded = JSON.stringify(data, null, 2);
if (encoded.includes(connection.token) || encoded.includes('auth.openai.com/'))
throw new Error('证据包含认证信息');
await writeFile(info.outputPath('v1-subscription-evidence.json'), encoded);
expect(data.state, data.error).toBe('succeeded');
expect(data.succeeded).toBe(true);
expect(data.transported).toBeGreaterThanOrEqual(0.5);
expect(data.llmCalls).toBe(1);
expect(data.jevCalls).toBe(11);
expect(data.receipts).toHaveLength(12);
expect(data.receipts.find((r: { kind: string }) => r.kind === 'plan').provider).toBe('codex');
expect(after.checkedModels).toContain(session.llmModel);
await page.screenshot({ path: info.outputPath('subscription-workbench.png') });
await panel.getByText('模型连接与官方订阅登录', { exact: true }).click();
await panel.getByRole('button', { name: '退出隔离账号', exact: true }).click();
await expect(panel).toContainText('已退出本次隔离会话');
const loggedOut = await subscriptionOperation('status');
expect(loggedOut.loggedIn).toBe(false);
await writeFile(
info.outputPath('subscription-logout.json'),
JSON.stringify(loggedOut, null, 2),
);
expect(data.receipts.find((r) => r.kind === 'plan')?.provider).toBe('codex');
const loggedOut = await page.evaluate(async (c) => {
const response = await fetch(c.address + '/api/decision/v1/codex/logout', {
method: 'POST',
headers: { Authorization: 'Bearer ' + c.token, 'Content-Type': 'application/json' },
body: '{}',
});
return response.ok;
}, connection);
expect(loggedOut).toBe(true);
expect((await subscriptionOperation('status')).loggedIn).toBe(false);
} catch (error) {
await page.close().catch(() => undefined);
// eslint-disable-next-line preserve-caught-error -- Playwright causes can contain the private bridge token.
throw new Error(String(error).replaceAll(token, '[redacted]'));
// eslint-disable-next-line preserve-caught-error -- Avoid exposing private bridge tokens.
throw new Error(String(error).replaceAll(connection.token, '[redacted]'));
}
});