@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
|
@@ -12,19 +12,23 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as crypto from "node:crypto";
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
|
-
import { chmodSync, copyFileSync, existsSync, unlinkSync } from "node:fs";
|
|
15
|
+
import { chmodSync, copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
|
-
import { join } from "node:path";
|
|
17
|
+
import { basename, dirname, join, relative } 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";
|
|
24
27
|
import { createSyntheticSourceInfo } from "../../core/source-info.js";
|
|
25
28
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
26
29
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog.js";
|
|
27
|
-
import {
|
|
30
|
+
import { parseFrontmatter } from "../../utils/frontmatter.js";
|
|
31
|
+
import { VERSION, CONFIG_DIR_NAME, getAgentDir, getBundledDefaultsDir, getShareViewerUrl } from "../../config.js";
|
|
28
32
|
import { openBrowser } from "../../utils/open-browser.js";
|
|
29
33
|
import { theme } from "../interactive/theme/theme.js";
|
|
30
34
|
import { getAvailableThemesWithPaths, setRegisteredThemes } from "../interactive/theme/theme.js";
|
|
@@ -89,6 +93,8 @@ export async function runRpcMode(runtimeHost) {
|
|
|
89
93
|
};
|
|
90
94
|
// Pending extension UI requests waiting for response
|
|
91
95
|
const pendingExtensionRequests = new Map();
|
|
96
|
+
// Prompts buffered while compacting (queueWhileCompacting) — flushed after compaction_end.
|
|
97
|
+
const compactionPromptQueue = [];
|
|
92
98
|
// Shutdown request flag
|
|
93
99
|
let shutdownRequested = false;
|
|
94
100
|
let shuttingDown = false;
|
|
@@ -159,14 +165,34 @@ export async function runRpcMode(runtimeHost) {
|
|
|
159
165
|
statusText: text,
|
|
160
166
|
});
|
|
161
167
|
},
|
|
162
|
-
setWorkingMessage(
|
|
163
|
-
//
|
|
168
|
+
setWorkingMessage(message) {
|
|
169
|
+
// Fire and forget - host renders the working loader row
|
|
170
|
+
output({
|
|
171
|
+
type: "extension_ui_request",
|
|
172
|
+
id: crypto.randomUUID(),
|
|
173
|
+
method: "working",
|
|
174
|
+
...(message !== undefined ? { message } : {}),
|
|
175
|
+
});
|
|
164
176
|
},
|
|
165
|
-
setWorkingVisible(
|
|
166
|
-
//
|
|
177
|
+
setWorkingVisible(visible) {
|
|
178
|
+
// Fire and forget - host shows/hides the working loader row
|
|
179
|
+
output({
|
|
180
|
+
type: "extension_ui_request",
|
|
181
|
+
id: crypto.randomUUID(),
|
|
182
|
+
method: "working",
|
|
183
|
+
visible,
|
|
184
|
+
});
|
|
167
185
|
},
|
|
168
|
-
setWorkingIndicator(
|
|
169
|
-
//
|
|
186
|
+
setWorkingIndicator(options) {
|
|
187
|
+
// Fire and forget - host customizes the working indicator. Custom frames only
|
|
188
|
+
// affect the normal streaming loader; compaction/retry keep built-in styling.
|
|
189
|
+
output({
|
|
190
|
+
type: "extension_ui_request",
|
|
191
|
+
id: crypto.randomUUID(),
|
|
192
|
+
method: "working",
|
|
193
|
+
...(options?.frames !== undefined ? { frames: options.frames } : {}),
|
|
194
|
+
...(options?.intervalMs !== undefined ? { intervalMs: options.intervalMs } : {}),
|
|
195
|
+
});
|
|
170
196
|
},
|
|
171
197
|
setHiddenThinkingLabel(_label) {
|
|
172
198
|
// Hidden thinking label not supported in RPC mode - requires TUI message rendering access
|
|
@@ -318,6 +344,30 @@ export async function runRpcMode(runtimeHost) {
|
|
|
318
344
|
if (event.type === "agent_settled") {
|
|
319
345
|
void checkShutdownRequested();
|
|
320
346
|
}
|
|
347
|
+
if (event.type === "compaction_end" && compactionPromptQueue.length > 0) {
|
|
348
|
+
// TUI flushCompactionQueue parity: replay buffered prompts after compaction finishes.
|
|
349
|
+
// Completed compaction replays in-order (extension commands via prompt, rest by
|
|
350
|
+
// their original streaming behavior); aborted compaction discards the buffer.
|
|
351
|
+
if (event.aborted || event.errorMessage) {
|
|
352
|
+
compactionPromptQueue.length = 0;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const queued = compactionPromptQueue.splice(0);
|
|
356
|
+
void (async () => {
|
|
357
|
+
for (const item of queued) {
|
|
358
|
+
try {
|
|
359
|
+
await session.prompt(item.message, {
|
|
360
|
+
images: item.images,
|
|
361
|
+
streamingBehavior: item.streamingBehavior,
|
|
362
|
+
source: "rpc",
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Replay failures surface through agent error handling; do not kill the stream.
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
})();
|
|
370
|
+
}
|
|
321
371
|
});
|
|
322
372
|
unsubscribeBackpressure = session.agent.subscribe(async () => {
|
|
323
373
|
await waitForRawStdoutBackpressure();
|
|
@@ -350,6 +400,21 @@ export async function runRpcMode(runtimeHost) {
|
|
|
350
400
|
// Start prompt handling immediately, but emit the authoritative response only after
|
|
351
401
|
// prompt preflight succeeds. Queued and immediately handled prompts also count as success.
|
|
352
402
|
let preflightSucceeded = false;
|
|
403
|
+
// During compaction: either buffer and auto-send after compaction_end (TUI parity),
|
|
404
|
+
// or fail fast with a clear error when the caller did not opt in.
|
|
405
|
+
if (session.isCompacting) {
|
|
406
|
+
if (!command.queueWhileCompacting) {
|
|
407
|
+
return error(id, "prompt", "Agent is compacting; set queueWhileCompacting:true to buffer this prompt and send it after compaction, or wait for compaction_end.");
|
|
408
|
+
}
|
|
409
|
+
const queued = {
|
|
410
|
+
message: command.message,
|
|
411
|
+
images: command.images,
|
|
412
|
+
streamingBehavior: command.streamingBehavior,
|
|
413
|
+
};
|
|
414
|
+
output(success(id, "prompt")); // accepted into the buffer
|
|
415
|
+
compactionPromptQueue.push(queued);
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
353
418
|
void session
|
|
354
419
|
.prompt(command.message, {
|
|
355
420
|
images: command.images,
|
|
@@ -378,6 +443,10 @@ export async function runRpcMode(runtimeHost) {
|
|
|
378
443
|
return success(id, "follow_up");
|
|
379
444
|
}
|
|
380
445
|
case "abort": {
|
|
446
|
+
// TUI Esc parity: cancel in-flight branch summarization and compaction as well,
|
|
447
|
+
// not just the active agent turn (session.abort() alone covers retry + agent).
|
|
448
|
+
session.abortBranchSummary();
|
|
449
|
+
session.abortCompaction();
|
|
381
450
|
await session.abort();
|
|
382
451
|
return success(id, "abort");
|
|
383
452
|
}
|
|
@@ -468,13 +537,22 @@ export async function runRpcMode(runtimeHost) {
|
|
|
468
537
|
return success(id, "set_model", model);
|
|
469
538
|
}
|
|
470
539
|
case "cycle_model": {
|
|
471
|
-
const result = await session.cycleModel();
|
|
540
|
+
const result = await session.cycleModel(command.direction ?? "forward");
|
|
472
541
|
if (!result) {
|
|
473
542
|
return success(id, "cycle_model", null);
|
|
474
543
|
}
|
|
475
544
|
return success(id, "cycle_model", result);
|
|
476
545
|
}
|
|
477
546
|
case "get_available_models": {
|
|
547
|
+
if (command.refresh) {
|
|
548
|
+
// TUI parity: refresh model catalogs before listing (shared coordinator).
|
|
549
|
+
try {
|
|
550
|
+
await refreshModelCatalogs(session.modelRuntime, AbortSignal.timeout(15_000));
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
// Fall back to the current snapshot if the network refresh fails.
|
|
554
|
+
}
|
|
555
|
+
}
|
|
478
556
|
const models = await session.modelRuntime.getAvailable();
|
|
479
557
|
return success(id, "get_available_models", { models });
|
|
480
558
|
}
|
|
@@ -580,7 +658,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
580
658
|
return success(id, "get_session_stats", stats);
|
|
581
659
|
}
|
|
582
660
|
case "export_html": {
|
|
583
|
-
const path = await session.exportToHtml(command.outputPath);
|
|
661
|
+
const path = await session.exportToHtml(command.outputPath, { themeName: command.themeName });
|
|
584
662
|
return success(id, "export_html", { path });
|
|
585
663
|
}
|
|
586
664
|
case "switch_session": {
|
|
@@ -644,6 +722,145 @@ export async function runRpcMode(runtimeHost) {
|
|
|
644
722
|
return success(id, "set_session_name");
|
|
645
723
|
}
|
|
646
724
|
// =================================================================
|
|
725
|
+
// Tree navigation
|
|
726
|
+
// =================================================================
|
|
727
|
+
case "navigate_tree": {
|
|
728
|
+
if (session.isStreaming) {
|
|
729
|
+
return error(id, "navigate_tree", "Wait for the current response to finish before navigating the session tree.");
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
const result = await session.navigateTree(command.targetId, {
|
|
733
|
+
summarize: command.summarize,
|
|
734
|
+
customInstructions: command.customInstructions,
|
|
735
|
+
replaceInstructions: command.replaceInstructions,
|
|
736
|
+
label: command.label,
|
|
737
|
+
});
|
|
738
|
+
return success(id, "navigate_tree", {
|
|
739
|
+
editorText: result.editorText,
|
|
740
|
+
cancelled: result.cancelled,
|
|
741
|
+
aborted: result.aborted,
|
|
742
|
+
summaryEntry: result.summaryEntry ?? null,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
catch (navigationError) {
|
|
746
|
+
return error(id, "navigate_tree", navigationError instanceof Error ? navigationError.message : String(navigationError));
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// =================================================================
|
|
750
|
+
// Session discovery / management
|
|
751
|
+
// =================================================================
|
|
752
|
+
case "list_sessions": {
|
|
753
|
+
try {
|
|
754
|
+
const sessionManager = session.sessionManager;
|
|
755
|
+
const isAll = command.scope === "all";
|
|
756
|
+
const source = isAll
|
|
757
|
+
? sessionManager.usesDefaultSessionDir()
|
|
758
|
+
? await SessionManager.listAll()
|
|
759
|
+
: await SessionManager.listAll(sessionManager.getSessionDir())
|
|
760
|
+
: await SessionManager.list(sessionManager.getCwd(), sessionManager.getSessionDir());
|
|
761
|
+
const sessions = source.map((info) => ({
|
|
762
|
+
path: info.path,
|
|
763
|
+
id: info.id,
|
|
764
|
+
cwd: info.cwd,
|
|
765
|
+
name: info.name,
|
|
766
|
+
parentSessionPath: info.parentSessionPath,
|
|
767
|
+
created: Number.isNaN(info.created.getTime()) ? new Date(0).toISOString() : info.created.toISOString(),
|
|
768
|
+
modified: Number.isNaN(info.modified.getTime()) ? new Date(0).toISOString() : info.modified.toISOString(),
|
|
769
|
+
messageCount: info.messageCount,
|
|
770
|
+
firstMessage: info.firstMessage,
|
|
771
|
+
}));
|
|
772
|
+
return success(id, "list_sessions", { sessions });
|
|
773
|
+
}
|
|
774
|
+
catch (listError) {
|
|
775
|
+
return error(id, "list_sessions", listError instanceof Error ? listError.message : String(listError));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
case "rename_session": {
|
|
779
|
+
const name = command.name.trim();
|
|
780
|
+
if (!name) {
|
|
781
|
+
return error(id, "rename_session", "Session name cannot be empty");
|
|
782
|
+
}
|
|
783
|
+
if (!existsSync(command.path)) {
|
|
784
|
+
return error(id, "rename_session", `Session file not found: ${command.path}`);
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const target = SessionManager.open(command.path);
|
|
788
|
+
target.appendSessionInfo(name);
|
|
789
|
+
return success(id, "rename_session", { path: command.path, name });
|
|
790
|
+
}
|
|
791
|
+
catch (renameError) {
|
|
792
|
+
return error(id, "rename_session", renameError instanceof Error ? renameError.message : String(renameError));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
case "delete_session": {
|
|
796
|
+
if (command.path === session.sessionFile) {
|
|
797
|
+
return error(id, "delete_session", "Cannot delete the currently active session.");
|
|
798
|
+
}
|
|
799
|
+
if (!existsSync(command.path)) {
|
|
800
|
+
return error(id, "delete_session", `Session file not found: ${command.path}`);
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
// trash-CLI-first with unlink fallback (identical to the TUI session selector).
|
|
804
|
+
const trashArgs = command.path.startsWith("-") ? ["--", command.path] : [command.path];
|
|
805
|
+
const trashResult = spawnSync("trash", trashArgs, { encoding: "utf-8" });
|
|
806
|
+
if (trashResult.status === 0 || !existsSync(command.path)) {
|
|
807
|
+
return success(id, "delete_session", { path: command.path, method: "trash" });
|
|
808
|
+
}
|
|
809
|
+
unlinkSync(command.path);
|
|
810
|
+
return success(id, "delete_session", { path: command.path, method: "unlink" });
|
|
811
|
+
}
|
|
812
|
+
catch (deleteError) {
|
|
813
|
+
return error(id, "delete_session", deleteError instanceof Error ? deleteError.message : String(deleteError));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// =================================================================
|
|
817
|
+
// Session-only scoped models (ephemeral, not persisted)
|
|
818
|
+
// =================================================================
|
|
819
|
+
case "set_session_models": {
|
|
820
|
+
try {
|
|
821
|
+
if (command.reorder !== undefined && command.reorder.length > 0) {
|
|
822
|
+
// Reorder-only path: rebuild scope from the reordered pattern list.
|
|
823
|
+
const scoped = await resolveModelScope(command.reorder, session.modelRuntime);
|
|
824
|
+
session.setScopedModels(scoped.map((sm) => ({ model: sm.model, thinkingLevel: sm.thinkingLevel })));
|
|
825
|
+
return success(id, "set_session_models", {
|
|
826
|
+
enabled: scoped.map((sm) => `${sm.model.provider}/${sm.model.id}`),
|
|
827
|
+
models: scoped.map((sm) => sm.model),
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
if (command.enabled === undefined) {
|
|
831
|
+
return error(id, "set_session_models", "Provide either enabled or reorder.");
|
|
832
|
+
}
|
|
833
|
+
if (command.enabled.length === 0) {
|
|
834
|
+
session.setScopedModels([]);
|
|
835
|
+
return success(id, "set_session_models", { enabled: [], models: [] });
|
|
836
|
+
}
|
|
837
|
+
const patterns = command.enabled;
|
|
838
|
+
const allModels = await session.modelRuntime.getAvailable();
|
|
839
|
+
const allModelIds = new Set(allModels.map((model) => `${model.provider}/${model.id}`));
|
|
840
|
+
const allEnabled = patterns.every((pattern) => allModelIds.has(pattern));
|
|
841
|
+
if (allEnabled) {
|
|
842
|
+
// Every enabled id resolves to a concrete model: build scope directly.
|
|
843
|
+
const enabledSet = new Set(patterns);
|
|
844
|
+
const scopedModels = allModels.filter((model) => enabledSet.has(`${model.provider}/${model.id}`));
|
|
845
|
+
session.setScopedModels(scopedModels.map((model) => ({ model })));
|
|
846
|
+
return success(id, "set_session_models", {
|
|
847
|
+
enabled: scopedModels.map((model) => `${model.provider}/${model.id}`),
|
|
848
|
+
models: scopedModels,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
// Patterns contain a scope/wildcard (e.g. "anthropic/*"): resolve against the registry.
|
|
852
|
+
const scoped = await resolveModelScope(patterns, session.modelRuntime);
|
|
853
|
+
session.setScopedModels(scoped.map((sm) => ({ model: sm.model, thinkingLevel: sm.thinkingLevel })));
|
|
854
|
+
return success(id, "set_session_models", {
|
|
855
|
+
enabled: scoped.map((sm) => `${sm.model.provider}/${sm.model.id}`),
|
|
856
|
+
models: scoped.map((sm) => sm.model),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
catch (scopeError) {
|
|
860
|
+
return error(id, "set_session_models", scopeError instanceof Error ? scopeError.message : String(scopeError));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
// =================================================================
|
|
647
864
|
// Messages
|
|
648
865
|
// =================================================================
|
|
649
866
|
case "get_messages": {
|
|
@@ -684,6 +901,57 @@ export async function runRpcMode(runtimeHost) {
|
|
|
684
901
|
return success(id, "get_commands", { commands });
|
|
685
902
|
}
|
|
686
903
|
// =================================================================
|
|
904
|
+
// Skills (resolved catalog + raw toggle patterns)
|
|
905
|
+
// =================================================================
|
|
906
|
+
case "get_skills": {
|
|
907
|
+
const resolvedSkills = session.resourceLoader.getResolvedSkills();
|
|
908
|
+
const agentDir = getAgentDir();
|
|
909
|
+
const cwd = session.sessionManager.getCwd();
|
|
910
|
+
const skills = [];
|
|
911
|
+
// Mirror the TUI skill-toggle resolution (buildSkillToggleItems):
|
|
912
|
+
// name/pattern from the SKILL.md parent dir, scope from resource metadata,
|
|
913
|
+
// enabled per isEnabledByOverrides via the resource loader.
|
|
914
|
+
for (const resource of resolvedSkills) {
|
|
915
|
+
const pathStr = resource.path;
|
|
916
|
+
const isSkillFile = basename(pathStr) === "SKILL.md";
|
|
917
|
+
const name = isSkillFile ? basename(dirname(pathStr)) : basename(pathStr);
|
|
918
|
+
const scope = resource.metadata.scope === "project" ? "project" : "user";
|
|
919
|
+
const baseDir = resource.metadata.baseDir ??
|
|
920
|
+
(scope === "project" ? join(cwd, CONFIG_DIR_NAME) : agentDir);
|
|
921
|
+
const pattern = relative(baseDir, pathStr) || name;
|
|
922
|
+
let frontmatterName;
|
|
923
|
+
let description;
|
|
924
|
+
try {
|
|
925
|
+
const raw = readFileSync(pathStr, "utf-8");
|
|
926
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
927
|
+
if (typeof frontmatter.name === "string" && frontmatter.name.trim()) {
|
|
928
|
+
frontmatterName = frontmatter.name.trim();
|
|
929
|
+
}
|
|
930
|
+
if (typeof frontmatter.description === "string" && frontmatter.description.trim()) {
|
|
931
|
+
description = frontmatter.description.trim();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch {
|
|
935
|
+
// Unreadable skill file: keep path-derived name, no description.
|
|
936
|
+
}
|
|
937
|
+
skills.push({
|
|
938
|
+
name: frontmatterName ?? name,
|
|
939
|
+
description,
|
|
940
|
+
scope: scope === "project" ? "project" : "global",
|
|
941
|
+
pattern,
|
|
942
|
+
enabled: resource.enabled,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
946
|
+
// Raw global+project merged settings.skills patterns so the caller can
|
|
947
|
+
// round-trip the exact write shape via set_settings + reload.
|
|
948
|
+
const patterns = [
|
|
949
|
+
...(session.settingsManager.getGlobalSettings().skills ?? []),
|
|
950
|
+
...(session.settingsManager.getProjectSettings().skills ?? []),
|
|
951
|
+
];
|
|
952
|
+
return success(id, "get_skills", { skills, patterns });
|
|
953
|
+
}
|
|
954
|
+
// =================================================================
|
|
687
955
|
// Settings
|
|
688
956
|
// =================================================================
|
|
689
957
|
case "get_settings": {
|
|
@@ -1079,7 +1347,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
1079
1347
|
return error(id, "share_gist", "GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
1080
1348
|
}
|
|
1081
1349
|
const tmpFile = join(tmpdir(), `selesai-session-${crypto.randomUUID()}.html`);
|
|
1082
|
-
await session.exportToHtml(tmpFile);
|
|
1350
|
+
await session.exportToHtml(tmpFile, { themeName: command.themeName });
|
|
1083
1351
|
const result = spawnSync("gh", ["gist", "create", "--public=false", tmpFile], { encoding: "utf-8" });
|
|
1084
1352
|
try {
|
|
1085
1353
|
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,12 +250,16 @@ 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";
|
|
225
257
|
} | {
|
|
226
258
|
id?: string;
|
|
227
259
|
type: "get_commands";
|
|
260
|
+
} | {
|
|
261
|
+
id?: string;
|
|
262
|
+
type: "get_skills";
|
|
228
263
|
};
|
|
229
264
|
/** A command available for invocation via prompt */
|
|
230
265
|
export interface RpcSlashCommand {
|
|
@@ -239,6 +274,31 @@ export interface RpcSlashCommand {
|
|
|
239
274
|
/** True for interactive-TUI-only builtins that cannot be invoked via prompt */
|
|
240
275
|
interactiveOnly?: boolean;
|
|
241
276
|
}
|
|
277
|
+
/** A resolved skill in the get_skills catalog (mirrors the TUI skill-toggle resolution). */
|
|
278
|
+
export interface RpcSkillInfo {
|
|
279
|
+
/** Skill name (frontmatter name, falling back to the SKILL.md parent directory name). */
|
|
280
|
+
name: string;
|
|
281
|
+
/** Skill description from frontmatter. */
|
|
282
|
+
description?: string;
|
|
283
|
+
/** "global" for user-level skills, "project" for project-level skills. */
|
|
284
|
+
scope: "global" | "project";
|
|
285
|
+
/** Toggle pattern relative to the skill base dir (same shape the TUI writes to settings.skills). */
|
|
286
|
+
pattern: string;
|
|
287
|
+
/** Resolved enabled state per isEnabledByOverrides semantics. */
|
|
288
|
+
enabled: boolean;
|
|
289
|
+
}
|
|
290
|
+
/** Session listing entry (SessionInfo with transcript text stripped). */
|
|
291
|
+
export interface RpcSessionInfo {
|
|
292
|
+
path: string;
|
|
293
|
+
id: string;
|
|
294
|
+
cwd: string;
|
|
295
|
+
name?: string;
|
|
296
|
+
parentSessionPath?: string;
|
|
297
|
+
created: string;
|
|
298
|
+
modified: string;
|
|
299
|
+
messageCount: number;
|
|
300
|
+
firstMessage: string;
|
|
301
|
+
}
|
|
242
302
|
export interface RpcSessionState {
|
|
243
303
|
model?: Model<any>;
|
|
244
304
|
thinkingLevel: ThinkingLevel;
|
|
@@ -511,6 +571,15 @@ export type RpcResponse = {
|
|
|
511
571
|
data: {
|
|
512
572
|
commands: RpcSlashCommand[];
|
|
513
573
|
};
|
|
574
|
+
} | {
|
|
575
|
+
id?: string;
|
|
576
|
+
type: "response";
|
|
577
|
+
command: "get_skills";
|
|
578
|
+
success: true;
|
|
579
|
+
data: {
|
|
580
|
+
skills: RpcSkillInfo[];
|
|
581
|
+
patterns: string[];
|
|
582
|
+
};
|
|
514
583
|
} | {
|
|
515
584
|
id?: string;
|
|
516
585
|
type: "response";
|
|
@@ -601,6 +670,52 @@ export type RpcResponse = {
|
|
|
601
670
|
data: {
|
|
602
671
|
sessionPath: string;
|
|
603
672
|
};
|
|
673
|
+
} | {
|
|
674
|
+
id?: string;
|
|
675
|
+
type: "response";
|
|
676
|
+
command: "navigate_tree";
|
|
677
|
+
success: true;
|
|
678
|
+
data: {
|
|
679
|
+
editorText?: string;
|
|
680
|
+
cancelled: boolean;
|
|
681
|
+
aborted?: boolean;
|
|
682
|
+
summaryEntry?: BranchSummaryEntry | null;
|
|
683
|
+
};
|
|
684
|
+
} | {
|
|
685
|
+
id?: string;
|
|
686
|
+
type: "response";
|
|
687
|
+
command: "list_sessions";
|
|
688
|
+
success: true;
|
|
689
|
+
data: {
|
|
690
|
+
sessions: RpcSessionInfo[];
|
|
691
|
+
};
|
|
692
|
+
} | {
|
|
693
|
+
id?: string;
|
|
694
|
+
type: "response";
|
|
695
|
+
command: "rename_session";
|
|
696
|
+
success: true;
|
|
697
|
+
data: {
|
|
698
|
+
path: string;
|
|
699
|
+
name: string;
|
|
700
|
+
};
|
|
701
|
+
} | {
|
|
702
|
+
id?: string;
|
|
703
|
+
type: "response";
|
|
704
|
+
command: "delete_session";
|
|
705
|
+
success: true;
|
|
706
|
+
data: {
|
|
707
|
+
path: string;
|
|
708
|
+
method: "trash" | "unlink";
|
|
709
|
+
};
|
|
710
|
+
} | {
|
|
711
|
+
id?: string;
|
|
712
|
+
type: "response";
|
|
713
|
+
command: "set_session_models";
|
|
714
|
+
success: true;
|
|
715
|
+
data: {
|
|
716
|
+
enabled: string[];
|
|
717
|
+
models: Model<any>[];
|
|
718
|
+
};
|
|
604
719
|
} | {
|
|
605
720
|
id?: string;
|
|
606
721
|
type: "response";
|
|
@@ -753,6 +868,18 @@ export type RpcExtensionUIRequest = {
|
|
|
753
868
|
id: string;
|
|
754
869
|
method: "set_editor_text";
|
|
755
870
|
text: string;
|
|
871
|
+
} | {
|
|
872
|
+
type: "extension_ui_request";
|
|
873
|
+
id: string;
|
|
874
|
+
method: "working";
|
|
875
|
+
/** Working/loading message shown during streaming. Omit to restore the default. */
|
|
876
|
+
message?: string;
|
|
877
|
+
/** Show or hide the built-in working loader row during streaming. */
|
|
878
|
+
visible?: boolean;
|
|
879
|
+
/** Custom animation frames for the normal streaming loader. Empty array hides the indicator; omit to restore the default spinner. */
|
|
880
|
+
frames?: string[];
|
|
881
|
+
/** Frame interval in milliseconds for animated indicators. */
|
|
882
|
+
intervalMs?: number;
|
|
756
883
|
};
|
|
757
884
|
/** Response to an extension UI request */
|
|
758
885
|
export type RpcExtensionUIResponse = {
|
|
@@ -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
|
|
@@ -22,12 +22,11 @@ Read `../pi-subagents/SKILL.md` and its references to pick builtin/custom agents
|
|
|
22
22
|
|
|
23
23
|
| Subtask | Agent |
|
|
24
24
|
| --- | --- |
|
|
25
|
-
| Codebase recon / find seams | `scout`
|
|
25
|
+
| Codebase recon / find seams | `scout` |
|
|
26
26
|
| External/uncertain research | `researcher` |
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
| Parent-synthesized fixes | `worker` |
|
|
27
|
+
| Scoped implementation | `worker` or `delegate` |
|
|
28
|
+
| Independent review | `reviewer` |
|
|
29
|
+
| Material architecture/product tradeoffs | `oracle` or Council Mode |
|
|
31
30
|
|
|
32
31
|
For material architecture/product tradeoffs, resolve them first with an `oracle` or Council Mode before implementation.
|
|
33
32
|
|
|
@@ -52,21 +51,16 @@ const scout = await runs.run("scout", {
|
|
|
52
51
|
agent: "scout",
|
|
53
52
|
task: "Find the code seams, existing helpers, and constraints involved. Do not edit."
|
|
54
53
|
});
|
|
55
|
-
const plan = await runs.run("plan", {
|
|
56
|
-
agent: "architect",
|
|
57
|
-
reads: [scout.outputReference],
|
|
58
|
-
task: "Plan the change from the scout findings. Return files, order, checks, non-goals."
|
|
59
|
-
});
|
|
60
54
|
const worker = await runs.run("implement", {
|
|
61
55
|
agent: "worker",
|
|
62
|
-
reads: [
|
|
63
|
-
task: "Implement the
|
|
56
|
+
reads: [scout.outputReference],
|
|
57
|
+
task: "Implement the requested change from the scout findings. Return changed files, checks run, residual risks, decisions needing approval."
|
|
64
58
|
});
|
|
65
59
|
const reviews = await runs.all([
|
|
66
|
-
{ key: "correctness", agent: "reviewer", reads: [
|
|
67
|
-
{ key: "simplicity", agent: "reviewer", reads: [
|
|
60
|
+
{ key: "correctness", agent: "reviewer", reads: [scout.outputReference, worker.outputReference], task: "Inspect the current diff for correctness and regressions. Do not edit." },
|
|
61
|
+
{ key: "simplicity", agent: "reviewer", reads: [scout.outputReference, worker.outputReference], task: "Inspect the current diff for unnecessary complexity. Do not edit." }
|
|
68
62
|
]);
|
|
69
|
-
return {
|
|
63
|
+
return { worker: worker.outputReference, reviews: reviews.map((result) => result.outputReference) };
|
|
70
64
|
```
|
|
71
65
|
|
|
72
66
|
### Parallel independent subtasks
|