@selesai/code 0.5.20 → 0.5.21
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/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +1 -1
|
@@ -5,8 +5,8 @@ import { createFileCoalescer } from "../../shared/file-coalescer.ts";
|
|
|
5
5
|
import {
|
|
6
6
|
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
7
7
|
type IntercomEventBus,
|
|
8
|
-
type Usage,
|
|
9
8
|
type NestedRunSummary,
|
|
9
|
+
type ParallelHandoffReference,
|
|
10
10
|
type SubagentResultIntercomChild,
|
|
11
11
|
type SubagentState,
|
|
12
12
|
} from "../../shared/types.ts";
|
|
@@ -18,9 +18,12 @@ import {
|
|
|
18
18
|
resolveSubagentResultStatus,
|
|
19
19
|
} from "../../intercom/result-intercom.ts";
|
|
20
20
|
import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-events.ts";
|
|
21
|
+
import { resolveWatchPath } from "../../shared/utils.ts";
|
|
22
|
+
import type { CompletionNotifier, CompletionNotification } from "./notify.ts";
|
|
21
23
|
|
|
22
24
|
const WATCHER_RESTART_DELAY_MS = 3000;
|
|
23
25
|
const POLL_INTERVAL_MS = 3000;
|
|
26
|
+
const RETRY_DELAY_MS = 100;
|
|
24
27
|
|
|
25
28
|
type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "realpathSync" | "watch">;
|
|
26
29
|
|
|
@@ -34,6 +37,7 @@ type ResultWatcherTimers = {
|
|
|
34
37
|
type ResultWatcherDeps = {
|
|
35
38
|
fs?: ResultWatcherFs;
|
|
36
39
|
timers?: ResultWatcherTimers;
|
|
40
|
+
notifier?: Pick<CompletionNotifier, "deliver">;
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
type ResultFileChild = {
|
|
@@ -41,28 +45,22 @@ type ResultFileChild = {
|
|
|
41
45
|
output?: string;
|
|
42
46
|
error?: string;
|
|
43
47
|
success?: boolean;
|
|
48
|
+
state?: string;
|
|
49
|
+
stopped?: boolean;
|
|
44
50
|
sessionFile?: string;
|
|
45
51
|
artifactPaths?: { outputPath?: string };
|
|
46
52
|
intercomTarget?: string;
|
|
47
53
|
children?: unknown;
|
|
48
54
|
};
|
|
49
55
|
|
|
50
|
-
type ResultFileData = {
|
|
51
|
-
id?: string;
|
|
56
|
+
type ResultFileData = CompletionNotification & {
|
|
52
57
|
runId?: string;
|
|
53
|
-
agent?: string;
|
|
54
|
-
success?: boolean;
|
|
55
|
-
state?: string;
|
|
56
58
|
mode?: string;
|
|
57
|
-
summary?: string;
|
|
58
59
|
results?: ResultFileChild[];
|
|
59
60
|
nestedChildren?: unknown;
|
|
60
|
-
sessionId?: string;
|
|
61
|
-
cwd?: string;
|
|
62
|
-
sessionFile?: string;
|
|
63
61
|
asyncDir?: string;
|
|
64
62
|
intercomTarget?: string;
|
|
65
|
-
|
|
63
|
+
parallelHandoff?: ParallelHandoffReference;
|
|
66
64
|
};
|
|
67
65
|
|
|
68
66
|
function sanitizeNestedResultChildren(value: unknown, resultPath: string, label: string): NestedRunSummary[] | undefined {
|
|
@@ -78,29 +76,24 @@ function sanitizeNestedResultChildren(value: unknown, resultPath: string, label:
|
|
|
78
76
|
return children.length ? children : undefined;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
function
|
|
82
|
-
return typeof error === "object" && error !== null && "code" in error
|
|
83
|
-
? (error as NodeJS.ErrnoException).code
|
|
84
|
-
: undefined;
|
|
79
|
+
function errorCode(error: unknown): string | undefined {
|
|
80
|
+
return typeof error === "object" && error !== null && "code" in error ? (error as NodeJS.ErrnoException).code : undefined;
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
function
|
|
88
|
-
return
|
|
83
|
+
function isNotFound(error: unknown): boolean {
|
|
84
|
+
return errorCode(error) === "ENOENT";
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
function
|
|
92
|
-
const code =
|
|
87
|
+
function shouldPoll(error: unknown): boolean {
|
|
88
|
+
const code = errorCode(error);
|
|
93
89
|
return code === "EMFILE" || code === "ENOSPC";
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Watches persisted async results for the session currently owned by this
|
|
94
|
+
* runtime. `stopResultWatcher()` revokes ownership before closing resources,
|
|
95
|
+
* so old callbacks can never emit or delete after reload/session replacement.
|
|
96
|
+
*/
|
|
104
97
|
export function createResultWatcher(
|
|
105
98
|
pi: { events: IntercomEventBus },
|
|
106
99
|
state: SubagentState,
|
|
@@ -109,18 +102,41 @@ export function createResultWatcher(
|
|
|
109
102
|
deps: ResultWatcherDeps = {},
|
|
110
103
|
): {
|
|
111
104
|
startResultWatcher: () => void;
|
|
112
|
-
primeExistingResults: () => void;
|
|
105
|
+
primeExistingResults: (options?: { triggerTurn?: boolean }) => void;
|
|
113
106
|
stopResultWatcher: () => void;
|
|
114
107
|
} {
|
|
115
108
|
const fsApi = deps.fs ?? fs;
|
|
116
109
|
const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval };
|
|
110
|
+
const notifier = deps.notifier ?? { deliver: async () => true };
|
|
111
|
+
const pendingTriggerTurn = new Map<string, boolean>();
|
|
112
|
+
const processing = new Set<string>();
|
|
113
|
+
let deliveryActive = true;
|
|
114
|
+
let deliveryEpoch = 0;
|
|
115
|
+
// The sole in-memory ownership lease. It is acquired for one active session
|
|
116
|
+
// and revoked before the watcher, queues, or callbacks are torn down.
|
|
117
|
+
let activeSessionId: string | null = null;
|
|
117
118
|
|
|
118
|
-
const
|
|
119
|
+
const ownsSession = (sessionId: string, epoch: number) => {
|
|
120
|
+
if (!deliveryActive || epoch !== deliveryEpoch) return false;
|
|
121
|
+
if (!activeSessionId && state.currentSessionId) activeSessionId = state.currentSessionId;
|
|
122
|
+
return activeSessionId === sessionId && state.currentSessionId === sessionId;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const scheduleResult = (file: string, triggerTurn: boolean, delayMs = 0) => {
|
|
126
|
+
const pendingMode = pendingTriggerTurn.get(file);
|
|
127
|
+
pendingTriggerTurn.set(file, pendingMode === false || !triggerTurn ? false : true);
|
|
128
|
+
state.resultFileCoalescer.schedule(file, delayMs);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleResult = async (file: string, triggerTurn: boolean) => {
|
|
119
132
|
const resultPath = path.join(resultsDir, file);
|
|
120
|
-
if (!fsApi.existsSync(resultPath)) return;
|
|
133
|
+
if (processing.has(file) || !fsApi.existsSync(resultPath)) return;
|
|
134
|
+
processing.add(file);
|
|
121
135
|
try {
|
|
122
136
|
const data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8")) as ResultFileData;
|
|
123
|
-
if (typeof data.sessionId !== "string" || data.sessionId
|
|
137
|
+
if (typeof data.sessionId !== "string" || !data.sessionId) return;
|
|
138
|
+
const epoch = deliveryEpoch;
|
|
139
|
+
if (!ownsSession(data.sessionId, epoch)) return;
|
|
124
140
|
|
|
125
141
|
const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, "");
|
|
126
142
|
const hasExplicitNestedChildren = data.nestedChildren !== undefined;
|
|
@@ -130,24 +146,32 @@ export function createResultWatcher(
|
|
|
130
146
|
nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
|
|
131
147
|
} catch (error) {
|
|
132
148
|
console.error(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
|
|
149
|
+
scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
|
|
133
150
|
return;
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
|
-
|
|
153
|
+
|
|
137
154
|
const completionKey = buildCompletionKey(data, `result:${file}`);
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
const lastSeenAt = state.completionSeen.get(completionKey);
|
|
156
|
+
if (lastSeenAt !== undefined && Date.now() - lastSeenAt > completionTtlMs) {
|
|
157
|
+
state.completionSeen.delete(completionKey);
|
|
158
|
+
} else if (lastSeenAt !== undefined) {
|
|
159
|
+
if (!ownsSession(data.sessionId, epoch) || !fsApi.existsSync(resultPath)) return;
|
|
160
|
+
try {
|
|
161
|
+
fsApi.unlinkSync(resultPath);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (!isNotFound(error)) {
|
|
164
|
+
console.error(`Failed to remove delivered subagent result '${resultPath}'; will retry:`, error);
|
|
165
|
+
scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
140
168
|
return;
|
|
141
169
|
}
|
|
142
170
|
|
|
143
171
|
const hasResultChildren = Array.isArray(data.results) && data.results.length > 0;
|
|
144
|
-
const resultChildren = hasResultChildren
|
|
172
|
+
const resultChildren: ResultFileChild[] = hasResultChildren
|
|
145
173
|
? data.results!
|
|
146
|
-
: [{
|
|
147
|
-
agent: data.agent,
|
|
148
|
-
output: data.summary,
|
|
149
|
-
success: data.success,
|
|
150
|
-
}];
|
|
174
|
+
: [{ agent: data.agent ?? undefined, output: data.summary, success: data.success }];
|
|
151
175
|
const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
|
|
152
176
|
const baseOutput = result.output ?? data.summary;
|
|
153
177
|
const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
|
|
@@ -157,12 +181,16 @@ export function createResultWatcher(
|
|
|
157
181
|
: output;
|
|
158
182
|
const sessionPath = result.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : undefined);
|
|
159
183
|
const childNestedChildren = sanitizeNestedResultChildren(result.children, resultPath, `results[${index}].children`);
|
|
184
|
+
const childState = result.state === "paused" || result.state === "stopped"
|
|
185
|
+
? result.state
|
|
186
|
+
: result.stopped === true
|
|
187
|
+
? "stopped"
|
|
188
|
+
: data.state === "paused" || (!hasResultChildren && (data.state === "stopped" || typeof result.success !== "boolean"))
|
|
189
|
+
? data.state
|
|
190
|
+
: undefined;
|
|
160
191
|
return {
|
|
161
192
|
agent: result.agent ?? data.agent ?? `step-${index + 1}`,
|
|
162
|
-
status: resolveSubagentResultStatus({
|
|
163
|
-
success: result.success,
|
|
164
|
-
state: data.state === "paused" || typeof result.success !== "boolean" ? data.state : undefined,
|
|
165
|
-
}),
|
|
193
|
+
status: resolveSubagentResultStatus({ success: result.success, state: childState }),
|
|
166
194
|
summary,
|
|
167
195
|
index,
|
|
168
196
|
artifactPath: result.artifactPaths?.outputPath,
|
|
@@ -173,11 +201,11 @@ export function createResultWatcher(
|
|
|
173
201
|
}), nestedChildren);
|
|
174
202
|
|
|
175
203
|
const intercomTarget = data.intercomTarget?.trim();
|
|
176
|
-
if (intercomTarget) {
|
|
204
|
+
if (intercomTarget && triggerTurn) {
|
|
177
205
|
const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
|
|
178
206
|
? data.mode
|
|
179
207
|
: resultChildren.length > 1 ? "chain" : "single";
|
|
180
|
-
const
|
|
208
|
+
const delivered = await deliverSubagentResultIntercomEvent(pi.events, buildSubagentResultIntercomPayload({
|
|
181
209
|
to: intercomTarget,
|
|
182
210
|
runId,
|
|
183
211
|
mode,
|
|
@@ -185,20 +213,45 @@ export function createResultWatcher(
|
|
|
185
213
|
children: normalizedChildren,
|
|
186
214
|
asyncId: data.id,
|
|
187
215
|
asyncDir: data.asyncDir,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!
|
|
191
|
-
|
|
192
|
-
}
|
|
216
|
+
...(data.parallelHandoff ? { parallelHandoff: data.parallelHandoff } : {}),
|
|
217
|
+
}));
|
|
218
|
+
if (!ownsSession(data.sessionId, epoch)) return;
|
|
219
|
+
if (!delivered) console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`);
|
|
193
220
|
}
|
|
194
221
|
|
|
195
|
-
|
|
222
|
+
const accepted = await notifier.deliver({
|
|
196
223
|
...data,
|
|
224
|
+
id: data.id ?? runId,
|
|
197
225
|
runId,
|
|
226
|
+
triggerTurn,
|
|
198
227
|
...(nestedChildren?.length ? { nestedChildren } : {}),
|
|
199
228
|
...(Array.isArray(data.results) ? {
|
|
200
|
-
results: hasResultChildren
|
|
201
|
-
|
|
229
|
+
results: hasResultChildren ? normalizedChildren.map((child, index) => ({
|
|
230
|
+
...data.results![index],
|
|
231
|
+
agent: child.agent,
|
|
232
|
+
status: child.status,
|
|
233
|
+
summary: child.summary,
|
|
234
|
+
index: child.index,
|
|
235
|
+
artifactPath: child.artifactPath,
|
|
236
|
+
sessionPath: child.sessionPath,
|
|
237
|
+
children: child.children,
|
|
238
|
+
})) : [],
|
|
239
|
+
} : {}),
|
|
240
|
+
});
|
|
241
|
+
if (!ownsSession(data.sessionId, epoch)) return;
|
|
242
|
+
if (!accepted) {
|
|
243
|
+
scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
markSeenWithTtl(state.completionSeen, completionKey, Date.now(), completionTtlMs);
|
|
247
|
+
try {
|
|
248
|
+
pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
|
|
249
|
+
...data,
|
|
250
|
+
runId,
|
|
251
|
+
triggerTurn,
|
|
252
|
+
...(nestedChildren?.length ? { nestedChildren } : {}),
|
|
253
|
+
...(Array.isArray(data.results) ? {
|
|
254
|
+
results: hasResultChildren ? normalizedChildren.map((child, index) => ({
|
|
202
255
|
...data.results![index],
|
|
203
256
|
agent: child.agent,
|
|
204
257
|
status: child.status,
|
|
@@ -207,40 +260,50 @@ export function createResultWatcher(
|
|
|
207
260
|
artifactPath: child.artifactPath,
|
|
208
261
|
sessionPath: child.sessionPath,
|
|
209
262
|
children: child.children,
|
|
210
|
-
}))
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
|
|
263
|
+
})) : [],
|
|
264
|
+
} : {}),
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error(`Completion observer failed for '${resultPath}':`, error);
|
|
268
|
+
}
|
|
269
|
+
if (!ownsSession(data.sessionId, epoch) || !fsApi.existsSync(resultPath)) return;
|
|
270
|
+
try {
|
|
271
|
+
fsApi.unlinkSync(resultPath);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!isNotFound(error)) {
|
|
274
|
+
console.error(`Failed to remove delivered subagent result '${resultPath}'; will retry:`, error);
|
|
275
|
+
scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
215
278
|
} catch (error) {
|
|
216
|
-
if (
|
|
217
|
-
|
|
279
|
+
if (!isNotFound(error)) console.error(`Failed to process subagent result file '${resultPath}':`, error);
|
|
280
|
+
} finally {
|
|
281
|
+
processing.delete(file);
|
|
218
282
|
}
|
|
219
283
|
};
|
|
220
284
|
|
|
221
285
|
state.resultFileCoalescer = createFileCoalescer((file) => {
|
|
222
|
-
|
|
286
|
+
const triggerTurn = pendingTriggerTurn.get(file) !== false;
|
|
287
|
+
pendingTriggerTurn.delete(file);
|
|
288
|
+
void handleResult(file, triggerTurn);
|
|
223
289
|
}, 50);
|
|
224
290
|
|
|
225
|
-
const primeExistingResults = () => {
|
|
291
|
+
const primeExistingResults = (options: { triggerTurn?: boolean } = {}) => {
|
|
226
292
|
try {
|
|
293
|
+
const triggerTurn = options.triggerTurn !== false;
|
|
227
294
|
fsApi.readdirSync(resultsDir)
|
|
228
295
|
.filter((f) => f.endsWith(".json"))
|
|
229
|
-
.forEach((file) =>
|
|
296
|
+
.forEach((file) => scheduleResult(file, triggerTurn));
|
|
230
297
|
} catch (error) {
|
|
231
|
-
if (
|
|
232
|
-
console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
|
|
298
|
+
if (!isNotFound(error)) console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
|
|
233
299
|
}
|
|
234
300
|
};
|
|
235
301
|
|
|
236
|
-
const
|
|
302
|
+
const startPolling = (reason: unknown) => {
|
|
237
303
|
state.watcher?.close();
|
|
238
304
|
state.watcher = null;
|
|
239
305
|
if (state.watcherRestartTimer) return;
|
|
240
|
-
|
|
241
|
-
console.error(
|
|
242
|
-
`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`,
|
|
243
|
-
);
|
|
306
|
+
console.error(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${errorCode(reason) ?? "unknown error"}).`);
|
|
244
307
|
primeExistingResults();
|
|
245
308
|
state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS);
|
|
246
309
|
state.watcherRestartTimer.unref?.();
|
|
@@ -254,10 +317,7 @@ export function createResultWatcher(
|
|
|
254
317
|
fsApi.mkdirSync(resultsDir, { recursive: true });
|
|
255
318
|
startResultWatcher();
|
|
256
319
|
} catch (error) {
|
|
257
|
-
if (
|
|
258
|
-
startPollingFallback(error);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
320
|
+
if (shouldPoll(error)) return startPolling(error);
|
|
261
321
|
console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error);
|
|
262
322
|
scheduleRestart();
|
|
263
323
|
}
|
|
@@ -267,24 +327,23 @@ export function createResultWatcher(
|
|
|
267
327
|
|
|
268
328
|
const startResultWatcher = () => {
|
|
269
329
|
if (state.watcher) return;
|
|
330
|
+
activeSessionId = state.currentSessionId;
|
|
331
|
+
deliveryActive = true;
|
|
332
|
+
deliveryEpoch += 1;
|
|
270
333
|
if (state.watcherRestartTimer) {
|
|
271
334
|
timers.clearTimeout(state.watcherRestartTimer);
|
|
272
335
|
timers.clearInterval(state.watcherRestartTimer);
|
|
273
336
|
state.watcherRestartTimer = null;
|
|
274
337
|
}
|
|
275
338
|
try {
|
|
276
|
-
const watchDir =
|
|
277
|
-
state.watcher = fsApi.watch(watchDir, (
|
|
278
|
-
if (
|
|
339
|
+
const watchDir = resolveWatchPath(resultsDir, fsApi.realpathSync.native);
|
|
340
|
+
state.watcher = fsApi.watch(watchDir, (event, file) => {
|
|
341
|
+
if (event !== "rename" || !file) return;
|
|
279
342
|
const fileName = file.toString();
|
|
280
|
-
if (
|
|
281
|
-
state.resultFileCoalescer.schedule(fileName);
|
|
343
|
+
if (fileName.endsWith(".json")) scheduleResult(fileName, true);
|
|
282
344
|
});
|
|
283
345
|
state.watcher.on("error", (error) => {
|
|
284
|
-
if (
|
|
285
|
-
startPollingFallback(error);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
346
|
+
if (shouldPoll(error)) return startPolling(error);
|
|
288
347
|
console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
|
|
289
348
|
state.watcher?.close();
|
|
290
349
|
state.watcher = null;
|
|
@@ -292,10 +351,7 @@ export function createResultWatcher(
|
|
|
292
351
|
});
|
|
293
352
|
state.watcher.unref?.();
|
|
294
353
|
} catch (error) {
|
|
295
|
-
if (
|
|
296
|
-
startPollingFallback(error);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
354
|
+
if (shouldPoll(error)) return startPolling(error);
|
|
299
355
|
console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error);
|
|
300
356
|
state.watcher = null;
|
|
301
357
|
scheduleRestart();
|
|
@@ -303,6 +359,9 @@ export function createResultWatcher(
|
|
|
303
359
|
};
|
|
304
360
|
|
|
305
361
|
const stopResultWatcher = () => {
|
|
362
|
+
deliveryActive = false;
|
|
363
|
+
activeSessionId = null;
|
|
364
|
+
deliveryEpoch += 1;
|
|
306
365
|
state.watcher?.close();
|
|
307
366
|
state.watcher = null;
|
|
308
367
|
if (state.watcherRestartTimer) {
|
|
@@ -311,6 +370,8 @@ export function createResultWatcher(
|
|
|
311
370
|
}
|
|
312
371
|
state.watcherRestartTimer = null;
|
|
313
372
|
state.resultFileCoalescer.clear();
|
|
373
|
+
pendingTriggerTurn.clear();
|
|
374
|
+
processing.clear();
|
|
314
375
|
};
|
|
315
376
|
|
|
316
377
|
return { startResultWatcher, primeExistingResults, stopResultWatcher };
|
|
@@ -29,7 +29,19 @@ function exactAsyncLocation(id: string, asyncDirRoot: string, resultsDir: string
|
|
|
29
29
|
|
|
30
30
|
function foregroundIds(state: SubagentState | undefined): string[] {
|
|
31
31
|
if (!state) return [];
|
|
32
|
-
|
|
32
|
+
const remembered = state.currentSessionId
|
|
33
|
+
? [...(state.foregroundRuns?.values() ?? [])]
|
|
34
|
+
.filter((run) => run.sessionId === state.currentSessionId)
|
|
35
|
+
.map((run) => run.runId)
|
|
36
|
+
: [];
|
|
37
|
+
return [...new Set([...state.foregroundControls.keys(), ...remembered])];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasExactForegroundId(state: SubagentState | undefined, id: string): boolean {
|
|
41
|
+
if (!state) return false;
|
|
42
|
+
if (state.foregroundControls.has(id)) return true;
|
|
43
|
+
const remembered = state.foregroundRuns?.get(id);
|
|
44
|
+
return Boolean(remembered && state.currentSessionId && remembered.sessionId === state.currentSessionId);
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolutionScope | undefined {
|
|
@@ -58,7 +70,7 @@ export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps
|
|
|
58
70
|
const resultsDir = deps.resultsDir ?? RESULTS_DIR;
|
|
59
71
|
|
|
60
72
|
const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
|
|
61
|
-
if (deps.state
|
|
73
|
+
if (hasExactForegroundId(deps.state, id)) return { kind: "foreground", id };
|
|
62
74
|
const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
|
|
63
75
|
if (exactAsync) return { kind: "async", id, location: exactAsync };
|
|
64
76
|
const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
|
|
@@ -6,8 +6,9 @@ import { formatAsyncResultTranscript, formatAsyncRunTranscript, formatNestedRunT
|
|
|
6
6
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
7
7
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
8
8
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
9
|
-
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SubagentState } from "../../shared/types.ts";
|
|
9
|
+
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
|
|
10
10
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
11
|
+
import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
|
|
11
12
|
import { resolveAsyncRunLocation } from "./async-resume.ts";
|
|
12
13
|
import { resolveSubagentRunId } from "./run-id-resolver.ts";
|
|
13
14
|
import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
|
|
@@ -38,7 +39,8 @@ function hasExistingSessionFile(value: unknown): value is string {
|
|
|
38
39
|
return typeof value === "string" && fs.existsSync(value);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown): string {
|
|
42
|
+
function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown, options: { stopped?: boolean } = {}): string {
|
|
43
|
+
if (options.stopped) return "Resume: unavailable; stopped runs are not resumable. Start a new run instead.";
|
|
42
44
|
const knownChildren = children
|
|
43
45
|
.map((child, index) => ({ child, index }))
|
|
44
46
|
.filter(({ child }) => typeof child.agent === "string");
|
|
@@ -73,15 +75,15 @@ function nestedRunDisplayName(run: NestedRunSummary): string {
|
|
|
73
75
|
return run.id;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
function formatSteeringSummary(input: {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
return
|
|
78
|
+
function formatSteeringSummary(input: { steering?: SteeringStatus }): string | undefined {
|
|
79
|
+
const steering = input.steering;
|
|
80
|
+
if (!steering || steering.requested === 0) return undefined;
|
|
81
|
+
const lateAcknowledgments = steering.recent.reduce((count, request) => count + request.targets.filter((target) => target.lateDeliveredAt !== undefined).length, 0);
|
|
82
|
+
return `${steering.requested} requested, ${steering.scheduled} scheduled, ${steering.pending} pending, ${steering.delivered} delivered, ${steering.failed} failed, ${steering.recovered} recovered${lateAcknowledgments ? `, ${lateAcknowledgments} late acknowledged` : ""}`;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
function rememberedForegroundChildOutput(child: ForegroundResumeRun["children"][number]): string {
|
|
84
|
-
const outputPath = child.artifactPaths?.outputPath;
|
|
86
|
+
const outputPath = child.artifactPaths?.outputPath ?? child.savedOutputPath;
|
|
85
87
|
if (outputPath && fs.existsSync(outputPath)) {
|
|
86
88
|
try {
|
|
87
89
|
const artifactOutput = fs.readFileSync(outputPath, "utf-8").trim();
|
|
@@ -107,24 +109,29 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
|
|
|
107
109
|
`${child.index + 1}. ${child.agent} ${child.status}`,
|
|
108
110
|
child.exitCode !== undefined ? `exit ${child.exitCode}` : undefined,
|
|
109
111
|
child.detachedReason ? `detached: ${child.detachedReason}` : undefined,
|
|
112
|
+
child.acceptance ? `acceptance: ${child.acceptance.status}` : undefined,
|
|
113
|
+
child.error ? `error: ${child.error}` : undefined,
|
|
110
114
|
output ? `output: ${output.slice(0, 160)}` : undefined,
|
|
111
115
|
].filter(Boolean);
|
|
112
116
|
lines.push(parts.join(", "));
|
|
113
117
|
if (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);
|
|
114
118
|
if (child.transcriptPath) lines.push(` Transcript: ${child.transcriptPath}`);
|
|
115
119
|
if (child.artifactPaths?.outputPath) lines.push(` Output: ${child.artifactPaths.outputPath}`);
|
|
120
|
+
if (child.savedOutputPath && child.savedOutputPath !== child.artifactPaths?.outputPath) lines.push(` Saved output: ${child.savedOutputPath}`);
|
|
121
|
+
if (child.outputSaveError) lines.push(` Output warning: ${child.outputSaveError}`);
|
|
116
122
|
if (child.transcriptError) lines.push(` Transcript warning: ${child.transcriptError}`);
|
|
117
123
|
}
|
|
118
124
|
lines.push("", `Status: subagent({ action: "status", id: "${run.runId}" })`);
|
|
119
125
|
if (run.children.length === 1) lines.push(`Transcript: subagent({ action: "status", id: "${run.runId}", view: "transcript" })`);
|
|
120
126
|
else lines.push(`Transcript: subagent({ action: "status", id: "${run.runId}", index: 0, view: "transcript" })`);
|
|
121
|
-
const
|
|
122
|
-
|
|
127
|
+
const detached = run.children.some((child) => child.status === "detached");
|
|
128
|
+
const resumable = run.children.find((child) => hasExistingSessionFile(child.sessionFile));
|
|
129
|
+
if (detached) {
|
|
130
|
+
lines.push(`Recovery: reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
|
|
131
|
+
} else if (resumable) {
|
|
123
132
|
lines.push(run.children.length === 1
|
|
124
133
|
? `Revive: subagent({ action: "resume", id: "${run.runId}", message: "..." })`
|
|
125
134
|
: `Revive child: subagent({ action: "resume", id: "${run.runId}", index: ${resumable.index}, message: "..." })`);
|
|
126
|
-
} else if (run.children.some((child) => child.status === "detached")) {
|
|
127
|
-
lines.push("Recovery: child detached for intercom coordination; status will show recovered output after the child exits when Pi can observe it.");
|
|
128
135
|
} else {
|
|
129
136
|
lines.push("Resume: unavailable; no child session file was persisted.");
|
|
130
137
|
}
|
|
@@ -147,6 +154,8 @@ function formatRememberedForegroundTranscript(run: ForegroundResumeRun, options:
|
|
|
147
154
|
child.sessionFile ? `Session: ${child.sessionFile}` : undefined,
|
|
148
155
|
child.transcriptPath ? `Transcript: ${child.transcriptPath}` : undefined,
|
|
149
156
|
child.artifactPaths?.outputPath ? `Output: ${child.artifactPaths.outputPath}` : undefined,
|
|
157
|
+
child.savedOutputPath && child.savedOutputPath !== child.artifactPaths?.outputPath ? `Saved output: ${child.savedOutputPath}` : undefined,
|
|
158
|
+
child.outputSaveError ? `Output warning: ${child.outputSaveError}` : undefined,
|
|
150
159
|
].filter((line): line is string => Boolean(line));
|
|
151
160
|
lines.push("Result transcript tail:");
|
|
152
161
|
if (outputLines.length === 0) lines.push(" (no recovered final output available yet)");
|
|
@@ -343,10 +352,15 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
343
352
|
const updated = status.lastUpdate ? new Date(status.lastUpdate).toISOString() : "n/a";
|
|
344
353
|
const statusActivityText = status.state === "running" ? formatActivityLabel(status.lastActivityAt, status.activityState) : undefined;
|
|
345
354
|
const steeringText = formatSteeringSummary(status);
|
|
355
|
+
const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
|
|
356
|
+
?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
|
|
346
357
|
|
|
347
358
|
const lines = [
|
|
348
359
|
`Run: ${status.runId}`,
|
|
349
360
|
`State: ${status.state}`,
|
|
361
|
+
processTerminal ? `Process terminal: ${processTerminal.state}${processTerminal.reason ? ` (${processTerminal.reason})` : ""}` : undefined,
|
|
362
|
+
status.capabilityCeiling ? `Capability ceiling: ${status.capabilityCeiling.allowedTools === undefined ? "names unrestricted" : status.capabilityCeiling.allowedTools.length === 0 ? "none" : status.capabilityCeiling.allowedTools.join(", ")}\nExtensions denied: ${status.capabilityCeiling.denyExtensions ? "yes" : "no"} (sources: ${status.capabilityCeiling.sources.join(", ")})` : undefined,
|
|
363
|
+
status.capabilityAudit ? `Capability audit: ${status.capabilityAudit.removedTools.length} tools removed, ${status.capabilityAudit.removedExtensionCount} extension entries removed` : undefined,
|
|
350
364
|
status.error ? `Error: ${status.error}` : undefined,
|
|
351
365
|
statusActivityText ? `Activity: ${statusActivityText}` : undefined,
|
|
352
366
|
steeringText ? `Steering: ${steeringText}` : undefined,
|
|
@@ -358,6 +372,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
358
372
|
status.turnBudget ? `Turn budget: ${status.turnBudget.turnCount}/${status.turnBudget.maxTurns}+${status.turnBudget.graceTurns} (${status.turnBudget.outcome})` : undefined,
|
|
359
373
|
`Dir: ${asyncDir}`,
|
|
360
374
|
outputPath ? `Output: ${outputPath}` : undefined,
|
|
375
|
+
status.parallelHandoff ? `Parallel handoff: ${status.parallelHandoff.path}` : undefined,
|
|
361
376
|
reconciliation.message ? `Diagnosis: ${reconciliation.message}` : undefined,
|
|
362
377
|
reconciliation.resultPath && fs.existsSync(reconciliation.resultPath) ? `Result: ${reconciliation.resultPath}` : undefined,
|
|
363
378
|
].filter((line): line is string => Boolean(line));
|
|
@@ -388,19 +403,19 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
388
403
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
389
404
|
if (status.state === "running") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
|
|
390
405
|
if (status.state !== "running") {
|
|
391
|
-
lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile));
|
|
406
|
+
lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
|
|
392
407
|
}
|
|
393
408
|
if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
|
|
394
409
|
if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
|
|
395
410
|
|
|
396
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
|
|
411
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
397
412
|
}
|
|
398
413
|
}
|
|
399
414
|
|
|
400
415
|
if (resultPath) {
|
|
401
416
|
try {
|
|
402
417
|
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
403
|
-
const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; sessionFile?: string; results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }> };
|
|
418
|
+
const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; sessionFile?: string; parallelHandoff?: { path?: string }; results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }> };
|
|
404
419
|
if (params.view === "transcript") {
|
|
405
420
|
try {
|
|
406
421
|
return { content: [{ type: "text", text: formatAsyncResultTranscript(data, resultPath, { index: params.index, lines: params.lines }) }], details: { mode: "single", results: [] } };
|
|
@@ -409,11 +424,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
409
424
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } };
|
|
410
425
|
}
|
|
411
426
|
}
|
|
412
|
-
const status = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
427
|
+
const status = data.state === "stopped" ? "stopped" : data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
413
428
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
414
429
|
const lines = [`Run: ${runId}`, `State: ${status}`, `Result: ${resultPath}`];
|
|
430
|
+
if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
|
|
415
431
|
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
|
|
416
|
-
lines.push(formatResumeGuidance(runId, children, data.sessionFile));
|
|
432
|
+
lines.push(formatResumeGuidance(runId, children, data.sessionFile, { stopped: status === "stopped" }));
|
|
417
433
|
if (data.summary) lines.push("", data.summary);
|
|
418
434
|
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
|
|
419
435
|
} catch (error) {
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
type ExtensionConfig,
|
|
13
13
|
} from "../../shared/types.ts";
|
|
14
14
|
import type { SubagentParamsLike } from "../foreground/subagent-executor.ts";
|
|
15
|
+
import { validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
16
|
+
import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
15
17
|
|
|
16
18
|
export const SCHEDULED_RUNS_DIR = path.join(TEMP_ROOT_DIR, "scheduled-subagent-runs");
|
|
17
19
|
export const SCHEDULED_RUN_ACTIONS = ["schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
|
|
@@ -56,6 +58,7 @@ type ScheduledRunManagerDeps = {
|
|
|
56
58
|
storeRoot?: string;
|
|
57
59
|
now?: () => number;
|
|
58
60
|
randomId?: () => string;
|
|
61
|
+
resolveCapabilityCeiling?: (sessionId: string) => ResolvedSubagentCapabilityCeiling | undefined;
|
|
59
62
|
timers?: ScheduledRunTimers;
|
|
60
63
|
};
|
|
61
64
|
|
|
@@ -228,6 +231,8 @@ function sanitizeScheduledParams(params: SubagentParamsLike): { params?: Subagen
|
|
|
228
231
|
if (params.context === "fork") return { error: "Scheduled subagent runs require fresh context. Forked parent-session context is not safe at fire time." };
|
|
229
232
|
if (params.async === false) return { error: "Scheduled subagent runs are always async; omit async or set async: true." };
|
|
230
233
|
if (params.clarify === true) return { error: "Scheduled subagent runs cannot open clarify UI; omit clarify or set clarify: false." };
|
|
234
|
+
const acceptanceErrors = validateExecutionAcceptance(params);
|
|
235
|
+
if (acceptanceErrors.length > 0) return { error: acceptanceErrors.join(" ") };
|
|
231
236
|
|
|
232
237
|
const {
|
|
233
238
|
action: _action,
|
|
@@ -307,12 +312,15 @@ export class ScheduledRunManager {
|
|
|
307
312
|
const sanitized = sanitizeScheduledParams(params);
|
|
308
313
|
if (sanitized.error) return textResult(sanitized.error, true);
|
|
309
314
|
const scheduleInput = params.schedule!.trim();
|
|
315
|
+
const sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
316
|
+
if (this.deps.resolveCapabilityCeiling?.(sessionId)) {
|
|
317
|
+
return textResult("Cannot schedule a capability-ceiling-restricted run because this store does not yet persist ceilings. Remove the active parent restriction or run it immediately.", true);
|
|
318
|
+
}
|
|
310
319
|
const runAt = parseScheduledRunTime(scheduleInput, this.now());
|
|
311
320
|
const pendingCount = store.list().filter((job) => job.state === "scheduled" || job.state === "running").length;
|
|
312
321
|
const maxPending = resolveMaxPending(this.deps.config);
|
|
313
322
|
if (pendingCount >= maxPending) return textResult(`Scheduled subagent limit reached (${pendingCount}/${maxPending} pending or running). Cancel an existing scheduled run before adding another.`, true);
|
|
314
323
|
const id = this.randomId();
|
|
315
|
-
const sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
316
324
|
const scheduleName = params.scheduleName?.trim();
|
|
317
325
|
const executionParams = sanitized.params!;
|
|
318
326
|
const now = this.now();
|