@selesai/code 0.13.8 → 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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/core/agent-session.d.ts +6 -0
  3. package/dist/core/agent-session.js +5 -3
  4. package/dist/core/settings-manager.d.ts +11 -0
  5. package/dist/core/settings-manager.js +48 -0
  6. package/dist/extensions/auto-session-name.test.ts +13 -1
  7. package/dist/extensions/auto-session-name.ts +8 -4
  8. package/dist/extensions/handoff-new.test.ts +26 -1
  9. package/dist/extensions/handoff-new.ts +5 -0
  10. package/dist/extensions/pi-subagents/README.md +0 -6
  11. package/dist/extensions/pi-subagents/docs/agents.md +1 -114
  12. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +1 -12
  13. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  14. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +3 -19
  15. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +8 -37
  16. package/dist/extensions/pi-subagents/src/shared/types.ts +2 -2
  17. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +2 -38
  18. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +0 -49
  19. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +0 -18
  20. package/dist/modes/rpc/rpc-client.d.ts +180 -10
  21. package/dist/modes/rpc/rpc-client.js +211 -13
  22. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  23. package/dist/modes/rpc/rpc-mode.js +682 -3
  24. package/dist/modes/rpc/rpc-types.d.ts +340 -2
  25. package/dist/skills/improve-codebase/SKILL.md +1 -1
  26. package/dist/skills/planger/SKILL.md +1 -1
  27. package/dist/skills/workflow/SKILL.md +9 -15
  28. package/docs/rpc.md +171 -1
  29. package/package.json +1 -1
  30. package/dist/extensions/pi-subagents/agents/architect.md +0 -170
  31. package/dist/extensions/pi-subagents/agents/builder.md +0 -37
  32. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +0 -15
  33. package/dist/extensions/pi-subagents/agents/claude-code.md +0 -15
  34. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +0 -15
  35. package/dist/extensions/pi-subagents/agents/codex-exec.md +0 -15
  36. package/dist/extensions/pi-subagents/agents/commentator.md +0 -37
  37. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +0 -14
  38. package/dist/extensions/pi-subagents/agents/cursor-agent.md +0 -14
  39. package/dist/extensions/pi-subagents/agents/explorer.md +0 -32
  40. package/dist/extensions/pi-subagents/agents/recapper.md +0 -31
  41. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +0 -129
  42. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +0 -129
  43. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +0 -114
  44. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +0 -52
  45. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +0 -55
  46. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +0 -53
  47. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +0 -57
  48. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +0 -59
  49. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +0 -62
  50. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +0 -245
  51. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +0 -192
  52. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +0 -259
