@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/core/agent-session.d.ts +6 -0
  3. package/dist/core/agent-session.js +5 -3
  4. package/dist/extensions/handoff-new.test.ts +26 -1
  5. package/dist/extensions/handoff-new.ts +8 -0
  6. package/dist/extensions/herdr-agent-state.ts +310 -0
  7. package/dist/extensions/package.json +1 -0
  8. package/dist/extensions/pi-subagents/README.md +0 -6
  9. package/dist/extensions/pi-subagents/docs/agents.md +1 -114
  10. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +1 -12
  11. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  12. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +3 -19
  13. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +8 -37
  14. package/dist/extensions/pi-subagents/src/shared/types.ts +2 -2
  15. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +2 -38
  16. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +0 -49
  17. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +0 -18
  18. package/dist/modes/rpc/rpc-client.d.ts +59 -7
  19. package/dist/modes/rpc/rpc-client.js +65 -11
  20. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  21. package/dist/modes/rpc/rpc-mode.js +280 -12
  22. package/dist/modes/rpc/rpc-types.d.ts +128 -1
  23. package/dist/skills/improve-codebase/SKILL.md +1 -1
  24. package/dist/skills/planger/SKILL.md +1 -1
  25. package/dist/skills/workflow/SKILL.md +9 -15
  26. package/docs/rpc.md +226 -2
  27. package/package.json +1 -1
  28. package/dist/extensions/pi-subagents/agents/architect.md +0 -170
  29. package/dist/extensions/pi-subagents/agents/builder.md +0 -37
  30. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +0 -15
  31. package/dist/extensions/pi-subagents/agents/claude-code.md +0 -15
  32. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +0 -15
  33. package/dist/extensions/pi-subagents/agents/codex-exec.md +0 -15
  34. package/dist/extensions/pi-subagents/agents/commentator.md +0 -37
  35. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +0 -14
  36. package/dist/extensions/pi-subagents/agents/cursor-agent.md +0 -14
  37. package/dist/extensions/pi-subagents/agents/explorer.md +0 -32
  38. package/dist/extensions/pi-subagents/agents/recapper.md +0 -31
  39. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +0 -129
  40. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +0 -129
  41. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +0 -114
  42. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +0 -52
  43. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +0 -55
  44. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +0 -53
  45. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +0 -57
  46. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +0 -59
  47. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +0 -62
  48. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +0 -245
  49. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +0 -192
  50. 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 { VERSION, getAgentDir, getBundledDefaultsDir, getShareViewerUrl } from "../../config.js";
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(_message) {
163
- // Working message not supported in RPC mode - requires TUI loader access
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(_visible) {
166
- // Working visibility not supported in RPC mode - requires TUI loader access
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(_options) {
169
- // Working indicator customization not supported in RPC mode - requires TUI loader access
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: "explorer", task: "..." }` (do not pass a model) when possible.
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 explorer agent):
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` or `explorer` |
25
+ | Codebase recon / find seams | `scout` |
26
26
  | External/uncertain research | `researcher` |
27
- | Implementation planning | `architect` |
28
- | Scoped implementation | `worker` or `builder` |
29
- | Independent review | `reviewer` or `commentator` |
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: [plan.outputReference],
63
- task: "Implement the approved plan. Return changed files, checks run, residual risks, decisions needing approval."
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: [plan.outputReference, worker.outputReference], task: "Inspect the current diff for correctness and regressions. Do not edit." },
67
- { key: "simplicity", agent: "reviewer", reads: [plan.outputReference, worker.outputReference], task: "Inspect the current diff for unnecessary complexity. Do not edit." }
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 { plan: plan.outputReference, worker: worker.outputReference, reviews: reviews.map((result) => result.outputReference) };
63
+ return { worker: worker.outputReference, reviews: reviews.map((result) => result.outputReference) };
70
64
  ```
71
65
 
72
66
  ### Parallel independent subtasks