@selesai/code 0.2.1 → 0.3.1
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/dist/config.d.ts +4 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -45
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +101 -51
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/defaults/models.json +64 -113
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/copy-turn.ts +2 -2
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +2 -2
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +40 -47
- package/dist/extensions/pi-powerline-footer/package.json +2 -2
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +10 -10
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +1 -1
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +2 -2
- package/dist/extensions/pi-powerline-footer/theme.ts +6 -6
- package/dist/extensions/pi-powerline-footer/tps.ts +1 -1
- package/dist/extensions/pi-powerline-footer/types.ts +7 -7
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +52 -59
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/agents/architect.md +188 -0
- package/dist/extensions/pi-subagents/agents/builder.md +120 -0
- package/dist/extensions/pi-subagents/agents/commentator.md +131 -0
- package/dist/extensions/pi-subagents/agents/explorer.md +51 -0
- package/dist/extensions/pi-subagents/agents/recapper.md +21 -0
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +108 -17
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/package.json +2 -2
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +2 -2
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +1 -1
- package/dist/extensions/pi-web-agent/src/extension.ts +1 -1
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/extensions/question/helpers.ts +3 -3
- package/dist/extensions/question/index.ts +2 -2
- package/dist/extensions/question/navigation.ts +2 -2
- package/dist/extensions/question/package.json +2 -2
- package/dist/extensions/question/question-list.ts +2 -2
- package/dist/extensions/question/tests/question-list.test.ts +2 -2
- package/dist/extensions/question/tui-adapter.ts +2 -2
- package/dist/extensions/question/types.ts +2 -2
- package/dist/extensions/question/ui-protocol.ts +2 -2
- package/dist/extensions/rtk.ts +2 -2
- package/dist/extensions/tokenin-onboarding.ts +2 -2
- package/dist/extensions/undo.ts +1 -1
- package/dist/extensions/web-agent-onboarding.ts +3 -3
- package/dist/extensions/workflow/adapter.ts +16 -8
- package/dist/extensions/workflow/extension.ts +1 -1
- package/dist/extensions/workflow/package.json +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/dist/utils/thinking-tags.d.ts +7 -0
- package/dist/utils/thinking-tags.d.ts.map +1 -0
- package/dist/utils/thinking-tags.js +63 -0
- package/dist/utils/thinking-tags.js.map +1 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +2 -2
|
@@ -8,35 +8,40 @@
|
|
|
8
8
|
* Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})
|
|
9
9
|
* Toggle: async parameter (default: false, configurable via config.json)
|
|
10
10
|
*
|
|
11
|
-
* Config file: ~/.
|
|
11
|
+
* Config file: ~/.selesai/agent/extensions/subagent/config.json
|
|
12
12
|
* { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import * as fs from "node:fs";
|
|
16
17
|
import * as os from "node:os";
|
|
17
18
|
import * as path from "node:path";
|
|
18
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
19
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
20
|
-
import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@selesai/code";
|
|
20
|
+
import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@selesai/code";
|
|
21
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
22
22
|
import { discoverAgents } from "../agents/agents.ts";
|
|
23
23
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
24
24
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
25
25
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
26
26
|
import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
|
|
27
|
-
import { SubagentParams } from "./schemas.ts";
|
|
27
|
+
import { SubagentParams, WaitParams } from "./schemas.ts";
|
|
28
28
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
29
29
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
30
30
|
import { createResultWatcher } from "../runs/background/result-watcher.ts";
|
|
31
|
+
import { createScheduledRunManager } from "../runs/background/scheduled-runs.ts";
|
|
31
32
|
import { registerSlashCommands } from "../slash/slash-commands.ts";
|
|
32
33
|
import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.ts";
|
|
33
34
|
import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
|
|
35
|
+
import { createNativeSupervisorChannel } from "../intercom/native-supervisor-channel.ts";
|
|
36
|
+
import { registerSubagentRpcBridge } from "./rpc.ts";
|
|
34
37
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
35
38
|
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
39
|
+
import { waitForSubagents } from "../runs/background/wait.ts";
|
|
36
40
|
import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
37
41
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
38
42
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
39
43
|
import { loadConfig } from "./config.ts";
|
|
44
|
+
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
40
45
|
import {
|
|
41
46
|
type Details,
|
|
42
47
|
type SubagentState,
|
|
@@ -60,36 +65,10 @@ import {
|
|
|
60
65
|
|
|
61
66
|
export { loadConfig } from "./config.ts";
|
|
62
67
|
|
|
63
|
-
function resolveRunnerScript(): string {
|
|
64
|
-
const srcDir = path.dirname(fileURLToPath(import.meta.url));
|
|
65
|
-
const candidates = [
|
|
66
|
-
path.join(srcDir, "..", "runs", "background", "subagent-runner.js"),
|
|
67
|
-
path.join(srcDir, "..", "..", "dist", "runner", "runs", "background", "subagent-runner.js"),
|
|
68
|
-
path.join(srcDir, "..", "runs", "background", "subagent-runner.ts"),
|
|
69
|
-
];
|
|
70
|
-
for (const candidate of candidates) {
|
|
71
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
72
|
-
}
|
|
73
|
-
throw new Error("Could not resolve subagent-runner script");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const runnerConfigPath = process.env.SELESAI_SUBAGENT_RUNNER_CONFIG;
|
|
77
|
-
if (runnerConfigPath) {
|
|
78
|
-
(async () => {
|
|
79
|
-
const runnerScript = resolveRunnerScript();
|
|
80
|
-
const runnerModule = await import(pathToFileURL(runnerScript).href);
|
|
81
|
-
await runnerModule.runSubagentFromConfigFile(runnerConfigPath);
|
|
82
|
-
process.exit(0);
|
|
83
|
-
})().catch((error) => {
|
|
84
|
-
console.error("Subagent runner hook failed:", error);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
68
|
/**
|
|
90
69
|
* Derive subagent session base directory from parent session file.
|
|
91
|
-
* If parent session is ~/.
|
|
92
|
-
* returns ~/.
|
|
70
|
+
* If parent session is ~/.selesai/agent/sessions/abc123.jsonl,
|
|
71
|
+
* returns ~/.selesai/agent/sessions/abc123/ as the base.
|
|
93
72
|
* Callers add runId to create the actual session root: abc123/{runId}/
|
|
94
73
|
* Falls back to a unique temp directory if no parent session.
|
|
95
74
|
*/
|
|
@@ -286,6 +265,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
286
265
|
baseCwd: "",
|
|
287
266
|
currentSessionId: null,
|
|
288
267
|
subagentInProgress: false,
|
|
268
|
+
subagentSpawns: { sessionId: null, count: 0 },
|
|
289
269
|
asyncJobs: new Map(),
|
|
290
270
|
foregroundRuns: new Map(),
|
|
291
271
|
foregroundControls: new Map(),
|
|
@@ -303,6 +283,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
303
283
|
},
|
|
304
284
|
};
|
|
305
285
|
|
|
286
|
+
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
306
287
|
const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
|
|
307
288
|
pi,
|
|
308
289
|
state,
|
|
@@ -314,6 +295,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
314
295
|
|
|
315
296
|
const runtimeCleanup = () => {
|
|
316
297
|
stopResultWatcher();
|
|
298
|
+
scheduledRunManager.stop();
|
|
299
|
+
supervisorChannel.dispose();
|
|
317
300
|
clearPendingForegroundControlNotices(state);
|
|
318
301
|
if (state.poller) {
|
|
319
302
|
clearInterval(state.poller);
|
|
@@ -322,17 +305,33 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
322
305
|
};
|
|
323
306
|
globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
|
|
324
307
|
|
|
325
|
-
const { ensurePoller, handleStarted, handleComplete, resetJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR);
|
|
308
|
+
const { ensurePoller, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR);
|
|
309
|
+
let executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
310
|
+
const scheduledRunManager = createScheduledRunManager({
|
|
311
|
+
config,
|
|
312
|
+
launch: (params, ctx, signal) => {
|
|
313
|
+
if (!executorExecute) {
|
|
314
|
+
return Promise.resolve({
|
|
315
|
+
content: [{ type: "text", text: "Scheduled subagent launch is unavailable (executor not ready)." }],
|
|
316
|
+
isError: true,
|
|
317
|
+
details: { mode: "management" as const, results: [] },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return executorExecute(randomUUID(), params, signal, undefined, ctx);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
326
323
|
const executor = createSubagentExecutor({
|
|
327
324
|
pi,
|
|
328
325
|
state,
|
|
329
326
|
config,
|
|
330
327
|
asyncByDefault,
|
|
328
|
+
handleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),
|
|
331
329
|
tempArtifactsDir,
|
|
332
330
|
getSubagentSessionRoot,
|
|
333
331
|
expandTilde,
|
|
334
332
|
discoverAgents,
|
|
335
333
|
});
|
|
334
|
+
executorExecute = executor.execute;
|
|
336
335
|
|
|
337
336
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
338
337
|
const details = resolveSlashMessageDetails(message.details);
|
|
@@ -372,7 +371,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
372
371
|
text += `\n ${theme.fg("dim", `⎿ ${line}`)}`;
|
|
373
372
|
}
|
|
374
373
|
if (!options.expanded && trimmedPreview.includes("\n")) {
|
|
375
|
-
|
|
374
|
+
const expandKey = keyText("app.tools.expand");
|
|
375
|
+
text += `\n ${theme.fg("dim", `${expandKey} full notification`)}`;
|
|
376
376
|
}
|
|
377
377
|
if (details.sessionLabel && details.sessionValue) {
|
|
378
378
|
text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;
|
|
@@ -437,6 +437,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
437
437
|
},
|
|
438
438
|
});
|
|
439
439
|
|
|
440
|
+
const rpcBridge = registerSubagentRpcBridge({
|
|
441
|
+
events: pi.events,
|
|
442
|
+
getContext: () => state.lastUiContext,
|
|
443
|
+
execute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),
|
|
444
|
+
});
|
|
445
|
+
|
|
440
446
|
function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
|
|
441
447
|
if (!tasks || tasks.length === 0) return 0;
|
|
442
448
|
return tasks.reduce((total, task) => {
|
|
@@ -448,40 +454,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
448
454
|
const tool: ToolDefinition<typeof SubagentParams, Details> = {
|
|
449
455
|
name: "subagent",
|
|
450
456
|
label: "Subagent",
|
|
451
|
-
description:
|
|
452
|
-
|
|
453
|
-
EXECUTION (use exactly ONE mode):
|
|
454
|
-
• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
|
|
455
|
-
• SINGLE: { agent, task? } - one task; omit task for self-contained agents
|
|
456
|
-
• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
457
|
-
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
458
|
-
• Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
459
|
-
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
460
|
-
|
|
461
|
-
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
462
|
-
• {task} - The original task/request from the user
|
|
463
|
-
• {previous} - Text response from the previous step (empty for first step)
|
|
464
|
-
• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
|
|
465
|
-
|
|
466
|
-
Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
|
|
467
|
-
|
|
468
|
-
MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
469
|
-
• { action: "list" } - discover executable agents/chains
|
|
470
|
-
• { action: "get", agent: "name" } - full detail; packaged agents use dotted runtime names like "package.agent"
|
|
471
|
-
• { action: "models", agent?: "name" } - show the runtime-loaded builtin subagent model mapping, optionally filtered to one builtin
|
|
472
|
-
• { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } }
|
|
473
|
-
• { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge
|
|
474
|
-
• { action: "delete", agent: "code-analysis.custom-agent" }
|
|
475
|
-
• Use chainName for chain operations; packaged chains also use dotted runtime names
|
|
476
|
-
|
|
477
|
-
CONTROL:
|
|
478
|
-
• { action: "status", id: "..." } - inspect an async/background run by id or prefix
|
|
479
|
-
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
|
|
480
|
-
• { action: "resume", id: "...", message: "...", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session
|
|
481
|
-
• { action: "append-step", id: "...", chain: [{agent:"agent-c", task:"Use {previous}"}] } - append one step to the tail of a running async chain
|
|
482
|
-
|
|
483
|
-
DIAGNOSTICS:
|
|
484
|
-
• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`,
|
|
457
|
+
description: buildSubagentToolDescription(config),
|
|
485
458
|
parameters: SubagentParams,
|
|
486
459
|
|
|
487
460
|
execute(id, params, signal, onUpdate, ctx) {
|
|
@@ -498,7 +471,7 @@ DIAGNOSTICS:
|
|
|
498
471
|
}
|
|
499
472
|
const isParallel = (args.tasks?.length ?? 0) > 0;
|
|
500
473
|
const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
|
|
501
|
-
const asyncLabel = args.async === true && args.clarify !== true
|
|
474
|
+
const asyncLabel = args.async === true && args.clarify !== true ? theme.fg("warning", " [async]") : "";
|
|
502
475
|
if (args.chain?.length)
|
|
503
476
|
return new Text(
|
|
504
477
|
`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`,
|
|
@@ -507,7 +480,7 @@ DIAGNOSTICS:
|
|
|
507
480
|
);
|
|
508
481
|
if (isParallel)
|
|
509
482
|
return new Text(
|
|
510
|
-
`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`,
|
|
483
|
+
`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})${asyncLabel}`,
|
|
511
484
|
0,
|
|
512
485
|
0,
|
|
513
486
|
);
|
|
@@ -531,6 +504,27 @@ DIAGNOSTICS:
|
|
|
531
504
|
};
|
|
532
505
|
|
|
533
506
|
pi.registerTool(tool);
|
|
507
|
+
|
|
508
|
+
const waitTool: ToolDefinition<typeof WaitParams, Details> = {
|
|
509
|
+
name: "wait",
|
|
510
|
+
label: "Wait",
|
|
511
|
+
description: `Block until background (async) subagent runs started in this session finish, then return.
|
|
512
|
+
|
|
513
|
+
Use this after launching async subagents when you have no independent work left and must not end your turn — for example inside a skill that has to run to completion, or any non-interactive run (\`pi -p ...\`) where the whole task is a single turn and ending it would abandon the still-running children.
|
|
514
|
+
|
|
515
|
+
• { } — return as soon as the FIRST active run finishes (default). Ideal for a rolling fleet: launch N, wait, spawn a replacement for the one that finished, wait again — keeping N in flight.
|
|
516
|
+
• { all: true } — block until EVERY active run in this session is finished.
|
|
517
|
+
• { id: "..." } — wait for one specific run (id or prefix) to finish.
|
|
518
|
+
• { timeoutMs: 600000 } — stop waiting after N ms (the runs keep going regardless; default 30 min)
|
|
519
|
+
|
|
520
|
+
wait also returns when a run needs attention (a child that went idle or blocked for a decision), not only on completion — so a stuck child never stalls the loop; the summary names the run(s) to inspect/nudge/resume/interrupt. It wakes the instant a completion or control event arrives (subscribed to Pi's event bus, with a poll fallback that reconciles crashed runners), keeps the turn alive for normal notification delivery, and resolves early if the turn is aborted.`,
|
|
521
|
+
parameters: WaitParams,
|
|
522
|
+
execute(_id, params, signal, _onUpdate, _ctx) {
|
|
523
|
+
return waitForSubagents(params, signal, { state, events: pi.events });
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
pi.registerTool(waitTool);
|
|
527
|
+
|
|
534
528
|
registerSlashCommands(pi, state);
|
|
535
529
|
|
|
536
530
|
const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes";
|
|
@@ -546,7 +540,7 @@ DIAGNOSTICS:
|
|
|
546
540
|
}
|
|
547
541
|
}
|
|
548
542
|
}
|
|
549
|
-
registerSubagentNotify(pi);
|
|
543
|
+
registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
550
544
|
|
|
551
545
|
const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
|
|
552
546
|
const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
|
|
@@ -563,6 +557,7 @@ DIAGNOSTICS:
|
|
|
563
557
|
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),
|
|
564
558
|
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),
|
|
565
559
|
pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
|
|
560
|
+
rpcBridge.dispose,
|
|
566
561
|
];
|
|
567
562
|
globalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;
|
|
568
563
|
|
|
@@ -591,6 +586,7 @@ DIAGNOSTICS:
|
|
|
591
586
|
const resetSessionState = (ctx: ExtensionContext) => {
|
|
592
587
|
state.baseCwd = ctx.cwd;
|
|
593
588
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
589
|
+
state.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };
|
|
594
590
|
// Set SELESAI_SUBAGENT_PARENT_SESSION for permission-system forwarding.
|
|
595
591
|
// Only set in the root session (the interactive UI session), not in
|
|
596
592
|
// child subagent processes — children inherit the parent's value
|
|
@@ -606,12 +602,16 @@ DIAGNOSTICS:
|
|
|
606
602
|
cleanupSessionArtifacts(ctx);
|
|
607
603
|
clearPendingForegroundControlNotices(state);
|
|
608
604
|
resetJobs(ctx);
|
|
605
|
+
restoreActiveJobs(ctx);
|
|
606
|
+
scheduledRunManager.bindSession(ctx);
|
|
609
607
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
610
608
|
primeExistingResults();
|
|
611
609
|
};
|
|
612
610
|
|
|
613
611
|
pi.on("session_start", (_event, ctx) => {
|
|
614
612
|
resetSessionState(ctx);
|
|
613
|
+
rpcBridge.emitReady(ctx);
|
|
614
|
+
supervisorChannel.start();
|
|
615
615
|
});
|
|
616
616
|
|
|
617
617
|
pi.on("session_shutdown", () => {
|
|
@@ -627,6 +627,7 @@ DIAGNOSTICS:
|
|
|
627
627
|
delete globalStore[eventUnsubscribeStoreKey];
|
|
628
628
|
}
|
|
629
629
|
stopResultWatcher();
|
|
630
|
+
scheduledRunManager.stop();
|
|
630
631
|
if (state.poller) clearInterval(state.poller);
|
|
631
632
|
state.poller = null;
|
|
632
633
|
clearPendingForegroundControlNotices(state);
|
|
@@ -640,6 +641,7 @@ DIAGNOSTICS:
|
|
|
640
641
|
slashBridge.dispose();
|
|
641
642
|
promptTemplateBridge.cancelAll();
|
|
642
643
|
promptTemplateBridge.dispose();
|
|
644
|
+
supervisorChannel.dispose();
|
|
643
645
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
644
646
|
delete globalStore[runtimeCleanupStoreKey];
|
|
645
647
|
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ExtensionContext } from "@selesai/code";
|
|
4
|
+
import { Compile } from "typebox/compile";
|
|
5
|
+
import { resolveAsyncRunLocation } from "../runs/background/async-resume.ts";
|
|
6
|
+
import { deliverTimeoutRequest } from "../runs/background/control-channel.ts";
|
|
7
|
+
import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.ts";
|
|
8
|
+
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
9
|
+
import { type Details, ASYNC_DIR, RESULTS_DIR } from "../shared/types.ts";
|
|
10
|
+
import { readStatus } from "../shared/utils.ts";
|
|
11
|
+
import { SubagentParams } from "./schemas.ts";
|
|
12
|
+
|
|
13
|
+
export const SUBAGENT_RPC_PROTOCOL_VERSION = 1;
|
|
14
|
+
export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
15
|
+
export const SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready";
|
|
16
|
+
export const SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:";
|
|
17
|
+
|
|
18
|
+
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "interrupt", "stop"] as const;
|
|
19
|
+
export type SubagentRpcMethod = typeof SUBAGENT_RPC_METHODS[number];
|
|
20
|
+
|
|
21
|
+
export interface SubagentRpcRequestEnvelope {
|
|
22
|
+
version: typeof SUBAGENT_RPC_PROTOCOL_VERSION;
|
|
23
|
+
requestId: string;
|
|
24
|
+
method: SubagentRpcMethod;
|
|
25
|
+
params?: unknown;
|
|
26
|
+
source?: {
|
|
27
|
+
extension?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SubagentRpcReplyEnvelope<T = unknown> = {
|
|
33
|
+
version: typeof SUBAGENT_RPC_PROTOCOL_VERSION;
|
|
34
|
+
requestId: string;
|
|
35
|
+
method?: SubagentRpcMethod;
|
|
36
|
+
success: true;
|
|
37
|
+
data: T;
|
|
38
|
+
} | {
|
|
39
|
+
version: typeof SUBAGENT_RPC_PROTOCOL_VERSION;
|
|
40
|
+
requestId: string;
|
|
41
|
+
method?: SubagentRpcMethod;
|
|
42
|
+
success: false;
|
|
43
|
+
error: {
|
|
44
|
+
code: SubagentRpcErrorCode;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type SubagentRpcErrorCode =
|
|
50
|
+
| "invalid_request"
|
|
51
|
+
| "invalid_params"
|
|
52
|
+
| "unsupported_version"
|
|
53
|
+
| "unsupported_method"
|
|
54
|
+
| "no_active_session"
|
|
55
|
+
| "execution_failed"
|
|
56
|
+
| "not_found"
|
|
57
|
+
| "invalid_state";
|
|
58
|
+
|
|
59
|
+
interface EventBus {
|
|
60
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
61
|
+
emit(event: string, data: unknown): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface RegisterSubagentRpcBridgeOptions {
|
|
65
|
+
events: EventBus;
|
|
66
|
+
getContext: () => ExtensionContext | null;
|
|
67
|
+
execute: (
|
|
68
|
+
id: string,
|
|
69
|
+
params: SubagentParamsLike,
|
|
70
|
+
signal: AbortSignal,
|
|
71
|
+
onUpdate: ((result: AgentToolResult<Details>) => void) | undefined,
|
|
72
|
+
ctx: ExtensionContext,
|
|
73
|
+
) => Promise<AgentToolResult<Details>>;
|
|
74
|
+
asyncDirRoot?: string;
|
|
75
|
+
resultsDir?: string;
|
|
76
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
77
|
+
now?: () => number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class SubagentRpcError extends Error {
|
|
81
|
+
readonly code: SubagentRpcErrorCode;
|
|
82
|
+
|
|
83
|
+
constructor(code: SubagentRpcErrorCode, message: string) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = "SubagentRpcError";
|
|
86
|
+
this.code = code;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const subagentParamsValidator = Compile(SubagentParams);
|
|
91
|
+
|
|
92
|
+
export function subagentRpcReplyEvent(requestId: string): string {
|
|
93
|
+
return `${SUBAGENT_RPC_REPLY_EVENT_PREFIX}${requestId}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
97
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function assertRequestId(value: unknown): string {
|
|
101
|
+
if (typeof value !== "string" || value.trim().length === 0 || /[\r\n]/.test(value)) {
|
|
102
|
+
throw new SubagentRpcError("invalid_request", "RPC requestId must be a non-empty string without newlines.");
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function assertRecordParams(params: unknown, method: SubagentRpcMethod): Record<string, unknown> {
|
|
108
|
+
if (params === undefined) return {};
|
|
109
|
+
if (!isRecord(params)) throw new SubagentRpcError("invalid_params", `RPC ${method} params must be an object.`);
|
|
110
|
+
return params;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assertSubagentParams(params: SubagentParamsLike, label: string): void {
|
|
114
|
+
if (subagentParamsValidator.Check(params)) return;
|
|
115
|
+
const messages = [...subagentParamsValidator.Errors(params)]
|
|
116
|
+
.slice(0, 4)
|
|
117
|
+
.map((error) => error.message);
|
|
118
|
+
throw new SubagentRpcError("invalid_params", `${label}: ${messages.join("; ") || "invalid subagent parameters"}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function textFromToolResult(result: AgentToolResult<Details>): string {
|
|
122
|
+
return result.content
|
|
123
|
+
.filter((part): part is { type: "text"; text: string } => part.type === "text")
|
|
124
|
+
.map((part) => part.text)
|
|
125
|
+
.join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function dataFromToolResult(result: AgentToolResult<Details>): { text: string; details?: Details; isError?: boolean } {
|
|
129
|
+
return {
|
|
130
|
+
text: textFromToolResult(result),
|
|
131
|
+
...(result.details ? { details: result.details } : {}),
|
|
132
|
+
...(result.isError ? { isError: true } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function failIfToolError(result: AgentToolResult<Details>): void {
|
|
137
|
+
if (!result.isError) return;
|
|
138
|
+
throw new SubagentRpcError("execution_failed", textFromToolResult(result) || "Subagent RPC execution failed.");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeTargetParams(params: unknown, method: SubagentRpcMethod): Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index"> {
|
|
142
|
+
const input = assertRecordParams(params, method);
|
|
143
|
+
const output: Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index"> = {};
|
|
144
|
+
if (input.id !== undefined) output.id = input.id as string;
|
|
145
|
+
if (input.runId !== undefined) output.runId = input.runId as string;
|
|
146
|
+
if (input.dir !== undefined) output.dir = input.dir as string;
|
|
147
|
+
if (input.index !== undefined) output.index = input.index as number;
|
|
148
|
+
return output;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function sessionData(ctx: ExtensionContext | null): { cwd?: string; sessionId?: string; sessionFile?: string | null } {
|
|
152
|
+
if (!ctx) return {};
|
|
153
|
+
return {
|
|
154
|
+
cwd: ctx.cwd,
|
|
155
|
+
sessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
156
|
+
sessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function pingData(ctx: ExtensionContext | null) {
|
|
161
|
+
return {
|
|
162
|
+
version: SUBAGENT_RPC_PROTOCOL_VERSION,
|
|
163
|
+
methods: [...SUBAGENT_RPC_METHODS],
|
|
164
|
+
capabilities: {
|
|
165
|
+
status: true,
|
|
166
|
+
asyncSpawn: true,
|
|
167
|
+
interrupt: true,
|
|
168
|
+
stop: true,
|
|
169
|
+
},
|
|
170
|
+
events: {
|
|
171
|
+
ready: SUBAGENT_RPC_READY_EVENT,
|
|
172
|
+
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
173
|
+
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
174
|
+
},
|
|
175
|
+
session: sessionData(ctx),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function executeChecked(
|
|
180
|
+
options: RegisterSubagentRpcBridgeOptions,
|
|
181
|
+
ctx: ExtensionContext,
|
|
182
|
+
requestId: string,
|
|
183
|
+
method: SubagentRpcMethod,
|
|
184
|
+
params: SubagentParamsLike,
|
|
185
|
+
): Promise<{ text: string; details?: Details; isError?: boolean }> {
|
|
186
|
+
assertSubagentParams(params, `RPC ${method} params`);
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
const result = await options.execute(`rpc-${method}-${requestId}`, params, controller.signal, undefined, ctx);
|
|
189
|
+
failIfToolError(result);
|
|
190
|
+
return dataFromToolResult(result);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function spawnParams(params: unknown): SubagentParamsLike {
|
|
194
|
+
const input = assertRecordParams(params, "spawn");
|
|
195
|
+
if (input.action !== undefined) {
|
|
196
|
+
throw new SubagentRpcError("invalid_params", "RPC spawn does not accept management/control actions. Use status or interrupt RPC methods instead.");
|
|
197
|
+
}
|
|
198
|
+
if (input.async === false) {
|
|
199
|
+
throw new SubagentRpcError("invalid_params", "RPC spawn only supports detached async launches; omit async or set async: true.");
|
|
200
|
+
}
|
|
201
|
+
if (input.clarify === true) {
|
|
202
|
+
throw new SubagentRpcError("invalid_params", "RPC spawn cannot open the clarify UI; omit clarify or set clarify: false.");
|
|
203
|
+
}
|
|
204
|
+
return { ...(input as SubagentParamsLike), async: true, clarify: false };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function stopAsyncRun(
|
|
208
|
+
params: unknown,
|
|
209
|
+
options: RegisterSubagentRpcBridgeOptions,
|
|
210
|
+
ctx: ExtensionContext,
|
|
211
|
+
): { runId: string; asyncDir: string; previousState: string; state: "stopping"; message: string } {
|
|
212
|
+
const target = normalizeTargetParams(params, "stop");
|
|
213
|
+
assertSubagentParams({ action: "status", ...target }, "RPC stop target params");
|
|
214
|
+
const asyncDirRoot = options.asyncDirRoot ?? ASYNC_DIR;
|
|
215
|
+
const resultsDir = options.resultsDir ?? RESULTS_DIR;
|
|
216
|
+
let location;
|
|
217
|
+
try {
|
|
218
|
+
location = resolveAsyncRunLocation(target, asyncDirRoot, resultsDir);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
throw new SubagentRpcError("invalid_params", error instanceof Error ? error.message : String(error));
|
|
221
|
+
}
|
|
222
|
+
if (!location.asyncDir) {
|
|
223
|
+
throw new SubagentRpcError("not_found", "Async run not found or already completed; stop requires a live async run directory.");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const currentSessionId = ctx.sessionManager.getSessionId();
|
|
227
|
+
const initialStatus = readStatus(location.asyncDir);
|
|
228
|
+
const initialRunId = initialStatus?.runId ?? location.resolvedId ?? path.basename(location.asyncDir);
|
|
229
|
+
if (!initialStatus) throw new SubagentRpcError("not_found", `Status file not found for async run '${initialRunId}'.`);
|
|
230
|
+
if (!currentSessionId || initialStatus.sessionId !== currentSessionId) {
|
|
231
|
+
throw new SubagentRpcError("not_found", `Async run '${initialRunId}' was not found in the active session.`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let status;
|
|
235
|
+
try {
|
|
236
|
+
status = reconcileAsyncRun(location.asyncDir, { resultsDir, kill: options.kill, now: options.now }).status;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw new SubagentRpcError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
239
|
+
}
|
|
240
|
+
const runId = status?.runId ?? initialRunId;
|
|
241
|
+
if (!status) throw new SubagentRpcError("not_found", `Status file not found for async run '${runId}'.`);
|
|
242
|
+
if (status.sessionId !== currentSessionId) {
|
|
243
|
+
throw new SubagentRpcError("not_found", `Async run '${runId}' was not found in the active session.`);
|
|
244
|
+
}
|
|
245
|
+
if (status.state !== "running") {
|
|
246
|
+
throw new SubagentRpcError("invalid_state", `Async run ${runId} is ${status.state}; stop only supports running async runs.`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
deliverTimeoutRequest({
|
|
251
|
+
asyncDir: location.asyncDir,
|
|
252
|
+
pid: status.pid,
|
|
253
|
+
kill: options.kill,
|
|
254
|
+
now: options.now,
|
|
255
|
+
source: "rpc-stop",
|
|
256
|
+
});
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new SubagentRpcError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
runId,
|
|
263
|
+
asyncDir: location.asyncDir,
|
|
264
|
+
previousState: status.state,
|
|
265
|
+
state: "stopping",
|
|
266
|
+
message: `Stop requested for async run ${runId}.`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function handleRequest(
|
|
271
|
+
request: SubagentRpcRequestEnvelope,
|
|
272
|
+
options: RegisterSubagentRpcBridgeOptions,
|
|
273
|
+
): Promise<unknown> {
|
|
274
|
+
const ctx = options.getContext();
|
|
275
|
+
if (request.method === "ping") return pingData(ctx);
|
|
276
|
+
if (!ctx) throw new SubagentRpcError("no_active_session", "No active extension context for subagent RPC.");
|
|
277
|
+
|
|
278
|
+
if (request.method === "spawn") {
|
|
279
|
+
return executeChecked(options, ctx, request.requestId, request.method, spawnParams(request.params));
|
|
280
|
+
}
|
|
281
|
+
if (request.method === "status") {
|
|
282
|
+
return executeChecked(options, ctx, request.requestId, request.method, { action: "status", ...normalizeTargetParams(request.params, "status") });
|
|
283
|
+
}
|
|
284
|
+
if (request.method === "interrupt") {
|
|
285
|
+
return executeChecked(options, ctx, request.requestId, request.method, { action: "interrupt", ...normalizeTargetParams(request.params, "interrupt") });
|
|
286
|
+
}
|
|
287
|
+
if (request.method === "stop") {
|
|
288
|
+
return stopAsyncRun(request.params, options, ctx);
|
|
289
|
+
}
|
|
290
|
+
throw new SubagentRpcError("unsupported_method", `Unsupported subagent RPC method: ${String(request.method)}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function parseRequest(raw: unknown): SubagentRpcRequestEnvelope {
|
|
294
|
+
if (!isRecord(raw)) throw new SubagentRpcError("invalid_request", "Subagent RPC request must be an object.");
|
|
295
|
+
const requestId = assertRequestId(raw.requestId);
|
|
296
|
+
if (raw.version !== SUBAGENT_RPC_PROTOCOL_VERSION) {
|
|
297
|
+
throw new SubagentRpcError("unsupported_version", `Unsupported subagent RPC version: ${String(raw.version)}.`);
|
|
298
|
+
}
|
|
299
|
+
if (typeof raw.method !== "string" || !(SUBAGENT_RPC_METHODS as readonly string[]).includes(raw.method)) {
|
|
300
|
+
throw new SubagentRpcError("unsupported_method", `Unsupported subagent RPC method: ${String(raw.method)}.`);
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
version: SUBAGENT_RPC_PROTOCOL_VERSION,
|
|
304
|
+
requestId,
|
|
305
|
+
method: raw.method as SubagentRpcMethod,
|
|
306
|
+
...(raw.params !== undefined ? { params: raw.params } : {}),
|
|
307
|
+
...(isRecord(raw.source) ? { source: raw.source as SubagentRpcRequestEnvelope["source"] } : {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function safeReplyRequestId(raw: unknown): string {
|
|
312
|
+
if (!isRecord(raw)) return "unknown";
|
|
313
|
+
const requestId = raw.requestId;
|
|
314
|
+
return typeof requestId === "string" && requestId.trim().length > 0 && !/[\r\n]/.test(requestId)
|
|
315
|
+
? requestId
|
|
316
|
+
: "unknown";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function errorReply(raw: unknown, error: unknown): SubagentRpcReplyEnvelope {
|
|
320
|
+
const requestId = safeReplyRequestId(raw);
|
|
321
|
+
const method = isRecord(raw) && typeof raw.method === "string" && (SUBAGENT_RPC_METHODS as readonly string[]).includes(raw.method)
|
|
322
|
+
? raw.method as SubagentRpcMethod
|
|
323
|
+
: undefined;
|
|
324
|
+
const rpcError = error instanceof SubagentRpcError
|
|
325
|
+
? error
|
|
326
|
+
: new SubagentRpcError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
327
|
+
return {
|
|
328
|
+
version: SUBAGENT_RPC_PROTOCOL_VERSION,
|
|
329
|
+
requestId,
|
|
330
|
+
...(method ? { method } : {}),
|
|
331
|
+
success: false,
|
|
332
|
+
error: {
|
|
333
|
+
code: rpcError.code,
|
|
334
|
+
message: rpcError.message,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function registerSubagentRpcBridge(options: RegisterSubagentRpcBridgeOptions): {
|
|
340
|
+
emitReady: (ctx?: ExtensionContext | null) => void;
|
|
341
|
+
dispose: () => void;
|
|
342
|
+
} {
|
|
343
|
+
const unsubscribe = options.events.on(SUBAGENT_RPC_REQUEST_EVENT, async (raw) => {
|
|
344
|
+
let request: SubagentRpcRequestEnvelope | undefined;
|
|
345
|
+
try {
|
|
346
|
+
request = parseRequest(raw);
|
|
347
|
+
const data = await handleRequest(request, options);
|
|
348
|
+
options.events.emit(subagentRpcReplyEvent(request.requestId), {
|
|
349
|
+
version: SUBAGENT_RPC_PROTOCOL_VERSION,
|
|
350
|
+
requestId: request.requestId,
|
|
351
|
+
method: request.method,
|
|
352
|
+
success: true,
|
|
353
|
+
data,
|
|
354
|
+
} satisfies SubagentRpcReplyEnvelope);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
const reply = errorReply(request ?? raw, error);
|
|
357
|
+
options.events.emit(subagentRpcReplyEvent(reply.requestId), reply);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
emitReady: (ctx) => {
|
|
363
|
+
options.events.emit(SUBAGENT_RPC_READY_EVENT, pingData(ctx ?? options.getContext()));
|
|
364
|
+
},
|
|
365
|
+
dispose: () => {
|
|
366
|
+
if (typeof unsubscribe === "function") unsubscribe();
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|