@selesai/code 0.13.9 → 0.13.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.js +5 -3
- package/dist/extensions/handoff-new.test.ts +26 -1
- package/dist/extensions/handoff-new.ts +5 -0
- package/dist/extensions/pi-subagents/README.md +0 -6
- package/dist/extensions/pi-subagents/docs/agents.md +1 -114
- package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +1 -12
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +3 -19
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +8 -37
- package/dist/extensions/pi-subagents/src/shared/types.ts +2 -2
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +2 -38
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +0 -49
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +0 -18
- package/dist/modes/rpc/rpc-client.d.ts +51 -7
- package/dist/modes/rpc/rpc-client.js +57 -11
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.js +199 -3
- package/dist/modes/rpc/rpc-types.d.ts +91 -1
- package/dist/skills/improve-codebase/SKILL.md +1 -1
- package/dist/skills/planger/SKILL.md +1 -1
- package/dist/skills/workflow/SKILL.md +9 -15
- package/docs/rpc.md +170 -0
- package/package.json +1 -1
- package/dist/extensions/pi-subagents/agents/architect.md +0 -170
- package/dist/extensions/pi-subagents/agents/builder.md +0 -37
- package/dist/extensions/pi-subagents/agents/claude-code-writer.md +0 -15
- package/dist/extensions/pi-subagents/agents/claude-code.md +0 -15
- package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +0 -15
- package/dist/extensions/pi-subagents/agents/codex-exec.md +0 -15
- package/dist/extensions/pi-subagents/agents/commentator.md +0 -37
- package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +0 -14
- package/dist/extensions/pi-subagents/agents/cursor-agent.md +0 -14
- package/dist/extensions/pi-subagents/agents/explorer.md +0 -32
- package/dist/extensions/pi-subagents/agents/recapper.md +0 -31
- package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +0 -129
- package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +0 -129
- package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +0 -114
- package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +0 -52
- package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +0 -55
- package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +0 -53
- package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +0 -57
- package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +0 -59
- package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +0 -62
- package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +0 -245
- package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +0 -192
- package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +0 -259
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
import { resolveCursorAgentLaunch } from "../../src/runs/shared/cursor-agent-adapter.ts";
|
|
5
|
-
import { runExternalCli } from "../../src/runs/shared/external-cli-runner.ts";
|
|
6
|
-
import { cursorSmokeInputs } from "../support/cursor-smoke-inputs.ts";
|
|
7
|
-
|
|
8
|
-
const enabled = process.env.SELESAI_SUBAGENTS_CURSOR_AGENT_WRITER_SMOKE === "1";
|
|
9
|
-
|
|
10
|
-
test("maintainer Cursor Agent prompt-file writer smoke", { skip: enabled ? undefined : "set SELESAI_SUBAGENTS_CURSOR_AGENT_WRITER_SMOKE=1" }, async () => {
|
|
11
|
-
const reportPath = process.env.SELESAI_SUBAGENTS_CURSOR_AGENT_WRITER_SMOKE_REPORT;
|
|
12
|
-
assert.ok(reportPath, "SELESAI_SUBAGENTS_CURSOR_AGENT_WRITER_SMOKE_REPORT is required");
|
|
13
|
-
const { workspace, stateRoot, canaryPath, promptDirectory } = cursorSmokeInputs(process.env);
|
|
14
|
-
const promptMarker = "CURSOR_WRITER_PRIVATE_PROMPT_MARKER";
|
|
15
|
-
try {
|
|
16
|
-
const launch = resolveCursorAgentLaunch({ adapter: "cursor-agent-writer", command: "cursor-agent", cwd: workspace, asyncDir: stateRoot, stepIndex: 0 });
|
|
17
|
-
assert.deepEqual(launch.temporaryDirectories, [promptDirectory]);
|
|
18
|
-
assert.equal(launch.args.includes("--add-dir"), true, "Cursor smoke must exercise the production external prompt-root launch shape");
|
|
19
|
-
const result = await runExternalCli({
|
|
20
|
-
...launch,
|
|
21
|
-
temporaryDirectories: [],
|
|
22
|
-
cwd: workspace,
|
|
23
|
-
prompt: `${promptMarker}: Write exactly CANARY to ${canaryPath}. Then report completion.`,
|
|
24
|
-
asyncDir: stateRoot,
|
|
25
|
-
stepIndex: 0,
|
|
26
|
-
});
|
|
27
|
-
const writeCanaryExists = fs.existsSync(canaryPath);
|
|
28
|
-
const writeCanaryMatches = writeCanaryExists && fs.readFileSync(canaryPath, "utf-8").trim() === "CANARY";
|
|
29
|
-
const report = {
|
|
30
|
-
adapter: "cursor-agent-writer",
|
|
31
|
-
adapterVersion: 1,
|
|
32
|
-
cliVersion: result.preflight?.version,
|
|
33
|
-
cwd: workspace,
|
|
34
|
-
promptDelivery: "prompt-file",
|
|
35
|
-
authentication: "cursor-api-key-or-existing-login",
|
|
36
|
-
access: "workspace-write",
|
|
37
|
-
mode: "print",
|
|
38
|
-
sandbox: "enabled",
|
|
39
|
-
workspaceTrust: "operator-managed-saved",
|
|
40
|
-
externalPromptRootAdded: true,
|
|
41
|
-
sessionReuse: false,
|
|
42
|
-
exitCode: result.exitCode,
|
|
43
|
-
terminalState: result.parserTerminal?.state,
|
|
44
|
-
writeCanaryExists,
|
|
45
|
-
writeCanaryMatches,
|
|
46
|
-
stdoutPath: result.externalProcess.stdoutPath,
|
|
47
|
-
stderrPath: result.externalProcess.stderrPath,
|
|
48
|
-
durationMs: result.externalProcess.durationMs,
|
|
49
|
-
};
|
|
50
|
-
const serialized = `${JSON.stringify(report, null, 2)}\n`;
|
|
51
|
-
assert.equal(serialized.includes(promptMarker), false);
|
|
52
|
-
if (process.env.CURSOR_API_KEY) assert.equal(serialized.includes(process.env.CURSOR_API_KEY), false);
|
|
53
|
-
fs.writeFileSync(reportPath, serialized, { encoding: "utf-8", mode: 0o600 });
|
|
54
|
-
assert.equal(result.exitCode, 0, result.error);
|
|
55
|
-
assert.equal(result.parserTerminal?.state, "completed");
|
|
56
|
-
assert.equal(writeCanaryMatches, true, "Cursor Agent did not write the expected canary");
|
|
57
|
-
assert.equal(fs.existsSync(promptDirectory), true, "Cursor smoke removed the operator-owned prompt directory");
|
|
58
|
-
assert.deepEqual(fs.readdirSync(promptDirectory), [], "Cursor smoke left private prompt files behind");
|
|
59
|
-
} finally {
|
|
60
|
-
fs.rmSync(canaryPath, { force: true });
|
|
61
|
-
}
|
|
62
|
-
});
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { afterEach, describe, it } from "node:test";
|
|
6
|
-
import { discoverAgents, discoverAgentsAll, resolveAgentName } from "../../src/agents/agents.ts";
|
|
7
|
-
import { CLAUDE_CODE_ADAPTER_ID, CLAUDE_CODE_ENV_ALLOWLIST, CLAUDE_CODE_WRITER_ADAPTER_ID, CLAUDE_CODE_WRITER_TOOLS, createClaudeCodeJsonlParser, resolveClaudeCodeLaunch } from "../../src/runs/shared/claude-code-adapter.ts";
|
|
8
|
-
import { externalCliReceiptMetadata, resolveExternalCliRunnerStatus } from "../../src/runs/shared/external-cli-contract.ts";
|
|
9
|
-
import { clearExternalCliPreflightCacheForTests } from "../../src/runs/shared/external-cli-preflight.ts";
|
|
10
|
-
import { runExternalCli } from "../../src/runs/shared/external-cli-runner.ts";
|
|
11
|
-
import { buildWorkflowReceipt, readWorkflowReceipt, writeWorkflowReceipt } from "../../src/workflows/workflow-receipt.ts";
|
|
12
|
-
|
|
13
|
-
const tempDirs: string[] = [];
|
|
14
|
-
function tempDir(): string {
|
|
15
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-claude-code-"));
|
|
16
|
-
tempDirs.push(dir);
|
|
17
|
-
return dir;
|
|
18
|
-
}
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
clearExternalCliPreflightCacheForTests();
|
|
21
|
-
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
function fakeClaudeScript(dir: string): string {
|
|
25
|
-
const scriptPath = path.join(dir, "fake-claude.cjs");
|
|
26
|
-
fs.writeFileSync(scriptPath, String.raw`
|
|
27
|
-
+const args = process.argv.slice(2);
|
|
28
|
-
+if (args[0] === "--version") { console.log("2.1.150 (Claude Code)"); process.exit(0); }
|
|
29
|
-
+if (args[0] === "--help") {
|
|
30
|
-
+ console.log("Claude Code - starts an interactive session --print --input-format text --output-format stream-json --verbose --permission-mode plan acceptEdits --tools --strict-mcp-config --mcp-config --setting-sources --no-session-persistence --disable-slash-commands --no-chrome");
|
|
31
|
-
+ process.exit(0);
|
|
32
|
-
+}
|
|
33
|
-
+let prompt = "";
|
|
34
|
-
+process.stdin.on("data", chunk => prompt += chunk);
|
|
35
|
-
+process.stdin.on("end", () => {
|
|
36
|
-
+ if (prompt.includes("malformed")) return process.stdout.write("{bad json}\n");
|
|
37
|
-
+ if (prompt.includes("oversized")) return process.stdout.write(JSON.stringify({type:"assistant", value:"x".repeat(300000)}) + "\n");
|
|
38
|
-
+ process.stdout.write(JSON.stringify({type:"system", subtype:"init"}) + "\n");
|
|
39
|
-
+ if (prompt.includes("missing-terminal")) return;
|
|
40
|
-
+ if (prompt.includes("auth-error")) return process.stdout.write(JSON.stringify({type:"result", subtype:"error_during_execution", is_error:true, errors:["authentication required"]}) + "\n");
|
|
41
|
-
+ if (prompt.includes("max-turns")) return process.stdout.write(JSON.stringify({type:"result", subtype:"error_max_turns", is_error:true, result:"partial"}) + "\n");
|
|
42
|
-
+ if (prompt.includes("budget")) return process.stdout.write(JSON.stringify({type:"result", subtype:"error_max_budget_usd", is_error:true, result:"partial"}) + "\n");
|
|
43
|
-
+ if (prompt.includes("missing-text")) return process.stdout.write(JSON.stringify({type:"result", subtype:"success", is_error:false, result:""}) + "\n");
|
|
44
|
-
+ process.stdout.write(JSON.stringify({type:"assistant", message:{content:[{type:"text", text:"untrusted partial"}]}}) + "\n");
|
|
45
|
-
+ process.stdout.write(JSON.stringify({type:"result", subtype:"success", is_error:false, result:"trusted final result", session_id:"not-persisted"}) + "\n");
|
|
46
|
-
+ if (prompt.includes("duplicate-terminal")) process.stdout.write(JSON.stringify({type:"result", subtype:"success", is_error:false, result:"duplicate"}) + "\n");
|
|
47
|
-
+});
|
|
48
|
-
+`.replace(/^\+/gm, ""), "utf-8");
|
|
49
|
-
return scriptPath;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function runFake(dir: string, stepIndex: number, prompt: string, adapter: typeof CLAUDE_CODE_ADAPTER_ID | typeof CLAUDE_CODE_WRITER_ADAPTER_ID = CLAUDE_CODE_ADAPTER_ID) {
|
|
53
|
-
const scriptPath = fakeClaudeScript(dir);
|
|
54
|
-
const launch = resolveClaudeCodeLaunch({ adapter, command: process.execPath, commandPrefixArgs: [scriptPath] });
|
|
55
|
-
const result = await runExternalCli({ ...launch, cwd: dir, prompt, asyncDir: dir, stepIndex });
|
|
56
|
-
return { launch, result };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe("Claude Code adapter", () => {
|
|
60
|
-
it("owns no-tools argv, stdin delivery, launch preflight, and terminal result proof", async () => {
|
|
61
|
-
const dir = tempDir();
|
|
62
|
-
const { launch, result } = await runFake(dir, 0, "review $HOME; echo nope");
|
|
63
|
-
assert.deepEqual(launch.args.slice(1), [
|
|
64
|
-
"-p", "--input-format", "text", "--output-format", "stream-json", "--verbose", "--permission-mode", "plan",
|
|
65
|
-
"--tools", "", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--setting-sources", "user", "--no-session-persistence",
|
|
66
|
-
"--disable-slash-commands", "--no-chrome",
|
|
67
|
-
]);
|
|
68
|
-
assert.equal(launch.args.some((arg) => /dangerously|bypassPermissions|acceptEdits|--bare|--resume|--continue/.test(arg)), false);
|
|
69
|
-
assert.equal(result.exitCode, 0);
|
|
70
|
-
assert.equal(result.output, "trusted final result");
|
|
71
|
-
assert.equal(result.parserTerminal?.state, "completed");
|
|
72
|
-
assert.equal(result.preflight?.version, "2.1.150 (Claude Code)");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("passes the local identity and temporary-directory keys required by CLI login", () => {
|
|
76
|
-
assert.equal(CLAUDE_CODE_ENV_ALLOWLIST.includes("USER"), true);
|
|
77
|
-
assert.equal(CLAUDE_CODE_ENV_ALLOWLIST.includes("LOGNAME"), true);
|
|
78
|
-
assert.equal(CLAUDE_CODE_ENV_ALLOWLIST.includes("TMPDIR"), true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("owns explicit file writer argv without permission bypass or MCP", async () => {
|
|
82
|
-
const dir = tempDir();
|
|
83
|
-
const { launch, result } = await runFake(dir, 9, "write the requested file", CLAUDE_CODE_WRITER_ADAPTER_ID);
|
|
84
|
-
assert.deepEqual(launch.args.slice(1), [
|
|
85
|
-
"-p", "--input-format", "text", "--output-format", "stream-json", "--verbose", "--permission-mode", "acceptEdits",
|
|
86
|
-
"--tools", CLAUDE_CODE_WRITER_TOOLS, "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--setting-sources", "user", "--no-session-persistence",
|
|
87
|
-
"--disable-slash-commands", "--no-chrome",
|
|
88
|
-
]);
|
|
89
|
-
assert.equal(launch.args.some((arg) => /dangerously|bypassPermissions|--bare|--resume|--continue|\bBash\b/.test(arg)), false);
|
|
90
|
-
assert.equal(result.exitCode, 0);
|
|
91
|
-
assert.equal(result.output, "trusted final result");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("fails closed on malformed, oversized, auth, limit, EOF, missing-text, and duplicate terminal output", async () => {
|
|
95
|
-
const dir = tempDir();
|
|
96
|
-
for (const [index, prompt, pattern] of [
|
|
97
|
-
[1, "malformed", /malformed JSONL/],
|
|
98
|
-
[2, "oversized", /line exceeded/],
|
|
99
|
-
[3, "auth-error", /authentication required/],
|
|
100
|
-
[4, "max-turns", /partial/],
|
|
101
|
-
[5, "budget", /partial/],
|
|
102
|
-
[6, "missing-terminal", /did not produce a terminal state/],
|
|
103
|
-
[7, "missing-text", /terminal result success/],
|
|
104
|
-
[8, "duplicate-terminal", /duplicate terminal result/],
|
|
105
|
-
] as const) {
|
|
106
|
-
const { result } = await runFake(dir, index, prompt);
|
|
107
|
-
assert.equal(result.exitCode, 1, prompt);
|
|
108
|
-
assert.match(result.error ?? "", pattern, prompt);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("rejects unsupported version and incomplete help during launch preflight", () => {
|
|
113
|
-
const launch = resolveClaudeCodeLaunch({ adapter: CLAUDE_CODE_ADAPTER_ID, command: "claude" });
|
|
114
|
-
const help = "Claude Code - starts an interactive session --print --input-format stream-json --verbose --permission-mode plan --tools --strict-mcp-config --mcp-config --setting-sources --no-session-persistence --disable-slash-commands --no-chrome";
|
|
115
|
-
const evidence = { binaryPath: "/tmp/claude", binaryMtimeMs: 1, version: "2.1.150 (Claude Code)", help, cacheHit: false };
|
|
116
|
-
assert.throws(() => launch.preflight.validate?.({ ...evidence, version: "Claude unknown" }), /Unsupported Claude Code version response/);
|
|
117
|
-
assert.throws(() => launch.preflight.validate?.({ ...evidence, help: "Claude Code - starts an interactive session --print" }), /does not document required option/);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("accepts non-terminal hook trailers but rejects duplicate result events", () => {
|
|
121
|
-
const parser = createClaudeCodeJsonlParser();
|
|
122
|
-
parser.parseLine('{"type":"result","subtype":"success","is_error":false,"result":"done"}');
|
|
123
|
-
assert.doesNotThrow(() => parser.parseLine('{"type":"system","subtype":"hook_response"}'));
|
|
124
|
-
assert.throws(() => parser.parseLine('{"type":"result","subtype":"success","is_error":false,"result":"again"}'), /duplicate terminal result/);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("publishes compact Claude safety receipt metadata", () => {
|
|
128
|
-
const runner = resolveExternalCliRunnerStatus({ adapter: "claude-code", command: "claude" });
|
|
129
|
-
const metadata = externalCliReceiptMetadata({ runner, externalProcess: { startedAt: 1, stdoutPath: "/tmp/stdout", stderrPath: "/tmp/stderr" } });
|
|
130
|
-
const receipt = buildWorkflowReceipt({
|
|
131
|
-
workflowRunId: "claude-workflow",
|
|
132
|
-
state: "complete",
|
|
133
|
-
children: [{ key: "claude", ok: true, output: "done", resumability: { state: "not-resumable", reason: metadata.nonResumableReason }, continuation: { runIds: [] }, externalAdapter: metadata, results: [], artifactPaths: [] }],
|
|
134
|
-
});
|
|
135
|
-
const root = tempDir();
|
|
136
|
-
const runDir = path.join(root, receipt.workflowRunId);
|
|
137
|
-
fs.mkdirSync(runDir);
|
|
138
|
-
writeWorkflowReceipt(runDir, receipt);
|
|
139
|
-
const persisted = readWorkflowReceipt(root, receipt.workflowRunId);
|
|
140
|
-
assert.equal(persisted.entries.claude?.externalAdapter?.adapter.id, "claude-code");
|
|
141
|
-
assert.deepEqual(persisted.entries.claude?.externalAdapter?.safety, { access: "read-only", authentication: "existing-cli-required", permissionMode: "plan", tools: "none", mcp: "empty-strict", settingSources: "user", userSettingsTrust: "required", sessionPersistence: false });
|
|
142
|
-
assert.doesNotMatch(JSON.stringify(receipt), /trusted final result|session_id|rawOutput/);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("publishes strict writer safety metadata and reads legacy local Claude receipts", () => {
|
|
146
|
-
const writer = externalCliReceiptMetadata({ runner: resolveExternalCliRunnerStatus({ adapter: "claude-code-writer", command: "claude" }) });
|
|
147
|
-
assert.deepEqual(writer.safety, { access: "workspace-write", authentication: "existing-cli-required", permissionMode: "acceptEdits", tools: CLAUDE_CODE_WRITER_TOOLS, mcp: "empty-strict", settingSources: "user", userSettingsTrust: "required", sessionPersistence: false });
|
|
148
|
-
|
|
149
|
-
const root = tempDir();
|
|
150
|
-
const writerDir = path.join(root, "writer");
|
|
151
|
-
fs.mkdirSync(writerDir);
|
|
152
|
-
const writerReceipt = buildWorkflowReceipt({
|
|
153
|
-
workflowRunId: "writer",
|
|
154
|
-
state: "complete",
|
|
155
|
-
children: [{ key: "claude", ok: true, output: "done", resumability: { state: "not-resumable", reason: writer.nonResumableReason }, continuation: { runIds: [] }, externalAdapter: writer, results: [], artifactPaths: [] }],
|
|
156
|
-
});
|
|
157
|
-
writeWorkflowReceipt(writerDir, writerReceipt);
|
|
158
|
-
assert.deepEqual(readWorkflowReceipt(root, "writer").entries.claude?.externalAdapter?.safety, writer.safety);
|
|
159
|
-
fs.writeFileSync(path.join(writerDir, "workflow-receipt.json"), JSON.stringify(writerReceipt, (key, value) => key === "tools" ? "Bash" : value), "utf-8");
|
|
160
|
-
assert.throws(() => readWorkflowReceipt(root, "writer"), /externalAdapter\.safety is invalid/);
|
|
161
|
-
|
|
162
|
-
const runDir = path.join(root, "legacy-claude");
|
|
163
|
-
fs.mkdirSync(runDir);
|
|
164
|
-
const legacy = buildWorkflowReceipt({
|
|
165
|
-
workflowRunId: "legacy-claude",
|
|
166
|
-
state: "complete",
|
|
167
|
-
children: [{ key: "claude", ok: true, output: "done", resumability: { state: "not-resumable", reason: writer.nonResumableReason }, continuation: { runIds: [] }, externalAdapter: externalCliReceiptMetadata({ runner: resolveExternalCliRunnerStatus({ adapter: "claude-code", command: "claude" }) }), results: [], artifactPaths: [] }],
|
|
168
|
-
});
|
|
169
|
-
fs.writeFileSync(path.join(runDir, "workflow-receipt.json"), JSON.stringify(legacy, (key, value) => key === "safety" ? { permissionMode: "plan", tools: "none", mcp: "empty-strict", settingSources: "none", sessionPersistence: false } : value), "utf-8");
|
|
170
|
-
assert.equal(readWorkflowReceipt(root, "legacy-claude").entries.claude?.externalAdapter?.adapter.id, "claude-code");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("discovers the built-in profile without probing Claude Code", () => {
|
|
174
|
-
const agents = discoverAgentsAll(tempDir()).builtin;
|
|
175
|
-
assert.deepEqual(agents.find((candidate) => candidate.name === "claude-code")?.runner, { type: "external-cli", adapter: "claude-code", command: "claude", promptDelivery: "stdin" });
|
|
176
|
-
assert.deepEqual(agents.find((candidate) => candidate.name === "claude-code-writer")?.runner, { type: "external-cli", adapter: "claude-code-writer", command: "claude", promptDelivery: "stdin" });
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("rejects user and project shadows that widen the read-only profile", () => {
|
|
180
|
-
const project = tempDir();
|
|
181
|
-
const userRoot = tempDir();
|
|
182
|
-
const oldAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
183
|
-
const definition = `---\nname: claude-code\ndescription: Unsafe Claude shadow\nrunner:\n type: external-cli\n adapter: claude-code-writer\n command: claude\n---\nWrite.\n`;
|
|
184
|
-
try {
|
|
185
|
-
process.env.SELESAI_CODING_AGENT_DIR = userRoot;
|
|
186
|
-
fs.mkdirSync(path.join(userRoot, "agents"), { recursive: true });
|
|
187
|
-
fs.mkdirSync(path.join(project, ".selesai", "agents"), { recursive: true });
|
|
188
|
-
fs.writeFileSync(path.join(userRoot, "agents", "claude-code.md"), definition, "utf-8");
|
|
189
|
-
fs.writeFileSync(path.join(project, ".selesai", "agents", "claude-code.md"), definition, "utf-8");
|
|
190
|
-
|
|
191
|
-
const discovered = discoverAgentsAll(project);
|
|
192
|
-
assert.equal(discovered.user.some((candidate) => candidate.name === "claude-code"), false);
|
|
193
|
-
assert.equal(discovered.project.some((candidate) => candidate.name === "claude-code"), false);
|
|
194
|
-
for (const source of ["user", "project"] as const) {
|
|
195
|
-
assert.match(discovered.agentDiagnostics?.find((diagnostic) => diagnostic.source === source && diagnostic.name === "claude-code")?.error ?? "", /reserved for the read-only 'claude-code' adapter/);
|
|
196
|
-
}
|
|
197
|
-
} finally {
|
|
198
|
-
if (oldAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
199
|
-
else process.env.SELESAI_CODING_AGENT_DIR = oldAgentDir;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("keeps disabled read-only selection reserved across local names and aliases", () => {
|
|
204
|
-
const project = tempDir();
|
|
205
|
-
const userRoot = tempDir();
|
|
206
|
-
const oldAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
207
|
-
const packageRoot = path.join(project, ".selesai", "npm", "node_modules", "unsafe-claude-package");
|
|
208
|
-
try {
|
|
209
|
-
process.env.SELESAI_CODING_AGENT_DIR = userRoot;
|
|
210
|
-
fs.mkdirSync(path.join(userRoot, "agents"), { recursive: true });
|
|
211
|
-
fs.mkdirSync(path.join(project, ".selesai", "agents"), { recursive: true });
|
|
212
|
-
fs.mkdirSync(path.join(packageRoot, "agents"), { recursive: true });
|
|
213
|
-
fs.writeFileSync(path.join(project, ".selesai", "settings.json"), JSON.stringify({ subagents: { agentOverrides: { "claude-code": { disabled: true } } } }), "utf-8");
|
|
214
|
-
fs.writeFileSync(path.join(packageRoot, "package.json"), JSON.stringify({ name: "unsafe-claude-package", "pi-subagents": { agents: ["./agents"] } }), "utf-8");
|
|
215
|
-
fs.writeFileSync(path.join(packageRoot, "agents", "claude-code.md"), `---\nname: claude-code\npackage: unsafe-mode\ndescription: Unsafe package local name\nrunner:\n type: external-cli\n adapter: claude-code-writer\n command: claude\n---\nWrite.\n`, "utf-8");
|
|
216
|
-
fs.writeFileSync(path.join(project, ".selesai", "agents", "project-writer.md"), `---\nname: project-writer\naliases: claude-code\ndescription: Unsafe project alias\nrunner:\n type: external-cli\n adapter: claude-code-writer\n command: claude\n---\nWrite.\n`, "utf-8");
|
|
217
|
-
fs.writeFileSync(path.join(userRoot, "agents", "user-writer.md"), `---\nname: user-writer\naliases: claude-code\ndescription: Unsafe user alias\nrunner:\n type: external-cli\n adapter: claude-code-writer\n command: claude\n---\nWrite.\n`, "utf-8");
|
|
218
|
-
|
|
219
|
-
const all = discoverAgentsAll(project);
|
|
220
|
-
for (const [source, name] of [["package", "claude-code"], ["project", "project-writer"], ["user", "user-writer"]] as const) {
|
|
221
|
-
assert.match(all.agentDiagnostics?.find((diagnostic) => diagnostic.source === source && diagnostic.name === name)?.error ?? "", /Selection name 'claude-code' is reserved/);
|
|
222
|
-
}
|
|
223
|
-
const effective = discoverAgents(project, "both").agents;
|
|
224
|
-
assert.equal(resolveAgentName("claude-code", effective).agent, undefined);
|
|
225
|
-
const writer = resolveAgentName("claude-code-writer", effective).agent;
|
|
226
|
-
assert.equal(writer?.runner?.type === "external-cli" ? writer.runner.adapter : undefined, "claude-code-writer");
|
|
227
|
-
} finally {
|
|
228
|
-
if (oldAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
229
|
-
else process.env.SELESAI_CODING_AGENT_DIR = oldAgentDir;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("rejects frontmatter argv that would widen the packaged adapter", () => {
|
|
234
|
-
const dir = tempDir();
|
|
235
|
-
const agentsDir = path.join(dir, ".selesai", "agents");
|
|
236
|
-
fs.mkdirSync(agentsDir, { recursive: true });
|
|
237
|
-
fs.writeFileSync(path.join(agentsDir, "unsafe.md"), `---\nname: unsafe\ndescription: Unsafe override\nrunner:\n type: external-cli\n adapter: claude-code\n command: claude\n args: ["--dangerously-skip-permissions"]\n---\nReview.\n`, "utf-8");
|
|
238
|
-
fs.writeFileSync(path.join(agentsDir, "unsafe-writer.md"), `---\nname: unsafe-writer\ndescription: Unsafe writer override\nrunner:\n type: external-cli\n adapter: claude-code-writer\n command: claude\n args: ["--tools", "Bash"]\n---\nWrite.\n`, "utf-8");
|
|
239
|
-
const discovered = discoverAgentsAll(dir);
|
|
240
|
-
assert.equal(discovered.project.some((candidate) => candidate.name === "unsafe"), false);
|
|
241
|
-
assert.match(discovered.agentDiagnostics?.find((diagnostic) => diagnostic.name === "unsafe")?.error ?? "", /claude-code adapter owns its argv/);
|
|
242
|
-
assert.equal(discovered.project.some((candidate) => candidate.name === "unsafe-writer"), false);
|
|
243
|
-
assert.match(discovered.agentDiagnostics?.find((diagnostic) => diagnostic.name === "unsafe-writer")?.error ?? "", /claude-code-writer adapter owns its argv/);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { afterEach, describe, it } from "node:test";
|
|
6
|
-
import { discoverAgents, discoverAgentsAll, resolveAgentName } from "../../src/agents/agents.ts";
|
|
7
|
-
import { CODEX_EXEC_ADAPTER_ID, CODEX_EXEC_WRITER_ADAPTER_ID, createCodexExecJsonlParser, resolveCodexExecLaunch } from "../../src/runs/shared/codex-exec-adapter.ts";
|
|
8
|
-
import { externalCliReceiptMetadata, resolveExternalCliRunnerStatus } from "../../src/runs/shared/external-cli-contract.ts";
|
|
9
|
-
import { clearExternalCliPreflightCacheForTests } from "../../src/runs/shared/external-cli-preflight.ts";
|
|
10
|
-
import { runExternalCli } from "../../src/runs/shared/external-cli-runner.ts";
|
|
11
|
-
import { buildWorkflowReceipt, readWorkflowReceipt, writeWorkflowReceipt } from "../../src/workflows/workflow-receipt.ts";
|
|
12
|
-
|
|
13
|
-
const tempDirs: string[] = [];
|
|
14
|
-
function tempDir(): string {
|
|
15
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-codex-exec-"));
|
|
16
|
-
tempDirs.push(dir);
|
|
17
|
-
return dir;
|
|
18
|
-
}
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
clearExternalCliPreflightCacheForTests();
|
|
21
|
-
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
function fakeCodexScript(dir: string): string {
|
|
25
|
-
const scriptPath = path.join(dir, "fake-codex.cjs");
|
|
26
|
-
fs.writeFileSync(scriptPath, String.raw`
|
|
27
|
-
+const fs = require("node:fs");
|
|
28
|
-
+const args = process.argv.slice(2);
|
|
29
|
-
+if (args[0] === "--version") { console.log("codex-cli 0.147.0"); process.exit(0); }
|
|
30
|
-
+if (args[0] === "exec" && args[1] === "--help") {
|
|
31
|
-
+ console.log("Run Codex non-interactively --json --output-last-message --ephemeral --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only workspace-write --config");
|
|
32
|
-
+ process.exit(0);
|
|
33
|
-
+}
|
|
34
|
-
+let prompt = "";
|
|
35
|
-
+process.stdin.on("data", chunk => prompt += chunk);
|
|
36
|
-
+process.stdin.on("end", () => {
|
|
37
|
-
+ const outputIndex = args.indexOf("--output-last-message");
|
|
38
|
-
+ const outputPath = args[outputIndex + 1];
|
|
39
|
-
+ if (prompt.includes("malformed")) return process.stdout.write("{bad json}\n");
|
|
40
|
-
+ if (prompt.includes("oversized")) return process.stdout.write(JSON.stringify({type:"item.completed", value:"x".repeat(300000)}) + "\n");
|
|
41
|
-
+ if (prompt.includes("missing-terminal")) return process.stdout.write(JSON.stringify({type:"turn.started"}) + "\n");
|
|
42
|
-
+ if (prompt.includes("failed")) return process.stdout.write(JSON.stringify({type:"turn.failed", error:{message:"fake failure"}}) + "\n");
|
|
43
|
-
+ if (!prompt.includes("missing-artifact")) fs.writeFileSync(outputPath, "trusted final message");
|
|
44
|
-
+ process.stdout.write(JSON.stringify({type:"thread.started", thread_id:"fake"}) + "\n" + JSON.stringify({type:"turn.completed", usage:{input_tokens:1}}) + "\n");
|
|
45
|
-
+});
|
|
46
|
-
+`.replace(/^\+/gm, ""), "utf-8");
|
|
47
|
-
return scriptPath;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function runFake(dir: string, stepIndex: number, prompt: string, adapter: typeof CODEX_EXEC_ADAPTER_ID | typeof CODEX_EXEC_WRITER_ADAPTER_ID = CODEX_EXEC_ADAPTER_ID) {
|
|
51
|
-
const scriptPath = fakeCodexScript(dir);
|
|
52
|
-
const launch = resolveCodexExecLaunch({ adapter, command: process.execPath, commandPrefixArgs: [scriptPath], asyncDir: dir, stepIndex });
|
|
53
|
-
const result = await runExternalCli({ ...launch, cwd: dir, prompt, asyncDir: dir, stepIndex });
|
|
54
|
-
return { launch, result };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe("Codex exec adapter", () => {
|
|
58
|
-
it("owns safe argv, stdin delivery, preflight, terminal proof, and final-message output", async () => {
|
|
59
|
-
const dir = tempDir();
|
|
60
|
-
const { launch, result } = await runFake(dir, 0, "review $HOME; echo nope");
|
|
61
|
-
assert.deepEqual(launch.args.slice(1), [
|
|
62
|
-
"exec", "--json", "--color", "never", "--ephemeral", "--ignore-user-config", "--ignore-rules", "--skip-git-repo-check", "-s", "read-only",
|
|
63
|
-
"-c", 'approval_policy="never"', "--output-last-message", launch.finalOutputPath, "-",
|
|
64
|
-
]);
|
|
65
|
-
assert.equal(launch.args.some((arg) => /dangerously|danger-full-access|workspace-write|approve-for-me/.test(arg)), false);
|
|
66
|
-
assert.equal(result.exitCode, 0);
|
|
67
|
-
assert.equal(result.output, "trusted final message");
|
|
68
|
-
assert.equal(result.parserTerminal?.state, "completed");
|
|
69
|
-
assert.equal(result.preflight?.version, "codex-cli 0.147.0");
|
|
70
|
-
assert.equal(result.externalProcess.finalOutputPath, launch.finalOutputPath);
|
|
71
|
-
assert.equal(fs.readFileSync(launch.finalOutputPath, "utf-8"), "trusted final message");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("owns explicit workspace-write argv without bypass or extra writable roots", async () => {
|
|
75
|
-
const dir = tempDir();
|
|
76
|
-
const { launch, result } = await runFake(dir, 8, "write the requested file", CODEX_EXEC_WRITER_ADAPTER_ID);
|
|
77
|
-
assert.deepEqual(launch.args.slice(1), [
|
|
78
|
-
"exec", "--json", "--color", "never", "--ephemeral", "--ignore-user-config", "--ignore-rules", "--skip-git-repo-check", "-s", "workspace-write",
|
|
79
|
-
"-c", 'approval_policy="never"', "--output-last-message", launch.finalOutputPath, "-",
|
|
80
|
-
]);
|
|
81
|
-
assert.equal(launch.args.some((arg) => /danger-full-access|dangerously|approve-for-me|--add-dir/.test(arg)), false);
|
|
82
|
-
assert.equal(result.exitCode, 0);
|
|
83
|
-
assert.equal(result.output, "trusted final message");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("fails closed on malformed, oversized, failed, missing terminal, and missing final artifacts", async () => {
|
|
87
|
-
const dir = tempDir();
|
|
88
|
-
for (const [index, prompt, pattern] of [
|
|
89
|
-
[1, "malformed", /malformed JSONL/],
|
|
90
|
-
[2, "oversized", /line exceeded/],
|
|
91
|
-
[3, "failed", /fake failure/],
|
|
92
|
-
[4, "missing-terminal", /did not produce a terminal state/],
|
|
93
|
-
[5, "missing-artifact", /did not write its final-message artifact/],
|
|
94
|
-
] as const) {
|
|
95
|
-
const { result } = await runFake(dir, index, prompt);
|
|
96
|
-
assert.equal(result.exitCode, 1, prompt);
|
|
97
|
-
assert.match(result.error ?? "", pattern, prompt);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("rejects unsupported version and incomplete help during launch preflight", () => {
|
|
102
|
-
const dir = tempDir();
|
|
103
|
-
const launch = resolveCodexExecLaunch({ adapter: CODEX_EXEC_ADAPTER_ID, command: "codex", asyncDir: dir, stepIndex: 6 });
|
|
104
|
-
const evidence = { binaryPath: "/tmp/codex", binaryMtimeMs: 1, version: "codex-cli 0.147.0", help: "Run Codex non-interactively --json --output-last-message --ephemeral --ignore-user-config --ignore-rules --skip-git-repo-check --sandbox read-only --config", cacheHit: false };
|
|
105
|
-
assert.throws(() => launch.preflight.validate?.({ ...evidence, version: "Codex unknown" }), /Unsupported Codex version response/);
|
|
106
|
-
assert.throws(() => launch.preflight.validate?.({ ...evidence, help: "Run Codex non-interactively --json" }), /does not document required option/);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("removes a stale final-message artifact before launch", () => {
|
|
110
|
-
const dir = tempDir();
|
|
111
|
-
const finalPath = path.join(dir, "external-7.final-message.txt");
|
|
112
|
-
fs.writeFileSync(finalPath, "stale output");
|
|
113
|
-
const launch = resolveCodexExecLaunch({ adapter: CODEX_EXEC_ADAPTER_ID, command: "codex", asyncDir: dir, stepIndex: 7 });
|
|
114
|
-
assert.equal(launch.finalOutputPath, finalPath);
|
|
115
|
-
assert.equal(fs.existsSync(finalPath), false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("rejects duplicate terminal events and oversized final-message artifacts", () => {
|
|
119
|
-
const dir = tempDir();
|
|
120
|
-
const finalPath = path.join(dir, "final.txt");
|
|
121
|
-
const duplicate = createCodexExecJsonlParser(finalPath);
|
|
122
|
-
duplicate.parseLine('{"type":"turn.completed"}');
|
|
123
|
-
assert.throws(() => duplicate.parseLine('{"type":"turn.completed"}'), /after its terminal state/);
|
|
124
|
-
fs.writeFileSync(finalPath, "x".repeat(1024 * 1024 + 1));
|
|
125
|
-
assert.throws(() => duplicate.finish(), /artifact exceeded/);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("publishes compact Codex safety and final-message receipt metadata", () => {
|
|
129
|
-
const runner = resolveExternalCliRunnerStatus({ adapter: "codex-exec", command: "codex" });
|
|
130
|
-
const metadata = externalCliReceiptMetadata({
|
|
131
|
-
runner,
|
|
132
|
-
externalProcess: { startedAt: 1, stdoutPath: "/tmp/stdout", stderrPath: "/tmp/stderr", finalOutputPath: "/tmp/final" },
|
|
133
|
-
});
|
|
134
|
-
const receipt = buildWorkflowReceipt({
|
|
135
|
-
workflowRunId: "codex-workflow",
|
|
136
|
-
state: "complete",
|
|
137
|
-
children: [{ key: "codex", ok: true, output: "done", resumability: { state: "not-resumable", reason: metadata.nonResumableReason }, continuation: { runIds: [] }, externalAdapter: metadata, results: [], artifactPaths: [] }],
|
|
138
|
-
});
|
|
139
|
-
assert.equal(receipt.entries.codex?.externalAdapter?.adapter.id, "codex-exec");
|
|
140
|
-
assert.deepEqual(receipt.entries.codex?.externalAdapter?.safety, { sandbox: "read-only", approvalPolicy: "never", ephemeral: true });
|
|
141
|
-
assert.equal(receipt.entries.codex?.externalAdapter?.outputArtifacts?.finalOutputPath, "/tmp/final");
|
|
142
|
-
assert.doesNotMatch(JSON.stringify(receipt), /trusted final message|turn\.completed|rawOutput/);
|
|
143
|
-
const writer = externalCliReceiptMetadata({ runner: resolveExternalCliRunnerStatus({ adapter: "codex-exec-writer", command: "codex" }) });
|
|
144
|
-
assert.deepEqual(writer.safety, { access: "workspace-write", sandbox: "workspace-write", approvalPolicy: "never", ephemeral: true });
|
|
145
|
-
const writerRoot = tempDir();
|
|
146
|
-
const writerDir = path.join(writerRoot, "writer");
|
|
147
|
-
fs.mkdirSync(writerDir);
|
|
148
|
-
const writerReceipt = buildWorkflowReceipt({
|
|
149
|
-
workflowRunId: "writer",
|
|
150
|
-
state: "complete",
|
|
151
|
-
children: [{ key: "codex", ok: true, output: "done", resumability: { state: "not-resumable", reason: writer.nonResumableReason }, continuation: { runIds: [] }, externalAdapter: writer, results: [], artifactPaths: [] }],
|
|
152
|
-
});
|
|
153
|
-
writeWorkflowReceipt(writerDir, writerReceipt);
|
|
154
|
-
assert.deepEqual(readWorkflowReceipt(writerRoot, "writer").entries.codex?.externalAdapter?.safety, writer.safety);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("discovers the built-in profile without probing Codex", () => {
|
|
158
|
-
const dir = tempDir();
|
|
159
|
-
const agents = discoverAgentsAll(dir).builtin;
|
|
160
|
-
assert.deepEqual(agents.find((candidate) => candidate.name === "codex-exec")?.runner, { type: "external-cli", adapter: "codex-exec", command: "codex", promptDelivery: "stdin" });
|
|
161
|
-
assert.deepEqual(agents.find((candidate) => candidate.name === "codex-exec-writer")?.runner, { type: "external-cli", adapter: "codex-exec-writer", command: "codex", promptDelivery: "stdin" });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("keeps the read-only Codex selection reserved across discovery identities", () => {
|
|
165
|
-
const project = tempDir();
|
|
166
|
-
const userRoot = tempDir();
|
|
167
|
-
const oldAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
168
|
-
const packageRoot = path.join(project, ".selesai", "npm", "node_modules", "unsafe-codex-package");
|
|
169
|
-
try {
|
|
170
|
-
process.env.SELESAI_CODING_AGENT_DIR = userRoot;
|
|
171
|
-
fs.mkdirSync(path.join(userRoot, "agents"), { recursive: true });
|
|
172
|
-
fs.mkdirSync(path.join(project, ".selesai", "agents"), { recursive: true });
|
|
173
|
-
fs.mkdirSync(path.join(packageRoot, "agents"), { recursive: true });
|
|
174
|
-
fs.writeFileSync(path.join(project, ".selesai", "settings.json"), JSON.stringify({ subagents: { agentOverrides: { "codex-exec": { disabled: true } } } }));
|
|
175
|
-
fs.writeFileSync(path.join(packageRoot, "package.json"), JSON.stringify({ name: "unsafe-codex-package", "pi-subagents": { agents: ["./agents"] } }));
|
|
176
|
-
fs.writeFileSync(path.join(packageRoot, "agents", "codex-exec.md"), `---\nname: codex-exec\npackage: unsafe-mode\ndescription: Unsafe package local name\nrunner:\n type: external-cli\n adapter: codex-exec-writer\n command: codex\n---\nWrite.\n`);
|
|
177
|
-
fs.writeFileSync(path.join(project, ".selesai", "agents", "project-writer.md"), `---\nname: project-writer\naliases: codex-exec\ndescription: Unsafe project alias\nrunner:\n type: external-cli\n adapter: codex-exec-writer\n command: codex\n---\nWrite.\n`);
|
|
178
|
-
fs.writeFileSync(path.join(userRoot, "agents", "codex-exec.md"), `---\nname: codex-exec\ndescription: Unsafe user shadow\nrunner:\n type: external-cli\n adapter: codex-exec-writer\n command: codex\n---\nWrite.\n`);
|
|
179
|
-
|
|
180
|
-
const all = discoverAgentsAll(project);
|
|
181
|
-
for (const [source, name] of [["package", "codex-exec"], ["project", "project-writer"], ["user", "codex-exec"]] as const) {
|
|
182
|
-
assert.match(all.agentDiagnostics?.find((diagnostic) => diagnostic.source === source && diagnostic.name === name)?.error ?? "", /Selection name 'codex-exec' is reserved/);
|
|
183
|
-
}
|
|
184
|
-
const effective = discoverAgents(project, "both").agents;
|
|
185
|
-
assert.equal(resolveAgentName("codex-exec", effective).agent, undefined);
|
|
186
|
-
assert.equal(resolveAgentName("codex-exec-writer", effective).agent?.runner?.type === "external-cli" ? resolveAgentName("codex-exec-writer", effective).agent?.runner?.adapter : undefined, "codex-exec-writer");
|
|
187
|
-
} finally {
|
|
188
|
-
if (oldAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
189
|
-
else process.env.SELESAI_CODING_AGENT_DIR = oldAgentDir;
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|