@oh-my-pi/pi-coding-agent 15.10.11 → 15.11.0
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 +103 -2
- package/dist/cli.js +5790 -5731
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +85 -34
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +5 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +49 -32
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +46 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +12 -2
- package/dist/types/task/index.d.ts +13 -6
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +63 -51
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +28 -15
- package/src/commands/launch.ts +4 -0
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/keybindings.ts +6 -1
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +99 -55
- package/src/config/settings.ts +68 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/prelude.py +5 -6
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +38 -13
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/shared-events.ts +2 -2
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +26 -66
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +65 -0
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +19 -2
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/controllers/selector-controller.ts +21 -17
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +47 -22
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/theme/theme.ts +18 -5
- package/src/modes/types.ts +5 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +38 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +37 -10
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +422 -291
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +226 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +851 -461
- package/src/task/index.ts +721 -796
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +148 -63
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +82 -66
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +61 -10
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +17 -13
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
|
@@ -21,9 +21,11 @@ import {
|
|
|
21
21
|
} from "../../extensibility/extensions";
|
|
22
22
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
23
23
|
import type { AgentSession } from "../../session/agent-session";
|
|
24
|
+
import type { EventBus } from "../../utils/event-bus";
|
|
24
25
|
import { initializeExtensions } from "../runtime-init";
|
|
25
26
|
import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
|
|
26
27
|
import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
|
|
28
|
+
import { RpcSubagentRegistry, readRpcSubagentTranscript } from "./rpc-subagents";
|
|
27
29
|
import type {
|
|
28
30
|
RpcCommand,
|
|
29
31
|
RpcExtensionUIRequest,
|
|
@@ -35,6 +37,7 @@ import type {
|
|
|
35
37
|
RpcHostUriRequest,
|
|
36
38
|
RpcResponse,
|
|
37
39
|
RpcSessionState,
|
|
40
|
+
RpcSubagentSubscriptionLevel,
|
|
38
41
|
} from "./rpc-types";
|
|
39
42
|
|
|
40
43
|
// Re-export types for consumers
|
|
@@ -56,6 +59,47 @@ type RpcOutput = (
|
|
|
56
59
|
| object,
|
|
57
60
|
) => void;
|
|
58
61
|
|
|
62
|
+
export type RpcSessionChangeCommand = Extract<
|
|
63
|
+
RpcCommand,
|
|
64
|
+
{ type: "new_session" } | { type: "switch_session" } | { type: "branch" }
|
|
65
|
+
>;
|
|
66
|
+
|
|
67
|
+
export type RpcSessionChangeResult =
|
|
68
|
+
| { type: "new_session"; data: { cancelled: boolean } }
|
|
69
|
+
| { type: "switch_session"; data: { cancelled: boolean } }
|
|
70
|
+
| { type: "branch"; data: { text: string; cancelled: boolean } };
|
|
71
|
+
|
|
72
|
+
export type RpcSessionChangeSession = Pick<AgentSession, "newSession" | "switchSession" | "branch">;
|
|
73
|
+
export type RpcSubagentResetRegistry = Pick<RpcSubagentRegistry, "clear">;
|
|
74
|
+
|
|
75
|
+
export async function handleRpcSessionChange(
|
|
76
|
+
session: RpcSessionChangeSession,
|
|
77
|
+
command: RpcSessionChangeCommand,
|
|
78
|
+
subagentRegistry?: RpcSubagentResetRegistry,
|
|
79
|
+
): Promise<RpcSessionChangeResult> {
|
|
80
|
+
switch (command.type) {
|
|
81
|
+
case "new_session": {
|
|
82
|
+
const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
|
|
83
|
+
const cancelled = !(await session.newSession(options));
|
|
84
|
+
if (!cancelled) subagentRegistry?.clear();
|
|
85
|
+
return { type: "new_session", data: { cancelled } };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case "switch_session": {
|
|
89
|
+
const cancelled = !(await session.switchSession(command.sessionPath));
|
|
90
|
+
if (!cancelled) subagentRegistry?.clear();
|
|
91
|
+
return { type: "switch_session", data: { cancelled } };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case "branch": {
|
|
95
|
+
const result = await session.branch(command.entryId);
|
|
96
|
+
if (!result.cancelled) subagentRegistry?.clear();
|
|
97
|
+
return { type: "branch", data: { text: result.selectedText, cancelled: result.cancelled } };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error("Unsupported RPC session change command");
|
|
101
|
+
}
|
|
102
|
+
|
|
59
103
|
function normalizeHostToolDefinitions(tools: RpcHostToolDefinition[]): RpcHostToolDefinition[] {
|
|
60
104
|
return tools.map((tool, index) => {
|
|
61
105
|
const name = typeof tool.name === "string" ? tool.name.trim() : "";
|
|
@@ -99,6 +143,10 @@ function shouldEmitRpcTitles(): boolean {
|
|
|
99
143
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
100
144
|
}
|
|
101
145
|
|
|
146
|
+
function isSubagentSubscriptionLevel(value: unknown): value is RpcSubagentSubscriptionLevel {
|
|
147
|
+
return value === "off" || value === "progress" || value === "events";
|
|
148
|
+
}
|
|
149
|
+
|
|
102
150
|
export function requestRpcEditor(
|
|
103
151
|
pendingRequests: Map<string, PendingExtensionRequest>,
|
|
104
152
|
output: RpcOutput,
|
|
@@ -169,6 +217,7 @@ export function requestRpcEditor(
|
|
|
169
217
|
export async function runRpcMode(
|
|
170
218
|
session: AgentSession,
|
|
171
219
|
setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
220
|
+
eventBus?: EventBus,
|
|
172
221
|
): Promise<never> {
|
|
173
222
|
// Signal to RPC clients that the server is ready to accept commands
|
|
174
223
|
// Suppress terminal notifications: they write \x07 (BEL) or OSC sequences directly to
|
|
@@ -201,6 +250,7 @@ export async function runRpcMode(
|
|
|
201
250
|
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
202
251
|
const hostToolBridge = new RpcHostToolBridge(output);
|
|
203
252
|
const hostUriBridge = new RpcHostUriBridge(output);
|
|
253
|
+
const subagentRegistry = eventBus ? new RpcSubagentRegistry(eventBus, output) : undefined;
|
|
204
254
|
|
|
205
255
|
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
206
256
|
const shutdownState = { requested: false };
|
|
@@ -507,9 +557,8 @@ export async function runRpcMode(
|
|
|
507
557
|
}
|
|
508
558
|
|
|
509
559
|
case "new_session": {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
return success(id, "new_session", { cancelled });
|
|
560
|
+
const result = await handleRpcSessionChange(session, command, subagentRegistry);
|
|
561
|
+
return success(id, result.type, result.data);
|
|
513
562
|
}
|
|
514
563
|
|
|
515
564
|
// =================================================================
|
|
@@ -564,6 +613,44 @@ export async function runRpcMode(
|
|
|
564
613
|
}
|
|
565
614
|
}
|
|
566
615
|
|
|
616
|
+
case "set_subagent_subscription": {
|
|
617
|
+
if (!subagentRegistry) {
|
|
618
|
+
return error(id, "set_subagent_subscription", "Subagent event bus is unavailable");
|
|
619
|
+
}
|
|
620
|
+
if (!isSubagentSubscriptionLevel(command.level)) {
|
|
621
|
+
return error(
|
|
622
|
+
id,
|
|
623
|
+
"set_subagent_subscription",
|
|
624
|
+
`Invalid subagent subscription level: ${String(command.level)}`,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
subagentRegistry.setSubscriptionLevel(command.level);
|
|
628
|
+
return success(id, "set_subagent_subscription", { level: subagentRegistry.getSubscriptionLevel() });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
case "get_subagents": {
|
|
632
|
+
if (!subagentRegistry) {
|
|
633
|
+
return error(id, "get_subagents", "Subagent event bus is unavailable");
|
|
634
|
+
}
|
|
635
|
+
return success(id, "get_subagents", { subagents: subagentRegistry.getSubagents() });
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
case "get_subagent_messages": {
|
|
639
|
+
if (!subagentRegistry) {
|
|
640
|
+
return error(id, "get_subagent_messages", "Subagent event bus is unavailable");
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
if (command.fromByte !== undefined && !Number.isFinite(command.fromByte)) {
|
|
644
|
+
return error(id, "get_subagent_messages", "fromByte must be a finite number");
|
|
645
|
+
}
|
|
646
|
+
const sessionFile = subagentRegistry.resolveSessionFile(command);
|
|
647
|
+
const transcript = await readRpcSubagentTranscript(sessionFile, command.fromByte);
|
|
648
|
+
return success(id, "get_subagent_messages", transcript);
|
|
649
|
+
} catch (err) {
|
|
650
|
+
return error(id, "get_subagent_messages", err instanceof Error ? err.message : String(err));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
567
654
|
// =================================================================
|
|
568
655
|
// Model
|
|
569
656
|
// =================================================================
|
|
@@ -683,14 +770,10 @@ export async function runRpcMode(
|
|
|
683
770
|
return success(id, "export_html", { path });
|
|
684
771
|
}
|
|
685
772
|
|
|
686
|
-
case "switch_session":
|
|
687
|
-
const cancelled = !(await session.switchSession(command.sessionPath));
|
|
688
|
-
return success(id, "switch_session", { cancelled });
|
|
689
|
-
}
|
|
690
|
-
|
|
773
|
+
case "switch_session":
|
|
691
774
|
case "branch": {
|
|
692
|
-
const result = await session
|
|
693
|
-
return success(id,
|
|
775
|
+
const result = await handleRpcSessionChange(session, command, subagentRegistry);
|
|
776
|
+
return success(id, result.type, result.data);
|
|
694
777
|
}
|
|
695
778
|
|
|
696
779
|
case "get_branch_messages": {
|
|
@@ -850,13 +933,15 @@ export async function runRpcMode(
|
|
|
850
933
|
|
|
851
934
|
// Check for deferred shutdown request (idle between commands)
|
|
852
935
|
await checkShutdownRequested();
|
|
853
|
-
} catch (e:
|
|
854
|
-
|
|
936
|
+
} catch (e: unknown) {
|
|
937
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
938
|
+
output(error(undefined, "parse", `Failed to parse command: ${message}`));
|
|
855
939
|
}
|
|
856
940
|
}
|
|
857
941
|
|
|
858
942
|
// stdin closed — RPC client is gone, exit cleanly
|
|
859
943
|
hostToolBridge.rejectAllPending("RPC client disconnected before host tool execution completed");
|
|
860
944
|
hostUriBridge.clear("RPC client disconnected before host URI request completed");
|
|
945
|
+
subagentRegistry?.dispose();
|
|
861
946
|
process.exit(0);
|
|
862
947
|
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import type { FileEntry, SessionMessageEntry } from "../../session/session-manager";
|
|
4
|
+
import { parseSessionEntries } from "../../session/session-manager";
|
|
5
|
+
import {
|
|
6
|
+
type AgentProgress,
|
|
7
|
+
type SubagentEventPayload,
|
|
8
|
+
type SubagentLifecyclePayload,
|
|
9
|
+
type SubagentProgressPayload,
|
|
10
|
+
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
11
|
+
TASK_SUBAGENT_LIFECYCLE_CHANNEL,
|
|
12
|
+
TASK_SUBAGENT_PROGRESS_CHANNEL,
|
|
13
|
+
} from "../../task";
|
|
14
|
+
import type { EventBus } from "../../utils/event-bus";
|
|
15
|
+
import type {
|
|
16
|
+
RpcSubagentEventFrame,
|
|
17
|
+
RpcSubagentFrame,
|
|
18
|
+
RpcSubagentMessagesResult,
|
|
19
|
+
RpcSubagentSnapshot,
|
|
20
|
+
RpcSubagentSubscriptionLevel,
|
|
21
|
+
} from "./rpc-types";
|
|
22
|
+
|
|
23
|
+
export interface RpcSubagentTranscriptSelector {
|
|
24
|
+
subagentId?: string;
|
|
25
|
+
sessionFile?: string;
|
|
26
|
+
fromByte?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type RpcSubagentOutput = (frame: RpcSubagentFrame) => void;
|
|
30
|
+
|
|
31
|
+
const MAX_RETAINED_TRANSCRIPT_REFERENCES = 256;
|
|
32
|
+
|
|
33
|
+
function isSessionMessageEntry(entry: FileEntry): entry is SessionMessageEntry {
|
|
34
|
+
return entry.type === "message";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function statusFromLifecycle(status: SubagentLifecyclePayload["status"]): AgentProgress["status"] {
|
|
38
|
+
return status === "started" ? "running" : status;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isTerminalLifecycleStatus(status: SubagentLifecyclePayload["status"]): boolean {
|
|
42
|
+
return status !== "started";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasSameOwner(
|
|
46
|
+
payload: Pick<SubagentLifecyclePayload | SubagentProgressPayload, "parentToolCallId" | "sessionFile">,
|
|
47
|
+
snapshot: RpcSubagentSnapshot,
|
|
48
|
+
): boolean {
|
|
49
|
+
if (payload.parentToolCallId !== undefined && snapshot.parentToolCallId !== undefined) {
|
|
50
|
+
return payload.parentToolCallId === snapshot.parentToolCallId;
|
|
51
|
+
}
|
|
52
|
+
if (payload.sessionFile !== undefined && snapshot.sessionFile !== undefined) {
|
|
53
|
+
return payload.sessionFile === snapshot.sessionFile;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addPruned(set: Set<string>, value: string, maxSize: number): void {
|
|
59
|
+
set.delete(value);
|
|
60
|
+
set.add(value);
|
|
61
|
+
while (set.size > maxSize) {
|
|
62
|
+
const oldest = set.keys().next();
|
|
63
|
+
if (oldest.done) break;
|
|
64
|
+
set.delete(oldest.value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function readRpcSubagentTranscript(sessionFile: string, fromByte = 0): Promise<RpcSubagentMessagesResult> {
|
|
69
|
+
let startByte = Number.isFinite(fromByte) ? Math.max(0, Math.trunc(fromByte)) : 0;
|
|
70
|
+
const file = Bun.file(sessionFile);
|
|
71
|
+
let size: number;
|
|
72
|
+
try {
|
|
73
|
+
({ size } = await fs.stat(sessionFile));
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (!isEnoent(err)) throw err;
|
|
76
|
+
return {
|
|
77
|
+
sessionFile,
|
|
78
|
+
fromByte: startByte,
|
|
79
|
+
nextByte: startByte,
|
|
80
|
+
reset: false,
|
|
81
|
+
entries: [],
|
|
82
|
+
messages: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
let reset = false;
|
|
86
|
+
if (startByte > size) {
|
|
87
|
+
startByte = 0;
|
|
88
|
+
reset = true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const text = startByte >= size ? "" : await file.slice(startByte).text();
|
|
92
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
93
|
+
const completeText = lastNewline >= 0 ? text.slice(0, lastNewline + 1) : "";
|
|
94
|
+
const entries = completeText.length > 0 ? parseSessionEntries(completeText) : [];
|
|
95
|
+
const nextByte = startByte + Buffer.byteLength(completeText, "utf8");
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
sessionFile,
|
|
99
|
+
fromByte: startByte,
|
|
100
|
+
nextByte,
|
|
101
|
+
reset,
|
|
102
|
+
entries,
|
|
103
|
+
messages: entries.filter(isSessionMessageEntry).map(entry => entry.message),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class RpcSubagentRegistry {
|
|
108
|
+
#subagents = new Map<string, RpcSubagentSnapshot>();
|
|
109
|
+
#transcriptSessionFilesBySubagentId = new Map<string, string>();
|
|
110
|
+
#staleSubagentIds = new Set<string>();
|
|
111
|
+
#unsubscribers: Array<() => void> = [];
|
|
112
|
+
#output: RpcSubagentOutput;
|
|
113
|
+
#subscriptionLevel: RpcSubagentSubscriptionLevel = "off";
|
|
114
|
+
|
|
115
|
+
constructor(eventBus: EventBus, output: RpcSubagentOutput) {
|
|
116
|
+
this.#output = output;
|
|
117
|
+
this.#unsubscribers.push(
|
|
118
|
+
eventBus.on(TASK_SUBAGENT_LIFECYCLE_CHANNEL, data => {
|
|
119
|
+
this.handleLifecycle(data as SubagentLifecyclePayload);
|
|
120
|
+
}),
|
|
121
|
+
eventBus.on(TASK_SUBAGENT_PROGRESS_CHANNEL, data => {
|
|
122
|
+
this.handleProgress(data as SubagentProgressPayload);
|
|
123
|
+
}),
|
|
124
|
+
eventBus.on(TASK_SUBAGENT_EVENT_CHANNEL, data => {
|
|
125
|
+
this.handleEvent(data as SubagentEventPayload);
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
dispose(): void {
|
|
131
|
+
for (const unsubscribe of this.#unsubscribers) unsubscribe();
|
|
132
|
+
this.#unsubscribers = [];
|
|
133
|
+
this.#subagents.clear();
|
|
134
|
+
this.#transcriptSessionFilesBySubagentId.clear();
|
|
135
|
+
this.#staleSubagentIds.clear();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
clear(): void {
|
|
139
|
+
for (const subagentId of this.#subagents.keys()) {
|
|
140
|
+
addPruned(this.#staleSubagentIds, subagentId, MAX_RETAINED_TRANSCRIPT_REFERENCES);
|
|
141
|
+
}
|
|
142
|
+
for (const subagentId of this.#transcriptSessionFilesBySubagentId.keys()) {
|
|
143
|
+
addPruned(this.#staleSubagentIds, subagentId, MAX_RETAINED_TRANSCRIPT_REFERENCES);
|
|
144
|
+
}
|
|
145
|
+
this.#subagents.clear();
|
|
146
|
+
this.#transcriptSessionFilesBySubagentId.clear();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setSubscriptionLevel(level: RpcSubagentSubscriptionLevel): void {
|
|
150
|
+
this.#subscriptionLevel = level;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getSubscriptionLevel(): RpcSubagentSubscriptionLevel {
|
|
154
|
+
return this.#subscriptionLevel;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getSubagents(): RpcSubagentSnapshot[] {
|
|
158
|
+
return [...this.#subagents.values()].sort((a, b) => a.index - b.index || a.id.localeCompare(b.id));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#rememberTranscriptSession(subagentId: string, sessionFile: string | undefined): void {
|
|
162
|
+
if (!sessionFile) return;
|
|
163
|
+
this.#transcriptSessionFilesBySubagentId.delete(subagentId);
|
|
164
|
+
this.#transcriptSessionFilesBySubagentId.set(subagentId, sessionFile);
|
|
165
|
+
while (this.#transcriptSessionFilesBySubagentId.size > MAX_RETAINED_TRANSCRIPT_REFERENCES) {
|
|
166
|
+
const oldest = this.#transcriptSessionFilesBySubagentId.keys().next();
|
|
167
|
+
if (oldest.done) break;
|
|
168
|
+
this.#transcriptSessionFilesBySubagentId.delete(oldest.value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#hasTranscriptSessionFile(sessionFile: string): boolean {
|
|
173
|
+
for (const snapshot of this.#subagents.values()) {
|
|
174
|
+
if (snapshot.sessionFile === sessionFile) return true;
|
|
175
|
+
}
|
|
176
|
+
for (const transcriptSessionFile of this.#transcriptSessionFilesBySubagentId.values()) {
|
|
177
|
+
if (transcriptSessionFile === sessionFile) return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
handleLifecycle(payload: SubagentLifecyclePayload): void {
|
|
183
|
+
const existing = this.#subagents.get(payload.id);
|
|
184
|
+
if (existing && !hasSameOwner(payload, existing)) return;
|
|
185
|
+
if (!existing && payload.status !== "started") return;
|
|
186
|
+
if (payload.status === "started") {
|
|
187
|
+
this.#staleSubagentIds.delete(payload.id);
|
|
188
|
+
}
|
|
189
|
+
const sessionFile = payload.sessionFile ?? existing?.sessionFile;
|
|
190
|
+
const snapshot: RpcSubagentSnapshot = {
|
|
191
|
+
id: payload.id,
|
|
192
|
+
index: payload.index,
|
|
193
|
+
agent: payload.agent,
|
|
194
|
+
agentSource: payload.agentSource,
|
|
195
|
+
description: payload.description ?? existing?.description,
|
|
196
|
+
status: statusFromLifecycle(payload.status),
|
|
197
|
+
task: existing?.task,
|
|
198
|
+
assignment: existing?.assignment,
|
|
199
|
+
sessionFile,
|
|
200
|
+
parentToolCallId: payload.parentToolCallId ?? existing?.parentToolCallId,
|
|
201
|
+
lastUpdate: Date.now(),
|
|
202
|
+
progress: existing?.progress,
|
|
203
|
+
};
|
|
204
|
+
this.#rememberTranscriptSession(payload.id, sessionFile);
|
|
205
|
+
if (isTerminalLifecycleStatus(payload.status)) {
|
|
206
|
+
this.#subagents.delete(payload.id);
|
|
207
|
+
} else {
|
|
208
|
+
this.#subagents.set(payload.id, snapshot);
|
|
209
|
+
}
|
|
210
|
+
if (this.#subscriptionLevel !== "off") {
|
|
211
|
+
this.#output({ type: "subagent_lifecycle", payload });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
handleProgress(payload: SubagentProgressPayload): void {
|
|
216
|
+
const progress = payload.progress;
|
|
217
|
+
if (this.#staleSubagentIds.has(progress.id)) return;
|
|
218
|
+
const existing = this.#subagents.get(progress.id);
|
|
219
|
+
if (!existing) return;
|
|
220
|
+
if (!hasSameOwner(payload, existing)) return;
|
|
221
|
+
const sessionFile = payload.sessionFile ?? existing?.sessionFile;
|
|
222
|
+
this.#rememberTranscriptSession(progress.id, sessionFile);
|
|
223
|
+
this.#subagents.set(progress.id, {
|
|
224
|
+
id: progress.id,
|
|
225
|
+
index: payload.index,
|
|
226
|
+
agent: payload.agent,
|
|
227
|
+
agentSource: payload.agentSource,
|
|
228
|
+
description: progress.description ?? existing?.description,
|
|
229
|
+
status: progress.status,
|
|
230
|
+
task: payload.task,
|
|
231
|
+
assignment: payload.assignment,
|
|
232
|
+
sessionFile,
|
|
233
|
+
lastUpdate: Date.now(),
|
|
234
|
+
parentToolCallId: payload.parentToolCallId ?? existing?.parentToolCallId,
|
|
235
|
+
progress,
|
|
236
|
+
});
|
|
237
|
+
if (this.#subscriptionLevel !== "off") {
|
|
238
|
+
this.#output({ type: "subagent_progress", payload });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
handleEvent(payload: SubagentEventPayload): void {
|
|
243
|
+
if (this.#staleSubagentIds.has(payload.id)) return;
|
|
244
|
+
if (this.#subscriptionLevel !== "events") return;
|
|
245
|
+
this.#output({ type: "subagent_event", payload } satisfies RpcSubagentEventFrame);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
resolveSessionFile(selector: RpcSubagentTranscriptSelector): string {
|
|
249
|
+
if (selector.subagentId) {
|
|
250
|
+
const snapshot = this.#subagents.get(selector.subagentId);
|
|
251
|
+
const sessionFile = snapshot?.sessionFile ?? this.#transcriptSessionFilesBySubagentId.get(selector.subagentId);
|
|
252
|
+
if (!sessionFile) {
|
|
253
|
+
throw new Error(`Unknown subagent or session file unavailable: ${selector.subagentId}`);
|
|
254
|
+
}
|
|
255
|
+
return sessionFile;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (selector.sessionFile) {
|
|
259
|
+
if (this.#hasTranscriptSessionFile(selector.sessionFile)) return selector.sessionFile;
|
|
260
|
+
throw new Error("Unknown subagent session file");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
throw new Error("get_subagent_messages requires subagentId or sessionFile");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -9,7 +9,14 @@ import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
|
|
|
9
9
|
import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { BashResult } from "../../exec/bash-executor";
|
|
11
11
|
import type { ContextUsage } from "../../extensibility/extensions/types";
|
|
12
|
-
import type { SessionStats } from "../../session/agent-session";
|
|
12
|
+
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
|
|
13
|
+
import type { FileEntry } from "../../session/session-manager";
|
|
14
|
+
import type {
|
|
15
|
+
AgentProgress,
|
|
16
|
+
SubagentEventPayload,
|
|
17
|
+
SubagentLifecyclePayload,
|
|
18
|
+
SubagentProgressPayload,
|
|
19
|
+
} from "../../task";
|
|
13
20
|
import type { TodoPhase } from "../../tools/todo";
|
|
14
21
|
|
|
15
22
|
// ============================================================================
|
|
@@ -30,6 +37,9 @@ export type RpcCommand =
|
|
|
30
37
|
| { id?: string; type: "set_todos"; phases: TodoPhase[] }
|
|
31
38
|
| { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
|
|
32
39
|
| { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
|
|
40
|
+
| { id?: string; type: "set_subagent_subscription"; level: RpcSubagentSubscriptionLevel }
|
|
41
|
+
| { id?: string; type: "get_subagents" }
|
|
42
|
+
| { id?: string; type: "get_subagent_messages"; subagentId?: string; sessionFile?: string; fromByte?: number }
|
|
33
43
|
|
|
34
44
|
// Model
|
|
35
45
|
| { id?: string; type: "set_model"; provider: string; modelId: string }
|
|
@@ -104,6 +114,32 @@ export interface RpcHandoffResult {
|
|
|
104
114
|
savedPath?: string;
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
export type RpcSubagentSubscriptionLevel = "off" | "progress" | "events";
|
|
118
|
+
|
|
119
|
+
export interface RpcSubagentSnapshot {
|
|
120
|
+
id: string;
|
|
121
|
+
index: number;
|
|
122
|
+
agent: string;
|
|
123
|
+
agentSource: AgentProgress["agentSource"];
|
|
124
|
+
description?: string;
|
|
125
|
+
status: AgentProgress["status"];
|
|
126
|
+
task?: string;
|
|
127
|
+
assignment?: string;
|
|
128
|
+
sessionFile?: string;
|
|
129
|
+
lastUpdate: number;
|
|
130
|
+
progress?: AgentProgress;
|
|
131
|
+
parentToolCallId?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface RpcSubagentMessagesResult {
|
|
135
|
+
sessionFile: string;
|
|
136
|
+
fromByte: number;
|
|
137
|
+
nextByte: number;
|
|
138
|
+
reset: boolean;
|
|
139
|
+
entries: FileEntry[];
|
|
140
|
+
messages: AgentMessage[];
|
|
141
|
+
}
|
|
142
|
+
|
|
107
143
|
// ============================================================================
|
|
108
144
|
// RPC Responses (stdout)
|
|
109
145
|
// ============================================================================
|
|
@@ -123,6 +159,27 @@ export type RpcResponse =
|
|
|
123
159
|
| { id?: string; type: "response"; command: "set_todos"; success: true; data: { todoPhases: TodoPhase[] } }
|
|
124
160
|
| { id?: string; type: "response"; command: "set_host_tools"; success: true; data: { toolNames: string[] } }
|
|
125
161
|
| { id?: string; type: "response"; command: "set_host_uri_schemes"; success: true; data: { schemes: string[] } }
|
|
162
|
+
| {
|
|
163
|
+
id?: string;
|
|
164
|
+
type: "response";
|
|
165
|
+
command: "set_subagent_subscription";
|
|
166
|
+
success: true;
|
|
167
|
+
data: { level: RpcSubagentSubscriptionLevel };
|
|
168
|
+
}
|
|
169
|
+
| {
|
|
170
|
+
id?: string;
|
|
171
|
+
type: "response";
|
|
172
|
+
command: "get_subagents";
|
|
173
|
+
success: true;
|
|
174
|
+
data: { subagents: RpcSubagentSnapshot[] };
|
|
175
|
+
}
|
|
176
|
+
| {
|
|
177
|
+
id?: string;
|
|
178
|
+
type: "response";
|
|
179
|
+
command: "get_subagent_messages";
|
|
180
|
+
success: true;
|
|
181
|
+
data: RpcSubagentMessagesResult;
|
|
182
|
+
}
|
|
126
183
|
|
|
127
184
|
// Model
|
|
128
185
|
| {
|
|
@@ -212,6 +269,29 @@ export type RpcResponse =
|
|
|
212
269
|
// Error response (any command can fail)
|
|
213
270
|
| { id?: string; type: "response"; command: string; success: false; error: string };
|
|
214
271
|
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// Subagent Events (stdout)
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
export interface RpcSubagentLifecycleFrame {
|
|
277
|
+
type: "subagent_lifecycle";
|
|
278
|
+
payload: SubagentLifecyclePayload;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface RpcSubagentProgressFrame {
|
|
282
|
+
type: "subagent_progress";
|
|
283
|
+
payload: SubagentProgressPayload;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface RpcSubagentEventFrame {
|
|
287
|
+
type: "subagent_event";
|
|
288
|
+
payload: SubagentEventPayload;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export type RpcSubagentFrame = RpcSubagentLifecycleFrame | RpcSubagentProgressFrame | RpcSubagentEventFrame;
|
|
292
|
+
|
|
293
|
+
export type RpcSessionEventFrame = AgentSessionEvent | RpcSubagentFrame;
|
|
294
|
+
|
|
215
295
|
// ============================================================================
|
|
216
296
|
// Extension UI Events (stdout)
|
|
217
297
|
// ============================================================================
|
|
@@ -65,9 +65,15 @@ export async function markSetupWizardComplete(
|
|
|
65
65
|
await settings.flush();
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export interface RunSetupWizardOptions {
|
|
69
|
+
markComplete?: boolean;
|
|
70
|
+
playWelcomeIntro?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
export async function runSetupWizard(
|
|
69
74
|
ctx: InteractiveModeContext,
|
|
70
75
|
scenes: readonly SetupScene[] = ALL_SCENES,
|
|
76
|
+
options: RunSetupWizardOptions = {},
|
|
71
77
|
): Promise<void> {
|
|
72
78
|
if (scenes.length === 0) return;
|
|
73
79
|
const component = new SetupWizardComponent(ctx, scenes);
|
|
@@ -79,11 +85,15 @@ export async function runSetupWizard(
|
|
|
79
85
|
});
|
|
80
86
|
try {
|
|
81
87
|
await component.run();
|
|
82
|
-
|
|
88
|
+
if (options.markComplete !== false) {
|
|
89
|
+
await markSetupWizardComplete(ctx.settings);
|
|
90
|
+
}
|
|
83
91
|
} finally {
|
|
84
92
|
component.dispose();
|
|
85
93
|
ctx.ui.setFocus(component);
|
|
86
94
|
overlay.hide();
|
|
87
95
|
}
|
|
88
|
-
|
|
96
|
+
if (options.playWelcomeIntro !== false) {
|
|
97
|
+
ctx.playWelcomeIntro();
|
|
98
|
+
}
|
|
89
99
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InteractiveModeContext } from "../types";
|
|
2
|
+
|
|
3
|
+
export async function runProviderSetupWizard(ctx: InteractiveModeContext): Promise<void> {
|
|
4
|
+
// Keep the full setup wizard behind the existing cold-start boundary; a static
|
|
5
|
+
// import here would load provider/OAuth/search/theme setup deps on every TUI startup.
|
|
6
|
+
const { ALL_SCENES, runSetupWizard } = await import("./index");
|
|
7
|
+
const providersScene = ALL_SCENES.find(scene => scene.id === "providers");
|
|
8
|
+
if (!providersScene) {
|
|
9
|
+
ctx.showError("Provider setup is unavailable.");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
await runSetupWizard(ctx, [providersScene], {
|
|
13
|
+
markComplete: false,
|
|
14
|
+
playWelcomeIntro: false,
|
|
15
|
+
});
|
|
16
|
+
}
|