@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
|
@@ -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, 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
|
*/
|
|
@@ -361,7 +405,7 @@ export declare class RpcClient {
|
|
|
361
405
|
/**
|
|
362
406
|
* Share the current session as a secret GitHub gist (requires `gh` CLI).
|
|
363
407
|
*/
|
|
364
|
-
shareGist(): Promise<{
|
|
408
|
+
shareGist(themeName?: string): Promise<{
|
|
365
409
|
url: string;
|
|
366
410
|
gistUrl: string;
|
|
367
411
|
}>;
|
|
@@ -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
|
*/
|
|
@@ -513,8 +559,8 @@ export class RpcClient {
|
|
|
513
559
|
/**
|
|
514
560
|
* Share the current session as a secret GitHub gist (requires `gh` CLI).
|
|
515
561
|
*/
|
|
516
|
-
async shareGist() {
|
|
517
|
-
const response = await this.send({ type: "share_gist", public: false });
|
|
562
|
+
async shareGist(themeName) {
|
|
563
|
+
const response = await this.send({ type: "share_gist", public: false, themeName });
|
|
518
564
|
return this.getData(response);
|
|
519
565
|
}
|
|
520
566
|
// =========================================================================
|
|
@@ -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.
|
|
@@ -18,6 +18,9 @@ import { join } from "node:path";
|
|
|
18
18
|
import { generateHandoff } from "../../core/handoff.js";
|
|
19
19
|
import { buildGitCommandPrompt } from "../../core/git-command.js";
|
|
20
20
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
21
|
+
import { SessionManager } from "../../core/session-manager.js";
|
|
22
|
+
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
23
|
+
import { refreshModelCatalogs } from "../interactive/model-catalog-refresh.js";
|
|
21
24
|
import { flushRawStdout, takeOverStdout, waitForRawStdoutBackpressure, writeRawStdout, } from "../../core/output-guard.js";
|
|
22
25
|
import { ProjectTrustStore, hasTrustRequiringProjectResources } from "../../core/trust-manager.js";
|
|
23
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -89,6 +92,8 @@ export async function runRpcMode(runtimeHost) {
|
|
|
89
92
|
};
|
|
90
93
|
// Pending extension UI requests waiting for response
|
|
91
94
|
const pendingExtensionRequests = new Map();
|
|
95
|
+
// Prompts buffered while compacting (queueWhileCompacting) — flushed after compaction_end.
|
|
96
|
+
const compactionPromptQueue = [];
|
|
92
97
|
// Shutdown request flag
|
|
93
98
|
let shutdownRequested = false;
|
|
94
99
|
let shuttingDown = false;
|
|
@@ -318,6 +323,30 @@ export async function runRpcMode(runtimeHost) {
|
|
|
318
323
|
if (event.type === "agent_settled") {
|
|
319
324
|
void checkShutdownRequested();
|
|
320
325
|
}
|
|
326
|
+
if (event.type === "compaction_end" && compactionPromptQueue.length > 0) {
|
|
327
|
+
// TUI flushCompactionQueue parity: replay buffered prompts after compaction finishes.
|
|
328
|
+
// Completed compaction replays in-order (extension commands via prompt, rest by
|
|
329
|
+
// their original streaming behavior); aborted compaction discards the buffer.
|
|
330
|
+
if (event.aborted || event.errorMessage) {
|
|
331
|
+
compactionPromptQueue.length = 0;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const queued = compactionPromptQueue.splice(0);
|
|
335
|
+
void (async () => {
|
|
336
|
+
for (const item of queued) {
|
|
337
|
+
try {
|
|
338
|
+
await session.prompt(item.message, {
|
|
339
|
+
images: item.images,
|
|
340
|
+
streamingBehavior: item.streamingBehavior,
|
|
341
|
+
source: "rpc",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// Replay failures surface through agent error handling; do not kill the stream.
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
})();
|
|
349
|
+
}
|
|
321
350
|
});
|
|
322
351
|
unsubscribeBackpressure = session.agent.subscribe(async () => {
|
|
323
352
|
await waitForRawStdoutBackpressure();
|
|
@@ -350,6 +379,21 @@ export async function runRpcMode(runtimeHost) {
|
|
|
350
379
|
// Start prompt handling immediately, but emit the authoritative response only after
|
|
351
380
|
// prompt preflight succeeds. Queued and immediately handled prompts also count as success.
|
|
352
381
|
let preflightSucceeded = false;
|
|
382
|
+
// During compaction: either buffer and auto-send after compaction_end (TUI parity),
|
|
383
|
+
// or fail fast with a clear error when the caller did not opt in.
|
|
384
|
+
if (session.isCompacting) {
|
|
385
|
+
if (!command.queueWhileCompacting) {
|
|
386
|
+
return error(id, "prompt", "Agent is compacting; set queueWhileCompacting:true to buffer this prompt and send it after compaction, or wait for compaction_end.");
|
|
387
|
+
}
|
|
388
|
+
const queued = {
|
|
389
|
+
message: command.message,
|
|
390
|
+
images: command.images,
|
|
391
|
+
streamingBehavior: command.streamingBehavior,
|
|
392
|
+
};
|
|
393
|
+
output(success(id, "prompt")); // accepted into the buffer
|
|
394
|
+
compactionPromptQueue.push(queued);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
353
397
|
void session
|
|
354
398
|
.prompt(command.message, {
|
|
355
399
|
images: command.images,
|
|
@@ -378,6 +422,10 @@ export async function runRpcMode(runtimeHost) {
|
|
|
378
422
|
return success(id, "follow_up");
|
|
379
423
|
}
|
|
380
424
|
case "abort": {
|
|
425
|
+
// TUI Esc parity: cancel in-flight branch summarization and compaction as well,
|
|
426
|
+
// not just the active agent turn (session.abort() alone covers retry + agent).
|
|
427
|
+
session.abortBranchSummary();
|
|
428
|
+
session.abortCompaction();
|
|
381
429
|
await session.abort();
|
|
382
430
|
return success(id, "abort");
|
|
383
431
|
}
|
|
@@ -468,13 +516,22 @@ export async function runRpcMode(runtimeHost) {
|
|
|
468
516
|
return success(id, "set_model", model);
|
|
469
517
|
}
|
|
470
518
|
case "cycle_model": {
|
|
471
|
-
const result = await session.cycleModel();
|
|
519
|
+
const result = await session.cycleModel(command.direction ?? "forward");
|
|
472
520
|
if (!result) {
|
|
473
521
|
return success(id, "cycle_model", null);
|
|
474
522
|
}
|
|
475
523
|
return success(id, "cycle_model", result);
|
|
476
524
|
}
|
|
477
525
|
case "get_available_models": {
|
|
526
|
+
if (command.refresh) {
|
|
527
|
+
// TUI parity: refresh model catalogs before listing (shared coordinator).
|
|
528
|
+
try {
|
|
529
|
+
await refreshModelCatalogs(session.modelRuntime, AbortSignal.timeout(15_000));
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
// Fall back to the current snapshot if the network refresh fails.
|
|
533
|
+
}
|
|
534
|
+
}
|
|
478
535
|
const models = await session.modelRuntime.getAvailable();
|
|
479
536
|
return success(id, "get_available_models", { models });
|
|
480
537
|
}
|
|
@@ -580,7 +637,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
580
637
|
return success(id, "get_session_stats", stats);
|
|
581
638
|
}
|
|
582
639
|
case "export_html": {
|
|
583
|
-
const path = await session.exportToHtml(command.outputPath);
|
|
640
|
+
const path = await session.exportToHtml(command.outputPath, { themeName: command.themeName });
|
|
584
641
|
return success(id, "export_html", { path });
|
|
585
642
|
}
|
|
586
643
|
case "switch_session": {
|
|
@@ -644,6 +701,145 @@ export async function runRpcMode(runtimeHost) {
|
|
|
644
701
|
return success(id, "set_session_name");
|
|
645
702
|
}
|
|
646
703
|
// =================================================================
|
|
704
|
+
// Tree navigation
|
|
705
|
+
// =================================================================
|
|
706
|
+
case "navigate_tree": {
|
|
707
|
+
if (session.isStreaming) {
|
|
708
|
+
return error(id, "navigate_tree", "Wait for the current response to finish before navigating the session tree.");
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const result = await session.navigateTree(command.targetId, {
|
|
712
|
+
summarize: command.summarize,
|
|
713
|
+
customInstructions: command.customInstructions,
|
|
714
|
+
replaceInstructions: command.replaceInstructions,
|
|
715
|
+
label: command.label,
|
|
716
|
+
});
|
|
717
|
+
return success(id, "navigate_tree", {
|
|
718
|
+
editorText: result.editorText,
|
|
719
|
+
cancelled: result.cancelled,
|
|
720
|
+
aborted: result.aborted,
|
|
721
|
+
summaryEntry: result.summaryEntry ?? null,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
catch (navigationError) {
|
|
725
|
+
return error(id, "navigate_tree", navigationError instanceof Error ? navigationError.message : String(navigationError));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// =================================================================
|
|
729
|
+
// Session discovery / management
|
|
730
|
+
// =================================================================
|
|
731
|
+
case "list_sessions": {
|
|
732
|
+
try {
|
|
733
|
+
const sessionManager = session.sessionManager;
|
|
734
|
+
const isAll = command.scope === "all";
|
|
735
|
+
const source = isAll
|
|
736
|
+
? sessionManager.usesDefaultSessionDir()
|
|
737
|
+
? await SessionManager.listAll()
|
|
738
|
+
: await SessionManager.listAll(sessionManager.getSessionDir())
|
|
739
|
+
: await SessionManager.list(sessionManager.getCwd(), sessionManager.getSessionDir());
|
|
740
|
+
const sessions = source.map((info) => ({
|
|
741
|
+
path: info.path,
|
|
742
|
+
id: info.id,
|
|
743
|
+
cwd: info.cwd,
|
|
744
|
+
name: info.name,
|
|
745
|
+
parentSessionPath: info.parentSessionPath,
|
|
746
|
+
created: Number.isNaN(info.created.getTime()) ? new Date(0).toISOString() : info.created.toISOString(),
|
|
747
|
+
modified: Number.isNaN(info.modified.getTime()) ? new Date(0).toISOString() : info.modified.toISOString(),
|
|
748
|
+
messageCount: info.messageCount,
|
|
749
|
+
firstMessage: info.firstMessage,
|
|
750
|
+
}));
|
|
751
|
+
return success(id, "list_sessions", { sessions });
|
|
752
|
+
}
|
|
753
|
+
catch (listError) {
|
|
754
|
+
return error(id, "list_sessions", listError instanceof Error ? listError.message : String(listError));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
case "rename_session": {
|
|
758
|
+
const name = command.name.trim();
|
|
759
|
+
if (!name) {
|
|
760
|
+
return error(id, "rename_session", "Session name cannot be empty");
|
|
761
|
+
}
|
|
762
|
+
if (!existsSync(command.path)) {
|
|
763
|
+
return error(id, "rename_session", `Session file not found: ${command.path}`);
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
const target = SessionManager.open(command.path);
|
|
767
|
+
target.appendSessionInfo(name);
|
|
768
|
+
return success(id, "rename_session", { path: command.path, name });
|
|
769
|
+
}
|
|
770
|
+
catch (renameError) {
|
|
771
|
+
return error(id, "rename_session", renameError instanceof Error ? renameError.message : String(renameError));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
case "delete_session": {
|
|
775
|
+
if (command.path === session.sessionFile) {
|
|
776
|
+
return error(id, "delete_session", "Cannot delete the currently active session.");
|
|
777
|
+
}
|
|
778
|
+
if (!existsSync(command.path)) {
|
|
779
|
+
return error(id, "delete_session", `Session file not found: ${command.path}`);
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
// trash-CLI-first with unlink fallback (identical to the TUI session selector).
|
|
783
|
+
const trashArgs = command.path.startsWith("-") ? ["--", command.path] : [command.path];
|
|
784
|
+
const trashResult = spawnSync("trash", trashArgs, { encoding: "utf-8" });
|
|
785
|
+
if (trashResult.status === 0 || !existsSync(command.path)) {
|
|
786
|
+
return success(id, "delete_session", { path: command.path, method: "trash" });
|
|
787
|
+
}
|
|
788
|
+
unlinkSync(command.path);
|
|
789
|
+
return success(id, "delete_session", { path: command.path, method: "unlink" });
|
|
790
|
+
}
|
|
791
|
+
catch (deleteError) {
|
|
792
|
+
return error(id, "delete_session", deleteError instanceof Error ? deleteError.message : String(deleteError));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// =================================================================
|
|
796
|
+
// Session-only scoped models (ephemeral, not persisted)
|
|
797
|
+
// =================================================================
|
|
798
|
+
case "set_session_models": {
|
|
799
|
+
try {
|
|
800
|
+
if (command.reorder !== undefined && command.reorder.length > 0) {
|
|
801
|
+
// Reorder-only path: rebuild scope from the reordered pattern list.
|
|
802
|
+
const scoped = await resolveModelScope(command.reorder, session.modelRuntime);
|
|
803
|
+
session.setScopedModels(scoped.map((sm) => ({ model: sm.model, thinkingLevel: sm.thinkingLevel })));
|
|
804
|
+
return success(id, "set_session_models", {
|
|
805
|
+
enabled: scoped.map((sm) => `${sm.model.provider}/${sm.model.id}`),
|
|
806
|
+
models: scoped.map((sm) => sm.model),
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (command.enabled === undefined) {
|
|
810
|
+
return error(id, "set_session_models", "Provide either enabled or reorder.");
|
|
811
|
+
}
|
|
812
|
+
if (command.enabled.length === 0) {
|
|
813
|
+
session.setScopedModels([]);
|
|
814
|
+
return success(id, "set_session_models", { enabled: [], models: [] });
|
|
815
|
+
}
|
|
816
|
+
const patterns = command.enabled;
|
|
817
|
+
const allModels = await session.modelRuntime.getAvailable();
|
|
818
|
+
const allModelIds = new Set(allModels.map((model) => `${model.provider}/${model.id}`));
|
|
819
|
+
const allEnabled = patterns.every((pattern) => allModelIds.has(pattern));
|
|
820
|
+
if (allEnabled) {
|
|
821
|
+
// Every enabled id resolves to a concrete model: build scope directly.
|
|
822
|
+
const enabledSet = new Set(patterns);
|
|
823
|
+
const scopedModels = allModels.filter((model) => enabledSet.has(`${model.provider}/${model.id}`));
|
|
824
|
+
session.setScopedModels(scopedModels.map((model) => ({ model })));
|
|
825
|
+
return success(id, "set_session_models", {
|
|
826
|
+
enabled: scopedModels.map((model) => `${model.provider}/${model.id}`),
|
|
827
|
+
models: scopedModels,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
// Patterns contain a scope/wildcard (e.g. "anthropic/*"): resolve against the registry.
|
|
831
|
+
const scoped = await resolveModelScope(patterns, session.modelRuntime);
|
|
832
|
+
session.setScopedModels(scoped.map((sm) => ({ model: sm.model, thinkingLevel: sm.thinkingLevel })));
|
|
833
|
+
return success(id, "set_session_models", {
|
|
834
|
+
enabled: scoped.map((sm) => `${sm.model.provider}/${sm.model.id}`),
|
|
835
|
+
models: scoped.map((sm) => sm.model),
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
catch (scopeError) {
|
|
839
|
+
return error(id, "set_session_models", scopeError instanceof Error ? scopeError.message : String(scopeError));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// =================================================================
|
|
647
843
|
// Messages
|
|
648
844
|
// =================================================================
|
|
649
845
|
case "get_messages": {
|
|
@@ -1079,7 +1275,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
1079
1275
|
return error(id, "share_gist", "GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
1080
1276
|
}
|
|
1081
1277
|
const tmpFile = join(tmpdir(), `selesai-session-${crypto.randomUUID()}.html`);
|
|
1082
|
-
await session.exportToHtml(tmpFile);
|
|
1278
|
+
await session.exportToHtml(tmpFile, { themeName: command.themeName });
|
|
1083
1279
|
const result = spawnSync("gh", ["gist", "create", "--public=false", tmpFile], { encoding: "utf-8" });
|
|
1084
1280
|
try {
|
|
1085
1281
|
unlinkSync(tmpFile);
|
|
@@ -11,7 +11,7 @@ import type { KeybindingsConfig } from "../../core/keybindings.ts";
|
|
|
11
11
|
import type { Settings, SettingsScope } from "../../core/settings-manager.ts";
|
|
12
12
|
import type { BashResult } from "../../core/bash-executor.ts";
|
|
13
13
|
import type { CompactionResult } from "../../core/compaction/index.ts";
|
|
14
|
-
import type { SessionEntry, SessionTreeNode } from "../../core/session-manager.ts";
|
|
14
|
+
import type { BranchSummaryEntry, SessionEntry, SessionTreeNode } from "../../core/session-manager.ts";
|
|
15
15
|
import type { SourceInfo } from "../../core/source-info.ts";
|
|
16
16
|
import type { ChangelogEntry } from "../../utils/changelog.ts";
|
|
17
17
|
export type RpcCommand = {
|
|
@@ -20,6 +20,8 @@ export type RpcCommand = {
|
|
|
20
20
|
message: string;
|
|
21
21
|
images?: ImageContent[];
|
|
22
22
|
streamingBehavior?: "steer" | "followUp";
|
|
23
|
+
/** Buffer the prompt during compaction and auto-send it after compaction_end (mirrors TUI compaction queue). */
|
|
24
|
+
queueWhileCompacting?: boolean;
|
|
23
25
|
} | {
|
|
24
26
|
id?: string;
|
|
25
27
|
type: "steer";
|
|
@@ -55,9 +57,11 @@ export type RpcCommand = {
|
|
|
55
57
|
} | {
|
|
56
58
|
id?: string;
|
|
57
59
|
type: "cycle_model";
|
|
60
|
+
direction?: "forward" | "backward";
|
|
58
61
|
} | {
|
|
59
62
|
id?: string;
|
|
60
63
|
type: "get_available_models";
|
|
64
|
+
refresh?: boolean;
|
|
61
65
|
} | {
|
|
62
66
|
id?: string;
|
|
63
67
|
type: "set_thinking_level";
|
|
@@ -119,6 +123,7 @@ export type RpcCommand = {
|
|
|
119
123
|
id?: string;
|
|
120
124
|
type: "export_html";
|
|
121
125
|
outputPath?: string;
|
|
126
|
+
themeName?: string;
|
|
122
127
|
} | {
|
|
123
128
|
id?: string;
|
|
124
129
|
type: "switch_session";
|
|
@@ -187,6 +192,32 @@ export type RpcCommand = {
|
|
|
187
192
|
id?: string;
|
|
188
193
|
type: "import_jsonl";
|
|
189
194
|
path: string;
|
|
195
|
+
} | {
|
|
196
|
+
id?: string;
|
|
197
|
+
type: "navigate_tree";
|
|
198
|
+
targetId: string;
|
|
199
|
+
summarize?: boolean;
|
|
200
|
+
customInstructions?: string;
|
|
201
|
+
replaceInstructions?: boolean;
|
|
202
|
+
label?: string;
|
|
203
|
+
} | {
|
|
204
|
+
id?: string;
|
|
205
|
+
type: "list_sessions";
|
|
206
|
+
scope?: "current" | "all";
|
|
207
|
+
} | {
|
|
208
|
+
id?: string;
|
|
209
|
+
type: "rename_session";
|
|
210
|
+
path: string;
|
|
211
|
+
name: string;
|
|
212
|
+
} | {
|
|
213
|
+
id?: string;
|
|
214
|
+
type: "delete_session";
|
|
215
|
+
path: string;
|
|
216
|
+
} | {
|
|
217
|
+
id?: string;
|
|
218
|
+
type: "set_session_models";
|
|
219
|
+
enabled?: string[];
|
|
220
|
+
reorder?: string[];
|
|
190
221
|
} | {
|
|
191
222
|
id?: string;
|
|
192
223
|
type: "git";
|
|
@@ -219,6 +250,7 @@ export type RpcCommand = {
|
|
|
219
250
|
id?: string;
|
|
220
251
|
type: "share_gist";
|
|
221
252
|
public?: boolean;
|
|
253
|
+
themeName?: string;
|
|
222
254
|
} | {
|
|
223
255
|
id?: string;
|
|
224
256
|
type: "get_messages";
|
|
@@ -239,6 +271,18 @@ export interface RpcSlashCommand {
|
|
|
239
271
|
/** True for interactive-TUI-only builtins that cannot be invoked via prompt */
|
|
240
272
|
interactiveOnly?: boolean;
|
|
241
273
|
}
|
|
274
|
+
/** Session listing entry (SessionInfo with transcript text stripped). */
|
|
275
|
+
export interface RpcSessionInfo {
|
|
276
|
+
path: string;
|
|
277
|
+
id: string;
|
|
278
|
+
cwd: string;
|
|
279
|
+
name?: string;
|
|
280
|
+
parentSessionPath?: string;
|
|
281
|
+
created: string;
|
|
282
|
+
modified: string;
|
|
283
|
+
messageCount: number;
|
|
284
|
+
firstMessage: string;
|
|
285
|
+
}
|
|
242
286
|
export interface RpcSessionState {
|
|
243
287
|
model?: Model<any>;
|
|
244
288
|
thinkingLevel: ThinkingLevel;
|
|
@@ -601,6 +645,52 @@ export type RpcResponse = {
|
|
|
601
645
|
data: {
|
|
602
646
|
sessionPath: string;
|
|
603
647
|
};
|
|
648
|
+
} | {
|
|
649
|
+
id?: string;
|
|
650
|
+
type: "response";
|
|
651
|
+
command: "navigate_tree";
|
|
652
|
+
success: true;
|
|
653
|
+
data: {
|
|
654
|
+
editorText?: string;
|
|
655
|
+
cancelled: boolean;
|
|
656
|
+
aborted?: boolean;
|
|
657
|
+
summaryEntry?: BranchSummaryEntry | null;
|
|
658
|
+
};
|
|
659
|
+
} | {
|
|
660
|
+
id?: string;
|
|
661
|
+
type: "response";
|
|
662
|
+
command: "list_sessions";
|
|
663
|
+
success: true;
|
|
664
|
+
data: {
|
|
665
|
+
sessions: RpcSessionInfo[];
|
|
666
|
+
};
|
|
667
|
+
} | {
|
|
668
|
+
id?: string;
|
|
669
|
+
type: "response";
|
|
670
|
+
command: "rename_session";
|
|
671
|
+
success: true;
|
|
672
|
+
data: {
|
|
673
|
+
path: string;
|
|
674
|
+
name: string;
|
|
675
|
+
};
|
|
676
|
+
} | {
|
|
677
|
+
id?: string;
|
|
678
|
+
type: "response";
|
|
679
|
+
command: "delete_session";
|
|
680
|
+
success: true;
|
|
681
|
+
data: {
|
|
682
|
+
path: string;
|
|
683
|
+
method: "trash" | "unlink";
|
|
684
|
+
};
|
|
685
|
+
} | {
|
|
686
|
+
id?: string;
|
|
687
|
+
type: "response";
|
|
688
|
+
command: "set_session_models";
|
|
689
|
+
success: true;
|
|
690
|
+
data: {
|
|
691
|
+
enabled: string[];
|
|
692
|
+
models: Model<any>[];
|
|
693
|
+
};
|
|
604
694
|
} | {
|
|
605
695
|
id?: string;
|
|
606
696
|
type: "response";
|
|
@@ -29,7 +29,7 @@ Your goal:
|
|
|
29
29
|
|
|
30
30
|
### 1. Explore the context
|
|
31
31
|
|
|
32
|
-
Call the subagent tool with `{ agent: "
|
|
32
|
+
Call the subagent tool with `{ agent: "scout", task: "..." }` (do not pass a model) when possible.
|
|
33
33
|
|
|
34
34
|
If the context is partial:
|
|
35
35
|
- Infer surrounding architecture cautiously
|
|
@@ -52,7 +52,7 @@ Choose the lowest-complexity solution that works.
|
|
|
52
52
|
|
|
53
53
|
## Reuse Before Build
|
|
54
54
|
|
|
55
|
-
Before creating anything new (use
|
|
55
|
+
Before creating anything new (use scout agent):
|
|
56
56
|
|
|
57
57
|
- Search for existing implementations
|
|
58
58
|
- Search for existing utilities
|