@@ -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 !== "codex-exec" && adapterRecord.id !== "codex-exec-writer" && adapterRecord.id !== "claude-code" && adapterRecord.id !== "claude-code-writer" && adapterRecord.id !== "cursor-agent" && adapterRecord.id !== "cursor-agent-writer" && adapterRecord.id !== "grok-build") || adapterRecord.version !== 1 || adapterRecord.executionMode !== (adapterRecord.id === "cursor-agent" || adapterRecord.id === "cursor-agent-writer" || adapterRecord.id === "grok-build" ? "one-shot-prompt-file" : "one-shot-stdin")) throw new Error(`${label}.adapter is invalid.`);
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 === "codex-exec") {
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,
@@ -4,12 +4,15 @@
4
4
  * Spawns the agent in RPC mode and provides a typed API for all operations.
5
5
  */
6
6
  import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
7
- import type { ImageContent } from "@earendil-works/pi-ai";
7
+ import type { ImageContent, Model } from "@earendil-works/pi-ai";
8
8
  import type { AgentSessionEvent, SessionStats } from "../../core/agent-session.ts";
9
+ import type { KeybindingsConfig } from "../../core/keybindings.ts";
10
+ import type { Settings } from "../../core/settings-manager.ts";
9
11
  import type { BashResult } from "../../core/bash-executor.ts";
10
12
  import type { CompactionResult } from "../../core/compaction/index.ts";
11
13
  import type { SessionEntry, SessionTreeNode } from "../../core/session-manager.ts";
12
- import type { RpcSessionState, RpcSlashCommand } from "./rpc-types.ts";
14
+ import type { ChangelogEntry } from "../../utils/changelog.ts";
15
+ import type { RpcSessionInfo, RpcSessionState, RpcSlashCommand } from "./rpc-types.ts";
13
16
  export interface RpcClientOptions {
14
17
  /** Path to the CLI entry point (default: searches for dist/cli.js) */
15
18
  cliPath?: string;
@@ -61,8 +64,9 @@ export declare class RpcClient {
61
64
  * Send a prompt to the agent.
62
65
  * Returns immediately after sending; use onEvent() to receive streaming events.
63
66
  * Use waitForIdle() to wait for completion.
67
+ * @param queueWhileCompacting - Buffer the prompt during compaction and send after compaction_end.
64
68
  */
65
- prompt(message: string, images?: ImageContent[]): Promise<void>;
69
+ prompt(message: string, images?: ImageContent[], queueWhileCompacting?: boolean): Promise<void>;
66
70
  /**
67
71
  * Queue a steering message to interrupt the agent mid-run.
68
72
  */
@@ -102,9 +106,9 @@ export declare class RpcClient {
102
106
  id: string;
103
107
  }>;
104
108
  /**
105
- * Cycle to next model.
109
+ * Cycle to next (or previous) model.
106
110
  */
107
- cycleModel(): Promise<{
111
+ cycleModel(direction?: "forward" | "backward"): Promise<{
108
112
  model: {
109
113
  provider: string;
110
114
  id: string;
@@ -114,8 +118,9 @@ export declare class RpcClient {
114
118
  } | null>;
115
119
  /**
116
120
  * Get list of available models.
121
+ * @param refresh - When true, refresh model catalogs before listing (TUI parity).
117
122
  */
118
- getAvailableModels(): Promise<ModelInfo[]>;
123
+ getAvailableModels(refresh?: boolean): Promise<ModelInfo[]>;
119
124
  /**
120
125
  * Set thinking level.
121
126
  */
@@ -177,7 +182,7 @@ export declare class RpcClient {
177
182
  /**
178
183
  * Export session to HTML.
179
184
  */
180
- exportHtml(outputPath?: string): Promise<{
185
+ exportHtml(outputPath?: string, themeName?: string): Promise<{
181
186
  path: string;
182
187
  }>;
183
188
  /**
@@ -217,9 +222,9 @@ export declare class RpcClient {
217
222
  leafId: string | null;
218
223
  }>;
219
224
  /**
220
- * Get the session entry tree.
225
+ * Get the session entry tree, optionally filtered.
221
226
  */
222
- getTree(): Promise<{
227
+ getTree(filter?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"): Promise<{
223
228
  tree: SessionTreeNode[];
224
229
  leafId: string | null;
225
230
  }>;
@@ -231,14 +236,179 @@ export declare class RpcClient {
231
236
  * Set the session display name.
232
237
  */
233
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
+ }>;
234
281
  /**
235
282
  * Get all messages in the session.
236
283
  */
237
284
  getMessages(): Promise<AgentMessage[]>;
238
285
  /**
239
- * Get available commands (extension commands, prompt templates).
286
+ * Get available commands (extension commands, prompt templates, builtins).
240
287
  */
241
288
  getCommands(): Promise<RpcSlashCommand[]>;
289
+ /**
290
+ * Get settings for a scope ("global" | "project" | "effective", default "effective").
291
+ */
292
+ getSettings(scope?: "global" | "project" | "effective"): Promise<{
293
+ scope: string;
294
+ settings: Settings;
295
+ }>;
296
+ /**
297
+ * Bulk-update settings and persist.
298
+ */
299
+ setSettings(values: Partial<Settings>, scope?: "global" | "project"): Promise<void>;
300
+ /**
301
+ * Reset settings to bundled factory defaults (backup preserved).
302
+ */
303
+ factoryResetSettings(): Promise<void>;
304
+ /**
305
+ * List providers and the login methods they support.
306
+ */
307
+ getAuthProviders(): Promise<Array<{
308
+ providerId: string;
309
+ name: string;
310
+ authTypes: Array<"oauth" | "api_key">;
311
+ }>>;
312
+ /**
313
+ * Login to a provider. API-key flows emit `extension_ui_request: input` with
314
+ * `inputKind: "secret"`; OAuth flows open a browser and emit notify events.
315
+ */
316
+ login(providerId: string, authType: "oauth" | "api_key"): Promise<{
317
+ providerId: string;
318
+ authType: "oauth" | "api_key";
319
+ message: string;
320
+ }>;
321
+ /**
322
+ * Log out of a provider, removing stored credentials.
323
+ */
324
+ logout(providerId: string): Promise<void>;
325
+ /**
326
+ * Get providers with configured auth (stored credential or environment).
327
+ */
328
+ getAuthState(): Promise<Array<{
329
+ providerId: string;
330
+ authType: "oauth" | "api_key";
331
+ source: string;
332
+ }>>;
333
+ /**
334
+ * Get enabled model patterns (Ctrl+P cycling scope) and available models.
335
+ */
336
+ getScopedModels(): Promise<{
337
+ enabled: string[];
338
+ models: Model<any>[];
339
+ }>;
340
+ /**
341
+ * Set enabled model patterns and/or reorder them (Ctrl+P cycling order).
342
+ */
343
+ setScopedModels(options?: {
344
+ enabled?: string[];
345
+ reorder?: string[];
346
+ }): Promise<string[]>;
347
+ /**
348
+ * Import and resume a session from a JSONL file (replaces current session).
349
+ */
350
+ importJsonl(path: string): Promise<string>;
351
+ /**
352
+ * Run the git helper (worktrees/checkpoints/commits/push). Same behavior as
353
+ * the `prompt` command: success is reported on preflight, then the normal
354
+ * agent/message event stream follows.
355
+ */
356
+ git(command: string): Promise<void>;
357
+ /**
358
+ * Reload keybindings, extensions, skills, prompts, themes, and context files.
359
+ */
360
+ reload(): Promise<void>;
361
+ /**
362
+ * Set or clear a label on a session entry (undefined/empty clears).
363
+ */
364
+ setEntryLabel(entryId: string, label?: string): Promise<string>;
365
+ /**
366
+ * Get the current project trust decision and status.
367
+ */
368
+ getTrust(): Promise<{
369
+ cwd: string;
370
+ savedDecision: {
371
+ path: string;
372
+ decision: boolean;
373
+ } | null;
374
+ projectTrusted: boolean;
375
+ trustRequired: boolean;
376
+ }>;
377
+ /**
378
+ * Save a project trust decision. Decision takes full effect after restart.
379
+ */
380
+ setTrust(decision: boolean | null): Promise<{
381
+ decision: boolean | null;
382
+ restartRequired: boolean;
383
+ }>;
384
+ /**
385
+ * Get the effective keybinding map.
386
+ */
387
+ getHotkeys(): Promise<KeybindingsConfig>;
388
+ /**
389
+ * Get available themes (built-in + custom) and the current theme setting.
390
+ */
391
+ getAvailableThemes(): Promise<{
392
+ themes: Array<{
393
+ name: string;
394
+ path?: string;
395
+ }>;
396
+ current: string | undefined;
397
+ }>;
398
+ /**
399
+ * Get the current version and parsed changelog entries.
400
+ */
401
+ getVersionInfo(): Promise<{
402
+ version: string;
403
+ changelog: ChangelogEntry[];
404
+ }>;
405
+ /**
406
+ * Share the current session as a secret GitHub gist (requires `gh` CLI).
407
+ */
408
+ shareGist(themeName?: string): Promise<{
409
+ url: string;
410
+ gistUrl: string;
411
+ }>;
242
412
  /**
243
413
  * Wait for agent to become idle (no streaming).
244
414
  * Resolves when agent_settled event is received.
@@ -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
  /**
@@ -332,10 +334,10 @@ export class RpcClient {
332
334
  return this.getData(response);
333
335
  }
334
336
  /**
335
- * Get the session entry tree.
337
+ * Get the session entry tree, optionally filtered.
336
338
  */
337
- async getTree() {
338
- const response = await this.send({ type: "get_tree" });
339
+ async getTree(filter) {
340
+ const response = await this.send({ type: "get_tree", ...(filter ? { filter } : {}) });
339
341
  return this.getData(response);
340
342
  }
341
343
  /**
@@ -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
  */
@@ -359,13 +405,165 @@ export class RpcClient {
359
405
  return this.getData(response).messages;
360
406
  }
361
407
  /**
362
- * Get available commands (extension commands, prompt templates).
408
+ * Get available commands (extension commands, prompt templates, builtins).
363
409
  */
364
410
  async getCommands() {
365
411
  const response = await this.send({ type: "get_commands" });
366
412
  return this.getData(response).commands;
367
413
  }
368
414
  // =========================================================================
415
+ // Settings
416
+ // =========================================================================
417
+ /**
418
+ * Get settings for a scope ("global" | "project" | "effective", default "effective").
419
+ */
420
+ async getSettings(scope) {
421
+ const response = await this.send({ type: "get_settings", ...(scope ? { scope } : {}) });
422
+ return this.getData(response);
423
+ }
424
+ /**
425
+ * Bulk-update settings and persist.
426
+ */
427
+ async setSettings(values, scope = "global") {
428
+ await this.send({ type: "set_settings", scope, values });
429
+ }
430
+ /**
431
+ * Reset settings to bundled factory defaults (backup preserved).
432
+ */
433
+ async factoryResetSettings() {
434
+ await this.send({ type: "factory_reset_settings" });
435
+ }
436
+ // =========================================================================
437
+ // Auth
438
+ // =========================================================================
439
+ /**
440
+ * List providers and the login methods they support.
441
+ */
442
+ async getAuthProviders() {
443
+ const response = await this.send({ type: "get_auth_providers" });
444
+ return this.getData(response).providers;
445
+ }
446
+ /**
447
+ * Login to a provider. API-key flows emit `extension_ui_request: input` with
448
+ * `inputKind: "secret"`; OAuth flows open a browser and emit notify events.
449
+ */
450
+ async login(providerId, authType) {
451
+ const response = await this.send({ type: "login", providerId, authType });
452
+ return this.getData(response);
453
+ }
454
+ /**
455
+ * Log out of a provider, removing stored credentials.
456
+ */
457
+ async logout(providerId) {
458
+ await this.send({ type: "logout", providerId });
459
+ }
460
+ /**
461
+ * Get providers with configured auth (stored credential or environment).
462
+ */
463
+ async getAuthState() {
464
+ const response = await this.send({ type: "get_auth_state" });
465
+ return this.getData(response).providers;
466
+ }
467
+ // =========================================================================
468
+ // Scoped models
469
+ // =========================================================================
470
+ /**
471
+ * Get enabled model patterns (Ctrl+P cycling scope) and available models.
472
+ */
473
+ async getScopedModels() {
474
+ const response = await this.send({ type: "get_scoped_models" });
475
+ return this.getData(response);
476
+ }
477
+ /**
478
+ * Set enabled model patterns and/or reorder them (Ctrl+P cycling order).
479
+ */
480
+ async setScopedModels(options = {}) {
481
+ const response = await this.send({ type: "set_scoped_models", ...options });
482
+ return this.getData(response).enabled;
483
+ }
484
+ // =========================================================================
485
+ // Session import
486
+ // =========================================================================
487
+ /**
488
+ * Import and resume a session from a JSONL file (replaces current session).
489
+ */
490
+ async importJsonl(path) {
491
+ const response = await this.send({ type: "import_jsonl", path });
492
+ return this.getData(response).sessionPath;
493
+ }
494
+ // =========================================================================
495
+ // Git helper / reload / tree ops
496
+ // =========================================================================
497
+ /**
498
+ * Run the git helper (worktrees/checkpoints/commits/push). Same behavior as
499
+ * the `prompt` command: success is reported on preflight, then the normal
500
+ * agent/message event stream follows.
501
+ */
502
+ async git(command) {
503
+ await this.send({ type: "git", command });
504
+ }
505
+ /**
506
+ * Reload keybindings, extensions, skills, prompts, themes, and context files.
507
+ */
508
+ async reload() {
509
+ await this.send({ type: "reload" });
510
+ }
511
+ /**
512
+ * Set or clear a label on a session entry (undefined/empty clears).
513
+ */
514
+ async setEntryLabel(entryId, label) {
515
+ const response = await this.send({ type: "set_entry_label", entryId, label });
516
+ return this.getData(response).labelId;
517
+ }
518
+ // =========================================================================
519
+ // Trust
520
+ // =========================================================================
521
+ /**
522
+ * Get the current project trust decision and status.
523
+ */
524
+ async getTrust() {
525
+ const response = await this.send({ type: "get_trust" });
526
+ return this.getData(response);
527
+ }
528
+ /**
529
+ * Save a project trust decision. Decision takes full effect after restart.
530
+ */
531
+ async setTrust(decision) {
532
+ const response = await this.send({ type: "set_trust", decision });
533
+ return this.getData(response);
534
+ }
535
+ // =========================================================================
536
+ // Hotkeys / version / share
537
+ // =========================================================================
538
+ /**
539
+ * Get the effective keybinding map.
540
+ */
541
+ async getHotkeys() {
542
+ const response = await this.send({ type: "get_hotkeys" });
543
+ return this.getData(response).hotkeys;
544
+ }
545
+ /**
546
+ * Get available themes (built-in + custom) and the current theme setting.
547
+ */
548
+ async getAvailableThemes() {
549
+ const response = await this.send({ type: "get_available_themes" });
550
+ return this.getData(response);
551
+ }
552
+ /**
553
+ * Get the current version and parsed changelog entries.
554
+ */
555
+ async getVersionInfo() {
556
+ const response = await this.send({ type: "get_version_info" });
557
+ return this.getData(response);
558
+ }
559
+ /**
560
+ * Share the current session as a secret GitHub gist (requires `gh` CLI).
561
+ */
562
+ async shareGist(themeName) {
563
+ const response = await this.send({ type: "share_gist", public: false, themeName });
564
+ return this.getData(response);
565
+ }
566
+ // =========================================================================
369
567
  // Helpers
370
568
  // =========================================================================
371
569
  /**