Files
remoteconn-gitea/apps/miniprogram/utils/aiLaunch.test.ts
2026-03-21 18:57:10 +08:00

118 lines
4.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
const {
DEFAULT_AI_PROVIDER,
AI_RUNTIME_EXIT_OSC_IDENT,
CODEX_BOOTSTRAP_TOKEN_CODEX_MISSING,
CODEX_BOOTSTRAP_TOKEN_DIR_MISSING,
CODEX_BOOTSTRAP_TOKEN_READY,
DEFAULT_CODEX_SANDBOX_MODE,
DEFAULT_COPILOT_PERMISSION_MODE,
buildAiRuntimeExitMarker,
buildCopilotLaunchCommand,
buildCopilotCommand,
buildCdCommand,
buildCodexBootstrapCommand,
buildCodexResumeCommand,
consumeAiRuntimeExitMarkers,
isAiSessionReady,
normalizeAiProvider,
normalizeCodexSandboxMode,
normalizeCopilotPermissionMode
} = require("./aiLaunch.js");
describe("miniprogram aiLaunch", () => {
it("保留 HOME 展开语义", () => {
expect(buildCdCommand("~")).toBe('cd "$HOME"');
expect(buildCdCommand("~/workspace/remoteconn")).toBe("cd \"$HOME\"/'workspace/remoteconn'");
});
it("对普通路径做单引号转义", () => {
expect(buildCdCommand("/var/www/my app")).toBe("cd '/var/www/my app'");
expect(buildCdCommand("/tmp/it's here")).toBe("cd '/tmp/it'\\''s here'");
});
it("生成与 Web 对齐的 Codex bootstrap 命令", () => {
const plan = buildCodexBootstrapCommand("~/workspace/demo", "danger-full-access");
expect(plan.projectPath).toBe("~/workspace/demo");
expect(plan.command.startsWith('sh -lc "')).toBe(true);
expect(plan.command).toContain("codex --sandbox danger-full-access");
expect(plan.command).toContain(CODEX_BOOTSTRAP_TOKEN_DIR_MISSING);
expect(plan.command).toContain(CODEX_BOOTSTRAP_TOKEN_CODEX_MISSING);
expect(plan.command).toContain(CODEX_BOOTSTRAP_TOKEN_READY);
expect(plan.command).toContain(
`printf '\\\\033]${AI_RUNTIME_EXIT_OSC_IDENT};RemoteConn;ai-exit=codex\\\\a'`
);
});
it("生成 Codex 恢复命令时应使用 resume --last 并保留退出标记", () => {
const plan = buildCodexResumeCommand("~/workspace/demo", "danger-full-access");
expect(plan.projectPath).toBe("~/workspace/demo");
expect(plan.command.startsWith('sh -lc "')).toBe(true);
expect(plan.command).toContain("workspace/demo");
expect(plan.command).toContain("codex resume --last --sandbox danger-full-access;");
expect(plan.command).toContain(
`printf '\\\\033]${AI_RUNTIME_EXIT_OSC_IDENT};RemoteConn;ai-exit=codex\\\\a'`
);
});
it("AI 模式脏值会回退到稳定默认值", () => {
expect(normalizeAiProvider("")).toBe(DEFAULT_AI_PROVIDER);
expect(normalizeAiProvider("invalid")).toBe(DEFAULT_AI_PROVIDER);
expect(normalizeCodexSandboxMode("")).toBe(DEFAULT_CODEX_SANDBOX_MODE);
expect(normalizeCodexSandboxMode("invalid")).toBe(DEFAULT_CODEX_SANDBOX_MODE);
expect(normalizeCopilotPermissionMode("")).toBe(DEFAULT_COPILOT_PERMISSION_MODE);
expect(normalizeCopilotPermissionMode("invalid")).toBe(DEFAULT_COPILOT_PERMISSION_MODE);
});
it("会按 Copilot 权限模式构造启动命令", () => {
expect(buildCopilotCommand("default")).toBe("copilot");
expect(buildCopilotCommand("experimental")).toBe("copilot --experimental");
expect(buildCopilotCommand("allow-all")).toBe("copilot --allow-all");
expect(buildCopilotCommand("invalid")).toBe("copilot");
});
it("Copilot 启动命令也会携带退出标记,供前端解除前台 AI 保护", () => {
const plan = buildCopilotLaunchCommand("~/workspace/demo", "allow-all");
expect(plan.projectPath).toBe("~/workspace/demo");
expect(plan.command.startsWith('sh -lc "')).toBe(true);
expect(plan.command).toContain("copilot --allow-all");
expect(plan.command).toContain(
`printf '\\\\033]${AI_RUNTIME_EXIT_OSC_IDENT};RemoteConn;ai-exit=copilot\\\\a'`
);
});
it("能消费完整或跨 chunk 的 AI 退出标记,而不污染可见文本", () => {
const codexExitMarker = buildAiRuntimeExitMarker("codex");
expect(consumeAiRuntimeExitMarkers(`prefix${codexExitMarker}suffix`, "")).toEqual({
text: "prefixsuffix",
carry: "",
exitedProviders: ["codex"]
});
const first = consumeAiRuntimeExitMarkers(`before${codexExitMarker.slice(0, 8)}`, "");
expect(first).toEqual({
text: "before",
carry: codexExitMarker.slice(0, 8),
exitedProviders: []
});
expect(consumeAiRuntimeExitMarkers(`${codexExitMarker.slice(8)}after`, first.carry)).toEqual({
text: "after",
carry: "",
exitedProviders: ["codex"]
});
});
it("只有收到显式 shell-ready 信号后,才认为 AI 会话可发命令", () => {
expect(isAiSessionReady("connected", true, false)).toBe(false);
expect(isAiSessionReady("connected", true, true)).toBe(true);
expect(isAiSessionReady("connecting", true, true)).toBe(false);
expect(isAiSessionReady("connected", false, true)).toBe(false);
});
});