@selesai/code 0.13.9 → 0.13.11
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 +19 -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 +8 -0
- package/dist/extensions/herdr-agent-state.ts +310 -0
- package/dist/extensions/package.json +1 -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 +59 -7
- package/dist/modes/rpc/rpc-client.js +65 -11
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.js +280 -12
- package/dist/modes/rpc/rpc-types.d.ts +128 -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 +226 -2
- 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
|
@@ -144,9 +144,6 @@ import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
|
144
144
|
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
145
145
|
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
146
146
|
import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
|
|
147
|
-
import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
|
|
148
|
-
import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.ts";
|
|
149
|
-
import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
|
|
150
147
|
import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
|
|
151
148
|
import { runExternalJob } from "../shared/external-job-runner.ts";
|
|
152
149
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
@@ -1480,28 +1477,15 @@ async function runSingleStepInner(
|
|
|
1480
1477
|
|
|
1481
1478
|
if (step.runner?.type === "external-cli") {
|
|
1482
1479
|
const externalCwd = step.cwd ?? ctx.cwd;
|
|
1483
|
-
const
|
|
1484
|
-
? resolveCodexExecLaunch({ adapter: step.runner.adapter, command: step.runner.command, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
1485
|
-
: step.runner.adapter === "claude-code" || step.runner.adapter === "claude-code-writer"
|
|
1486
|
-
? resolveClaudeCodeLaunch({ adapter: step.runner.adapter, command: step.runner.command })
|
|
1487
|
-
: step.runner.adapter === "cursor-agent" || step.runner.adapter === "cursor-agent-writer"
|
|
1488
|
-
? resolveCursorAgentLaunch({ adapter: step.runner.adapter, command: step.runner.command, cwd: externalCwd, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
|
|
1489
|
-
: undefined;
|
|
1490
|
-
const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}) });
|
|
1480
|
+
const runner = resolveExternalCliRunnerStatus({ ...step.runner });
|
|
1491
1481
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
1492
1482
|
const external = await runExternalCli(omitUndefinedProperties({
|
|
1493
|
-
command:
|
|
1494
|
-
args:
|
|
1483
|
+
command: runner.command,
|
|
1484
|
+
args: runner.args,
|
|
1495
1485
|
cwd: externalCwd,
|
|
1496
1486
|
prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
|
|
1497
1487
|
asyncDir: path.dirname(ctx.outputFile),
|
|
1498
1488
|
stepIndex: ctx.flatIndex,
|
|
1499
|
-
environment: adapterLaunch?.environment,
|
|
1500
|
-
preflight: adapterLaunch?.preflight,
|
|
1501
|
-
parser: adapterLaunch?.parser,
|
|
1502
|
-
finalOutputPath: adapterLaunch?.finalOutputPath,
|
|
1503
|
-
promptFilePath: adapterLaunch?.promptFilePath,
|
|
1504
|
-
temporaryDirectories: adapterLaunch?.temporaryDirectories,
|
|
1505
1489
|
registerTimeout: ctx.registerTimeout,
|
|
1506
1490
|
registerStop: ctx.registerStop,
|
|
1507
1491
|
timeoutMessage: ctx.timeoutMessage,
|
|
@@ -22,43 +22,27 @@ const PROMPT_FILE_UNSUPPORTED = {
|
|
|
22
22
|
resume: "The one-shot prompt-file adapter does not retain a durable external session identity.",
|
|
23
23
|
} as const;
|
|
24
24
|
|
|
25
|
-
export const CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS = [
|
|
26
|
-
"codex-exec",
|
|
27
|
-
"codex-exec-writer",
|
|
28
|
-
"claude-code",
|
|
29
|
-
"claude-code-writer",
|
|
30
|
-
"cursor-agent",
|
|
31
|
-
"cursor-agent-writer",
|
|
32
|
-
] as const;
|
|
25
|
+
export const CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS = [] as const;
|
|
33
26
|
export type CodeOwnedExternalCliAdapterId = typeof CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS[number];
|
|
34
27
|
|
|
35
28
|
const CODE_OWNED_EXTERNAL_CLI_ADAPTER_ID_SET = new Set<string>(CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS);
|
|
36
|
-
export const CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL =
|
|
29
|
+
export const CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL = "(none)";
|
|
37
30
|
|
|
38
31
|
export function isCodeOwnedExternalCliAdapterId(value: unknown): value is CodeOwnedExternalCliAdapterId {
|
|
39
32
|
return typeof value === "string" && CODE_OWNED_EXTERNAL_CLI_ADAPTER_ID_SET.has(value);
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
const RESERVED_READ_ONLY_ADAPTERS = [
|
|
43
|
-
{ name: "claude-code", writer: "claude-code-writer", access: "file-write" },
|
|
44
|
-
{ name: "codex-exec", writer: "codex-exec-writer", access: "workspace-write" },
|
|
45
|
-
{ name: "cursor-agent", writer: "cursor-agent-writer", access: "workspace-write" },
|
|
46
|
-
] as const;
|
|
35
|
+
const RESERVED_READ_ONLY_ADAPTERS = [] as const;
|
|
47
36
|
|
|
48
37
|
export function validateCodeOwnedProfileRunner(
|
|
49
|
-
|
|
38
|
+
_agent: {
|
|
50
39
|
name: string;
|
|
51
40
|
localName?: string;
|
|
52
41
|
aliases?: readonly string[];
|
|
53
42
|
runner?: { type: string; adapter?: string };
|
|
54
43
|
},
|
|
55
44
|
): string | undefined {
|
|
56
|
-
|
|
57
|
-
for (const adapter of RESERVED_READ_ONLY_ADAPTERS) {
|
|
58
|
-
if (selectionNames.includes(adapter.name) && !(agent.runner?.type === "external-cli" && agent.runner.adapter === adapter.name)) {
|
|
59
|
-
return `Selection name '${adapter.name}' is reserved for the read-only '${adapter.name}' adapter. Use '${adapter.writer}' for explicit ${adapter.access} access.`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
45
|
+
// With no code-owned adapter profiles bundled, no selection names are reserved.
|
|
62
46
|
return undefined;
|
|
63
47
|
}
|
|
64
48
|
|
|
@@ -81,26 +65,13 @@ export function resolveExternalCliRunnerStatus(input: {
|
|
|
81
65
|
promptDelivery?: "stdin";
|
|
82
66
|
capabilities?: ExternalCliCapabilityNarrowing;
|
|
83
67
|
}): ExternalCliRunnerStatus {
|
|
84
|
-
const
|
|
85
|
-
const codexExecWriter = input.adapter === "codex-exec-writer";
|
|
86
|
-
const claudeCode = input.adapter === "claude-code";
|
|
87
|
-
const claudeCodeWriter = input.adapter === "claude-code-writer";
|
|
88
|
-
const cursorAgent = input.adapter === "cursor-agent";
|
|
89
|
-
const cursorAgentWriter = input.adapter === "cursor-agent-writer";
|
|
90
|
-
const cursor = cursorAgent || cursorAgentWriter;
|
|
91
|
-
const unsupported = cursor ? PROMPT_FILE_UNSUPPORTED : UNSUPPORTED;
|
|
68
|
+
const unsupported = UNSUPPORTED;
|
|
92
69
|
return {
|
|
93
70
|
type: "external-cli",
|
|
94
71
|
command: input.command,
|
|
95
72
|
args: input.args ?? [],
|
|
96
|
-
promptDelivery:
|
|
97
|
-
adapter: { id: input.adapter ?? "external-cli", version: 1, executionMode:
|
|
98
|
-
...(codexExec ? { safety: { sandbox: "read-only" as const, approvalPolicy: "never" as const, ephemeral: true as const } } : {}),
|
|
99
|
-
...(codexExecWriter ? { safety: { access: "workspace-write" as const, sandbox: "workspace-write" as const, approvalPolicy: "never" as const, ephemeral: true as const } } : {}),
|
|
100
|
-
...(claudeCode ? { safety: { access: "read-only" as const, authentication: "existing-cli-required" as const, permissionMode: "plan" as const, tools: "none" as const, mcp: "empty-strict" as const, settingSources: "user" as const, userSettingsTrust: "required" as const, sessionPersistence: false as const } } : {}),
|
|
101
|
-
...(claudeCodeWriter ? { safety: { access: "workspace-write" as const, authentication: "existing-cli-required" as const, permissionMode: "acceptEdits" as const, tools: "Read,Write,Edit,Glob,Grep" as const, mcp: "empty-strict" as const, settingSources: "user" as const, userSettingsTrust: "required" as const, sessionPersistence: false as const } } : {}),
|
|
102
|
-
...(cursorAgent ? { safety: { access: "read-only" as const, authentication: "cursor-api-key-or-existing-login" as const, mode: "ask" as const, sandbox: "enabled" as const, workspaceTrust: "existing-required" as const, sessionReuse: false as const } } : {}),
|
|
103
|
-
...(cursorAgentWriter ? { safety: { access: "workspace-write" as const, authentication: "cursor-api-key-or-existing-login" as const, mode: "print" as const, sandbox: "enabled" as const, workspaceTrust: "existing-required" as const, sessionReuse: false as const } } : {}),
|
|
73
|
+
promptDelivery: input.promptDelivery ?? "stdin",
|
|
74
|
+
adapter: { id: input.adapter ?? "external-cli", version: 1, executionMode: "one-shot-stdin" },
|
|
104
75
|
capabilities: {
|
|
105
76
|
stop: true,
|
|
106
77
|
steer: false,
|
|
@@ -1652,7 +1652,7 @@ export type AgentRunnerConfig =
|
|
|
1652
1652
|
| { type: "pi" }
|
|
1653
1653
|
| {
|
|
1654
1654
|
type: "external-cli";
|
|
1655
|
-
adapter?:
|
|
1655
|
+
adapter?: never;
|
|
1656
1656
|
command: string;
|
|
1657
1657
|
args?: string[];
|
|
1658
1658
|
promptDelivery?: "stdin";
|
|
@@ -1678,7 +1678,7 @@ export interface ExternalCliCapabilities {
|
|
|
1678
1678
|
}
|
|
1679
1679
|
|
|
1680
1680
|
export interface ExternalCliReceiptMetadata {
|
|
1681
|
-
adapter: { id: "external-cli" | "
|
|
1681
|
+
adapter: { id: "external-cli" | "grok-build"; version: 1; executionMode: "one-shot-stdin" | "one-shot-prompt-file" };
|
|
1682
1682
|
capabilities: ExternalCliCapabilities;
|
|
1683
1683
|
safety?:
|
|
1684
1684
|
| { sandbox: "read-only"; approvalPolicy: "never"; ephemeral: true }
|
|
@@ -127,7 +127,7 @@ function parseExternalCliReceiptMetadata(value: unknown, key: string, source: st
|
|
|
127
127
|
const adapterRecord = adapter as Record<string, unknown>;
|
|
128
128
|
const unknownAdapter = Object.keys(adapterRecord).filter((field) => !["id", "version", "executionMode"].includes(field));
|
|
129
129
|
if (unknownAdapter.length > 0) throw new Error(`${label}.adapter has unsupported fields: ${unknownAdapter.join(", ")}.`);
|
|
130
|
-
if ((adapterRecord.id !== "external-cli" && adapterRecord.id !== "
|
|
130
|
+
if ((adapterRecord.id !== "external-cli" && adapterRecord.id !== "grok-build") || adapterRecord.version !== 1 || adapterRecord.executionMode !== (adapterRecord.id === "grok-build" ? "one-shot-prompt-file" : "one-shot-stdin")) throw new Error(`${label}.adapter is invalid.`);
|
|
131
131
|
const capabilities = metadata.capabilities;
|
|
132
132
|
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities)) throw new Error(`${label}.capabilities must be an object.`);
|
|
133
133
|
const capabilityRecord = capabilities as Record<string, unknown>;
|
|
@@ -137,43 +137,7 @@ function parseExternalCliReceiptMetadata(value: unknown, key: string, source: st
|
|
|
137
137
|
if (capabilityRecord[capability] !== expected) throw new Error(`${label}.capabilities.${capability} is invalid.`);
|
|
138
138
|
}
|
|
139
139
|
const safety = metadata.safety;
|
|
140
|
-
if (adapterRecord.id === "
|
|
141
|
-
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
142
|
-
const safetyRecord = safety as Record<string, unknown>;
|
|
143
|
-
const unknownSafety = Object.keys(safetyRecord).filter((field) => !["sandbox", "approvalPolicy", "ephemeral"].includes(field));
|
|
144
|
-
if (unknownSafety.length > 0) throw new Error(`${label}.safety has unsupported fields: ${unknownSafety.join(", ")}.`);
|
|
145
|
-
if (safetyRecord.sandbox !== "read-only" || safetyRecord.approvalPolicy !== "never" || safetyRecord.ephemeral !== true) throw new Error(`${label}.safety is invalid.`);
|
|
146
|
-
} else if (adapterRecord.id === "codex-exec-writer") {
|
|
147
|
-
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
148
|
-
const safetyRecord = safety as Record<string, unknown>;
|
|
149
|
-
const unknownSafety = Object.keys(safetyRecord).filter((field) => !["access", "sandbox", "approvalPolicy", "ephemeral"].includes(field));
|
|
150
|
-
if (unknownSafety.length > 0) throw new Error(`${label}.safety has unsupported fields: ${unknownSafety.join(", ")}.`);
|
|
151
|
-
if (safetyRecord.access !== "workspace-write" || safetyRecord.sandbox !== "workspace-write" || safetyRecord.approvalPolicy !== "never" || safetyRecord.ephemeral !== true) throw new Error(`${label}.safety is invalid.`);
|
|
152
|
-
} else if (adapterRecord.id === "claude-code") {
|
|
153
|
-
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
154
|
-
const safetyRecord = safety as Record<string, unknown>;
|
|
155
|
-
const legacy = safetyRecord.authentication === undefined;
|
|
156
|
-
const unknownSafety = Object.keys(safetyRecord).filter((field) => !(legacy
|
|
157
|
-
? ["permissionMode", "tools", "mcp", "settingSources", "sessionPersistence"]
|
|
158
|
-
: ["access", "authentication", "permissionMode", "tools", "mcp", "settingSources", "userSettingsTrust", "sessionPersistence"]).includes(field));
|
|
159
|
-
if (unknownSafety.length > 0) throw new Error(`${label}.safety has unsupported fields: ${unknownSafety.join(", ")}.`);
|
|
160
|
-
if (legacy) {
|
|
161
|
-
if (safetyRecord.permissionMode !== "plan" || safetyRecord.tools !== "none" || safetyRecord.mcp !== "empty-strict" || safetyRecord.settingSources !== "none" || safetyRecord.sessionPersistence !== false) throw new Error(`${label}.safety is invalid.`);
|
|
162
|
-
} else if (safetyRecord.access !== "read-only" || safetyRecord.authentication !== "existing-cli-required" || safetyRecord.permissionMode !== "plan" || safetyRecord.tools !== "none" || safetyRecord.mcp !== "empty-strict" || safetyRecord.settingSources !== "user" || safetyRecord.userSettingsTrust !== "required" || safetyRecord.sessionPersistence !== false) throw new Error(`${label}.safety is invalid.`);
|
|
163
|
-
} else if (adapterRecord.id === "claude-code-writer") {
|
|
164
|
-
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
165
|
-
const safetyRecord = safety as Record<string, unknown>;
|
|
166
|
-
const unknownSafety = Object.keys(safetyRecord).filter((field) => !["access", "authentication", "permissionMode", "tools", "mcp", "settingSources", "userSettingsTrust", "sessionPersistence"].includes(field));
|
|
167
|
-
if (unknownSafety.length > 0) throw new Error(`${label}.safety has unsupported fields: ${unknownSafety.join(", ")}.`);
|
|
168
|
-
if (safetyRecord.access !== "workspace-write" || safetyRecord.authentication !== "existing-cli-required" || safetyRecord.permissionMode !== "acceptEdits" || safetyRecord.tools !== "Read,Write,Edit,Glob,Grep" || safetyRecord.mcp !== "empty-strict" || safetyRecord.settingSources !== "user" || safetyRecord.userSettingsTrust !== "required" || safetyRecord.sessionPersistence !== false) throw new Error(`${label}.safety is invalid.`);
|
|
169
|
-
} else if (adapterRecord.id === "cursor-agent" || adapterRecord.id === "cursor-agent-writer") {
|
|
170
|
-
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
171
|
-
const safetyRecord = safety as Record<string, unknown>;
|
|
172
|
-
const unknownSafety = Object.keys(safetyRecord).filter((field) => !["access", "authentication", "mode", "sandbox", "workspaceTrust", "sessionReuse"].includes(field));
|
|
173
|
-
if (unknownSafety.length > 0) throw new Error(`${label}.safety has unsupported fields: ${unknownSafety.join(", ")}.`);
|
|
174
|
-
const writer = adapterRecord.id === "cursor-agent-writer";
|
|
175
|
-
if (safetyRecord.access !== (writer ? "workspace-write" : "read-only") || safetyRecord.authentication !== "cursor-api-key-or-existing-login" || safetyRecord.mode !== (writer ? "print" : "ask") || safetyRecord.sandbox !== "enabled" || safetyRecord.workspaceTrust !== "existing-required" || safetyRecord.sessionReuse !== false) throw new Error(`${label}.safety is invalid.`);
|
|
176
|
-
} else if (adapterRecord.id === "grok-build") {
|
|
140
|
+
if (adapterRecord.id === "grok-build") {
|
|
177
141
|
if (!safety || typeof safety !== "object" || Array.isArray(safety)) throw new Error(`${label}.safety is missing.`);
|
|
178
142
|
const safetyRecord = safety as Record<string, unknown>;
|
|
179
143
|
const unknownSafety = Object.keys(safetyRecord).filter((field) => !["access", "authentication", "permissionMode", "tools", "deniedTools", "sandbox", "webSearch", "subagents", "config", "updates", "sessionPersistence"].includes(field));
|
|
@@ -108,55 +108,6 @@ describe("agent management config parsing", () => {
|
|
|
108
108
|
assert.equal(JSON.stringify(capabilities).includes("SYSTEM_PROMPT_SENTINEL"), false);
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
it("rejects management attempts to widen the reserved read-only Claude profile", () => {
|
|
112
|
-
const ctx = { cwd: tempDir, modelRegistry: { getAvailable: () => [] } };
|
|
113
|
-
const writerRunner = { type: "external-cli", adapter: "claude-code-writer", command: "claude" };
|
|
114
|
-
const unsafeCreate = handleCreate({ config: { name: "claude-code", description: "Unsafe shadow", scope: "project", runner: writerRunner } }, ctx);
|
|
115
|
-
assert.equal(unsafeCreate.isError, true);
|
|
116
|
-
assert.match(readText(unsafeCreate), /reserved for the read-only 'claude-code' adapter/);
|
|
117
|
-
const unsafeLocalName = handleCreate({ config: { name: "claude-code", package: "custom", description: "Unsafe local name", scope: "project", runner: writerRunner } }, ctx);
|
|
118
|
-
assert.equal(unsafeLocalName.isError, true);
|
|
119
|
-
assert.match(readText(unsafeLocalName), /Selection name 'claude-code' is reserved/);
|
|
120
|
-
const unsafeAlias = handleCreate({ config: { name: "aliased-writer", aliases: ["claude-code"], description: "Unsafe alias", scope: "project", runner: writerRunner } }, ctx);
|
|
121
|
-
assert.equal(unsafeAlias.isError, true);
|
|
122
|
-
assert.match(readText(unsafeAlias), /Selection name 'claude-code' is reserved/);
|
|
123
|
-
|
|
124
|
-
const readOnlyCreate = handleCreate({ config: { name: "claude-code", description: "Narrow shadow", scope: "project", runner: { type: "external-cli", adapter: "claude-code", command: "claude" } } }, ctx);
|
|
125
|
-
assert.equal(readOnlyCreate.isError, false);
|
|
126
|
-
const unsafeUpdate = handleUpdate({ agent: "claude-code", agentScope: "project", config: { runner: writerRunner } }, ctx);
|
|
127
|
-
assert.equal(unsafeUpdate.isError, true);
|
|
128
|
-
assert.match(readText(unsafeUpdate), /reserved for the read-only 'claude-code' adapter/);
|
|
129
|
-
assert.match(fs.readFileSync(path.join(tempDir, ".selesai", "agents", "claude-code.md"), "utf-8"), /adapter: claude-code\n/);
|
|
130
|
-
|
|
131
|
-
const writerCreate = handleCreate({ config: { name: "custom-writer", description: "Writer", scope: "project", runner: writerRunner } }, ctx);
|
|
132
|
-
assert.equal(writerCreate.isError, false);
|
|
133
|
-
const unsafeAliasUpdate = handleUpdate({ agent: "custom-writer", agentScope: "project", config: { aliases: ["claude-code"] } }, ctx);
|
|
134
|
-
assert.equal(unsafeAliasUpdate.isError, true);
|
|
135
|
-
assert.match(readText(unsafeAliasUpdate), /Selection name 'claude-code' is reserved/);
|
|
136
|
-
const unsafeRename = handleUpdate({ agent: "custom-writer", agentScope: "project", config: { name: "claude-code" } }, ctx);
|
|
137
|
-
assert.equal(unsafeRename.isError, true);
|
|
138
|
-
assert.match(readText(unsafeRename), /reserved for the read-only 'claude-code' adapter/);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("rejects create, update, alias, and rename widening for Codex and Cursor", () => {
|
|
142
|
-
const ctx = { cwd: tempDir, modelRegistry: { getAvailable: () => [] } };
|
|
143
|
-
for (const [readOnly, writer, command] of [["codex-exec", "codex-exec-writer", "codex"], ["cursor-agent", "cursor-agent-writer", "cursor-agent"]] as const) {
|
|
144
|
-
const writerRunner = { type: "external-cli", adapter: writer, command };
|
|
145
|
-
assert.equal(handleCreate({ config: { name: readOnly, description: "Unsafe shadow", scope: "project", runner: writerRunner } }, ctx).isError, true);
|
|
146
|
-
assert.equal(handleCreate({ config: { name: readOnly, package: `custom-${readOnly}`, description: "Unsafe local name", scope: "project", runner: writerRunner } }, ctx).isError, true);
|
|
147
|
-
assert.equal(handleCreate({ config: { name: `${readOnly}-alias`, aliases: [readOnly], description: "Unsafe alias", scope: "project", runner: writerRunner } }, ctx).isError, true);
|
|
148
|
-
|
|
149
|
-
const readOnlyCreate = handleCreate({ config: { name: readOnly, description: "Narrow shadow", scope: "project", runner: { type: "external-cli", adapter: readOnly, command } } }, ctx);
|
|
150
|
-
assert.equal(readOnlyCreate.isError, false);
|
|
151
|
-
assert.equal(handleUpdate({ agent: readOnly, agentScope: "project", config: { runner: writerRunner } }, ctx).isError, true);
|
|
152
|
-
|
|
153
|
-
const customName = `custom-${writer}`;
|
|
154
|
-
assert.equal(handleCreate({ config: { name: customName, description: "Writer", scope: "project", runner: writerRunner } }, ctx).isError, false);
|
|
155
|
-
assert.equal(handleUpdate({ agent: customName, agentScope: "project", config: { aliases: [readOnly] } }, ctx).isError, true);
|
|
156
|
-
assert.equal(handleUpdate({ agent: customName, agentScope: "project", config: { name: readOnly } }, ctx).isError, true);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
111
|
it("lists valid agents and diagnoses malformed agent definitions", () => {
|
|
161
112
|
const agentsDir = path.join(tempDir, ".selesai", "agents");
|
|
162
113
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
@@ -255,24 +255,6 @@ describe("runtime agent registration", () => {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
it("fails closed for builtin and duplicate runtime identities", () => {
|
|
258
|
-
assert.throws(
|
|
259
|
-
() => registerAgent({ pi, name: "claude-code", definition: { description: "Unsafe", systemPrompt: "Write.", runner: { type: "external-cli", adapter: "claude-code-writer", command: "claude" } } }),
|
|
260
|
-
/reserved for the read-only 'claude-code' adapter/,
|
|
261
|
-
);
|
|
262
|
-
assert.throws(
|
|
263
|
-
() => registerAgent({ pi, name: "runtime-writer", definition: { description: "Unsafe alias", systemPrompt: "Write.", aliases: ["claude-code"], runner: { type: "external-cli", adapter: "claude-code-writer", command: "claude" } } }),
|
|
264
|
-
/Selection name 'claude-code' is reserved/,
|
|
265
|
-
);
|
|
266
|
-
for (const [readOnly, writer, command] of [["codex-exec", "codex-exec-writer", "codex"], ["cursor-agent", "cursor-agent-writer", "cursor-agent"]] as const) {
|
|
267
|
-
assert.throws(
|
|
268
|
-
() => registerAgent({ pi, name: readOnly, definition: { description: "Unsafe", systemPrompt: "Write.", runner: { type: "external-cli", adapter: writer, command } } }),
|
|
269
|
-
/reserved for the read-only/,
|
|
270
|
-
);
|
|
271
|
-
assert.throws(
|
|
272
|
-
() => registerAgent({ pi, name: `runtime-${writer}`, definition: { description: "Unsafe alias", systemPrompt: "Write.", aliases: [readOnly], runner: { type: "external-cli", adapter: writer, command } } }),
|
|
273
|
-
/Selection name .* is reserved/,
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
258
|
assert.throws(
|
|
277
259
|
() => registerAgent({ pi, name: "worker", definition: { description: "Bad", systemPrompt: "Bad." } }),
|
|
278
260
|
/Worker|builtin agent 'worker'|collides with builtin agent 'worker'/i,
|
|
@@ -12,7 +12,7 @@ import type { BashResult } from "../../core/bash-executor.ts";
|
|
|
12
12
|
import type { CompactionResult } from "../../core/compaction/index.ts";
|
|
13
13
|
import type { SessionEntry, SessionTreeNode } from "../../core/session-manager.ts";
|
|
14
14
|
import type { ChangelogEntry } from "../../utils/changelog.ts";
|
|
15
|
-
import type { RpcSessionState, RpcSlashCommand } from "./rpc-types.ts";
|
|
15
|
+
import type { RpcSessionInfo, RpcSessionState, RpcSkillInfo, RpcSlashCommand } from "./rpc-types.ts";
|
|
16
16
|
export interface RpcClientOptions {
|
|
17
17
|
/** Path to the CLI entry point (default: searches for dist/cli.js) */
|
|
18
18
|
cliPath?: string;
|
|
@@ -64,8 +64,9 @@ export declare class RpcClient {
|
|
|
64
64
|
* Send a prompt to the agent.
|
|
65
65
|
* Returns immediately after sending; use onEvent() to receive streaming events.
|
|
66
66
|
* Use waitForIdle() to wait for completion.
|
|
67
|
+
* @param queueWhileCompacting - Buffer the prompt during compaction and send after compaction_end.
|
|
67
68
|
*/
|
|
68
|
-
prompt(message: string, images?: ImageContent[]): Promise<void>;
|
|
69
|
+
prompt(message: string, images?: ImageContent[], queueWhileCompacting?: boolean): Promise<void>;
|
|
69
70
|
/**
|
|
70
71
|
* Queue a steering message to interrupt the agent mid-run.
|
|
71
72
|
*/
|
|
@@ -105,9 +106,9 @@ export declare class RpcClient {
|
|
|
105
106
|
id: string;
|
|
106
107
|
}>;
|
|
107
108
|
/**
|
|
108
|
-
* Cycle to next model.
|
|
109
|
+
* Cycle to next (or previous) model.
|
|
109
110
|
*/
|
|
110
|
-
cycleModel(): Promise<{
|
|
111
|
+
cycleModel(direction?: "forward" | "backward"): Promise<{
|
|
111
112
|
model: {
|
|
112
113
|
provider: string;
|
|
113
114
|
id: string;
|
|
@@ -117,8 +118,9 @@ export declare class RpcClient {
|
|
|
117
118
|
} | null>;
|
|
118
119
|
/**
|
|
119
120
|
* Get list of available models.
|
|
121
|
+
* @param refresh - When true, refresh model catalogs before listing (TUI parity).
|
|
120
122
|
*/
|
|
121
|
-
getAvailableModels(): Promise<ModelInfo[]>;
|
|
123
|
+
getAvailableModels(refresh?: boolean): Promise<ModelInfo[]>;
|
|
122
124
|
/**
|
|
123
125
|
* Set thinking level.
|
|
124
126
|
*/
|
|
@@ -180,7 +182,7 @@ export declare class RpcClient {
|
|
|
180
182
|
/**
|
|
181
183
|
* Export session to HTML.
|
|
182
184
|
*/
|
|
183
|
-
exportHtml(outputPath?: string): Promise<{
|
|
185
|
+
exportHtml(outputPath?: string, themeName?: string): Promise<{
|
|
184
186
|
path: string;
|
|
185
187
|
}>;
|
|
186
188
|
/**
|
|
@@ -234,6 +236,48 @@ export declare class RpcClient {
|
|
|
234
236
|
* Set the session display name.
|
|
235
237
|
*/
|
|
236
238
|
setSessionName(name: string): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* Navigate the session tree to a target entry, optionally summarizing the
|
|
241
|
+
* branch being left. Mirrors the TUI tree-selector flow.
|
|
242
|
+
*/
|
|
243
|
+
navigateTree(options: {
|
|
244
|
+
targetId: string;
|
|
245
|
+
summarize?: boolean;
|
|
246
|
+
customInstructions?: string;
|
|
247
|
+
replaceInstructions?: boolean;
|
|
248
|
+
label?: string;
|
|
249
|
+
}): Promise<{
|
|
250
|
+
editorText?: string;
|
|
251
|
+
cancelled: boolean;
|
|
252
|
+
aborted?: boolean;
|
|
253
|
+
summaryEntry?: unknown | null;
|
|
254
|
+
}>;
|
|
255
|
+
/**
|
|
256
|
+
* List sessions. scope "current" = sessions in the active cwd; "all" = across all projects.
|
|
257
|
+
*/
|
|
258
|
+
listSessions(scope?: "current" | "all"): Promise<RpcSessionInfo[]>;
|
|
259
|
+
/**
|
|
260
|
+
* Rename a session (by path). Does not switch the active runtime.
|
|
261
|
+
*/
|
|
262
|
+
renameSession(path: string, name: string): Promise<void>;
|
|
263
|
+
/**
|
|
264
|
+
* Delete a session file (trash first, unlink fallback). Active session is rejected.
|
|
265
|
+
* @returns The method used: "trash" or "unlink".
|
|
266
|
+
*/
|
|
267
|
+
deleteSession(path: string): Promise<{
|
|
268
|
+
method: "trash" | "unlink";
|
|
269
|
+
}>;
|
|
270
|
+
/**
|
|
271
|
+
* Set session-only (ephemeral) scoped models — resolved to concrete model ids,
|
|
272
|
+
* not persisted to settings. Clears scope with an empty array.
|
|
273
|
+
*/
|
|
274
|
+
setSessionModels(options: {
|
|
275
|
+
enabled?: string[];
|
|
276
|
+
reorder?: string[];
|
|
277
|
+
}): Promise<{
|
|
278
|
+
enabled: string[];
|
|
279
|
+
models: ModelInfo[];
|
|
280
|
+
}>;
|
|
237
281
|
/**
|
|
238
282
|
* Get all messages in the session.
|
|
239
283
|
*/
|
|
@@ -242,6 +286,14 @@ export declare class RpcClient {
|
|
|
242
286
|
* Get available commands (extension commands, prompt templates, builtins).
|
|
243
287
|
*/
|
|
244
288
|
getCommands(): Promise<RpcSlashCommand[]>;
|
|
289
|
+
/**
|
|
290
|
+
* Get the resolved skill catalog (per-skill enabled state) plus the raw
|
|
291
|
+
* global+project merged settings.skills patterns for round-trip toggling.
|
|
292
|
+
*/
|
|
293
|
+
getSkills(): Promise<{
|
|
294
|
+
skills: RpcSkillInfo[];
|
|
295
|
+
patterns: string[];
|
|
296
|
+
}>;
|
|
245
297
|
/**
|
|
246
298
|
* Get settings for a scope ("global" | "project" | "effective", default "effective").
|
|
247
299
|
*/
|
|
@@ -361,7 +413,7 @@ export declare class RpcClient {
|
|
|
361
413
|
/**
|
|
362
414
|
* Share the current session as a secret GitHub gist (requires `gh` CLI).
|
|
363
415
|
*/
|
|
364
|
-
shareGist(): Promise<{
|
|
416
|
+
shareGist(themeName?: string): Promise<{
|
|
365
417
|
url: string;
|
|
366
418
|
gistUrl: string;
|
|
367
419
|
}>;
|
|
@@ -131,9 +131,10 @@ export class RpcClient {
|
|
|
131
131
|
* Send a prompt to the agent.
|
|
132
132
|
* Returns immediately after sending; use onEvent() to receive streaming events.
|
|
133
133
|
* Use waitForIdle() to wait for completion.
|
|
134
|
+
* @param queueWhileCompacting - Buffer the prompt during compaction and send after compaction_end.
|
|
134
135
|
*/
|
|
135
|
-
async prompt(message, images) {
|
|
136
|
-
await this.send({ type: "prompt", message, images });
|
|
136
|
+
async prompt(message, images, queueWhileCompacting) {
|
|
137
|
+
await this.send({ type: "prompt", message, images, queueWhileCompacting });
|
|
137
138
|
}
|
|
138
139
|
/**
|
|
139
140
|
* Queue a steering message to interrupt the agent mid-run.
|
|
@@ -184,17 +185,18 @@ export class RpcClient {
|
|
|
184
185
|
return this.getData(response);
|
|
185
186
|
}
|
|
186
187
|
/**
|
|
187
|
-
* Cycle to next model.
|
|
188
|
+
* Cycle to next (or previous) model.
|
|
188
189
|
*/
|
|
189
|
-
async cycleModel() {
|
|
190
|
-
const response = await this.send({ type: "cycle_model" });
|
|
190
|
+
async cycleModel(direction) {
|
|
191
|
+
const response = await this.send({ type: "cycle_model", direction });
|
|
191
192
|
return this.getData(response);
|
|
192
193
|
}
|
|
193
194
|
/**
|
|
194
195
|
* Get list of available models.
|
|
196
|
+
* @param refresh - When true, refresh model catalogs before listing (TUI parity).
|
|
195
197
|
*/
|
|
196
|
-
async getAvailableModels() {
|
|
197
|
-
const response = await this.send({ type: "get_available_models" });
|
|
198
|
+
async getAvailableModels(refresh) {
|
|
199
|
+
const response = await this.send({ type: "get_available_models", refresh });
|
|
198
200
|
return this.getData(response).models;
|
|
199
201
|
}
|
|
200
202
|
/**
|
|
@@ -289,8 +291,8 @@ export class RpcClient {
|
|
|
289
291
|
/**
|
|
290
292
|
* Export session to HTML.
|
|
291
293
|
*/
|
|
292
|
-
async exportHtml(outputPath) {
|
|
293
|
-
const response = await this.send({ type: "export_html", outputPath });
|
|
294
|
+
async exportHtml(outputPath, themeName) {
|
|
295
|
+
const response = await this.send({ type: "export_html", outputPath, themeName });
|
|
294
296
|
return this.getData(response);
|
|
295
297
|
}
|
|
296
298
|
/**
|
|
@@ -351,6 +353,50 @@ export class RpcClient {
|
|
|
351
353
|
async setSessionName(name) {
|
|
352
354
|
await this.send({ type: "set_session_name", name });
|
|
353
355
|
}
|
|
356
|
+
// =========================================================================
|
|
357
|
+
// Tree navigation (rev 3)
|
|
358
|
+
// =========================================================================
|
|
359
|
+
/**
|
|
360
|
+
* Navigate the session tree to a target entry, optionally summarizing the
|
|
361
|
+
* branch being left. Mirrors the TUI tree-selector flow.
|
|
362
|
+
*/
|
|
363
|
+
async navigateTree(options) {
|
|
364
|
+
const response = await this.send({ type: "navigate_tree", ...options });
|
|
365
|
+
return this.getData(response);
|
|
366
|
+
}
|
|
367
|
+
// =========================================================================
|
|
368
|
+
// Session discovery / management (rev 3)
|
|
369
|
+
// =========================================================================
|
|
370
|
+
/**
|
|
371
|
+
* List sessions. scope "current" = sessions in the active cwd; "all" = across all projects.
|
|
372
|
+
*/
|
|
373
|
+
async listSessions(scope) {
|
|
374
|
+
const response = await this.send({ type: "list_sessions", scope });
|
|
375
|
+
return this.getData(response).sessions;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Rename a session (by path). Does not switch the active runtime.
|
|
379
|
+
*/
|
|
380
|
+
async renameSession(path, name) {
|
|
381
|
+
const response = await this.send({ type: "rename_session", path, name });
|
|
382
|
+
this.getData(response);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Delete a session file (trash first, unlink fallback). Active session is rejected.
|
|
386
|
+
* @returns The method used: "trash" or "unlink".
|
|
387
|
+
*/
|
|
388
|
+
async deleteSession(path) {
|
|
389
|
+
const response = await this.send({ type: "delete_session", path });
|
|
390
|
+
return this.getData(response);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Set session-only (ephemeral) scoped models — resolved to concrete model ids,
|
|
394
|
+
* not persisted to settings. Clears scope with an empty array.
|
|
395
|
+
*/
|
|
396
|
+
async setSessionModels(options) {
|
|
397
|
+
const response = await this.send({ type: "set_session_models", ...options });
|
|
398
|
+
return this.getData(response);
|
|
399
|
+
}
|
|
354
400
|
/**
|
|
355
401
|
* Get all messages in the session.
|
|
356
402
|
*/
|
|
@@ -365,6 +411,14 @@ export class RpcClient {
|
|
|
365
411
|
const response = await this.send({ type: "get_commands" });
|
|
366
412
|
return this.getData(response).commands;
|
|
367
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Get the resolved skill catalog (per-skill enabled state) plus the raw
|
|
416
|
+
* global+project merged settings.skills patterns for round-trip toggling.
|
|
417
|
+
*/
|
|
418
|
+
async getSkills() {
|
|
419
|
+
const response = await this.send({ type: "get_skills" });
|
|
420
|
+
return this.getData(response);
|
|
421
|
+
}
|
|
368
422
|
// =========================================================================
|
|
369
423
|
// Settings
|
|
370
424
|
// =========================================================================
|
|
@@ -513,8 +567,8 @@ export class RpcClient {
|
|
|
513
567
|
/**
|
|
514
568
|
* Share the current session as a secret GitHub gist (requires `gh` CLI).
|
|
515
569
|
*/
|
|
516
|
-
async shareGist() {
|
|
517
|
-
const response = await this.send({ type: "share_gist", public: false });
|
|
570
|
+
async shareGist(themeName) {
|
|
571
|
+
const response = await this.send({ type: "share_gist", public: false, themeName });
|
|
518
572
|
return this.getData(response);
|
|
519
573
|
}
|
|
520
574
|
// =========================================================================
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
import type { AgentSessionRuntime } from "../../core/agent-session-runtime.ts";
|
|
14
|
-
export type { RpcCommand, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionState, RpcSlashCommand, PrototypePhase, QuicktypePhase, WorkflowState, } from "./rpc-types.ts";
|
|
14
|
+
export type { RpcCommand, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionInfo, RpcSessionState, RpcSlashCommand, PrototypePhase, QuicktypePhase, WorkflowState, } from "./rpc-types.ts";
|
|
15
15
|
/**
|
|
16
16
|
* Run in RPC mode.
|
|
17
17
|
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|