@selesai/code 0.5.19 → 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-intercom/broker/spawn.test.ts +31 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
- 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 +2 -1
|
@@ -19,6 +19,7 @@ import * as fs from "node:fs";
|
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
21
21
|
import { POLL_INTERVAL_MS } from "../../shared/types.ts";
|
|
22
|
+
import { resolveWatchPath } from "../../shared/utils.ts";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Opportunistic fast-path interrupt signal. On Unix `SIGUSR2` is trapped by the
|
|
@@ -27,7 +28,7 @@ import { POLL_INTERVAL_MS } from "../../shared/types.ts";
|
|
|
27
28
|
*/
|
|
28
29
|
export const INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
29
30
|
|
|
30
|
-
export type ControlChannelFs = Pick<typeof fs, "mkdirSync" | "existsSync" | "rmSync" | "watch" | "readdirSync" | "readFileSync">;
|
|
31
|
+
export type ControlChannelFs = Pick<typeof fs, "mkdirSync" | "existsSync" | "rmSync" | "watch" | "readdirSync" | "readFileSync" | "realpathSync">;
|
|
31
32
|
export type ControlChannelTimers = { setInterval: typeof setInterval; clearInterval: typeof clearInterval };
|
|
32
33
|
type KillFn = (pid: number, signal?: NodeJS.Signals | 0) => unknown;
|
|
33
34
|
|
|
@@ -45,17 +46,49 @@ export interface TimeoutRequest {
|
|
|
45
46
|
reason?: string;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
export interface StopRequest {
|
|
50
|
+
type: "stop";
|
|
51
|
+
ts?: number;
|
|
52
|
+
source?: string;
|
|
53
|
+
reason?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
export interface SteerRequest {
|
|
49
57
|
type: "steer";
|
|
50
58
|
id: string;
|
|
51
59
|
ts: number;
|
|
52
60
|
message: string;
|
|
53
61
|
targetIndex?: number;
|
|
62
|
+
targetIndexes?: number[];
|
|
54
63
|
source?: string;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
export interface SteerCapability {
|
|
67
|
+
type: "steer-capability";
|
|
68
|
+
protocolVersion: 1;
|
|
69
|
+
index: number;
|
|
70
|
+
pid: number;
|
|
71
|
+
readyAt: number;
|
|
72
|
+
supported: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SteerAck {
|
|
76
|
+
type: "steer-ack";
|
|
77
|
+
protocolVersion: 1;
|
|
78
|
+
requestId: string;
|
|
79
|
+
index: number;
|
|
80
|
+
ts: number;
|
|
81
|
+
state: "delivered" | "failed";
|
|
82
|
+
message: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
const STEER_REQUESTS_DIR = "steer-requests";
|
|
58
86
|
const STEER_TARGETS_DIR = "steer-targets";
|
|
87
|
+
const STEER_CAPABILITIES_DIR = "steer-capabilities";
|
|
88
|
+
const STEER_ACKS_DIR = "steer-acks";
|
|
89
|
+
const STEER_INBOX_CLOSED_FILE = "steer-inbox-closed.json";
|
|
90
|
+
const MAX_STEER_MESSAGE_BYTES = 128 * 1024;
|
|
91
|
+
const MAX_STEER_REQUEST_ID_LENGTH = 256;
|
|
59
92
|
|
|
60
93
|
/** Control inbox directory inside an async run dir. */
|
|
61
94
|
export function controlInboxDir(asyncDir: string): string {
|
|
@@ -72,26 +105,118 @@ export function timeoutRequestPath(asyncDir: string): string {
|
|
|
72
105
|
return path.join(controlInboxDir(asyncDir), "timeout.json");
|
|
73
106
|
}
|
|
74
107
|
|
|
108
|
+
/** Path of the portable manual stop request file. */
|
|
109
|
+
export function stopRequestPath(asyncDir: string): string {
|
|
110
|
+
return path.join(controlInboxDir(asyncDir), "stop.json");
|
|
111
|
+
}
|
|
112
|
+
|
|
75
113
|
/** Directory of parent-to-runner steering requests. */
|
|
76
114
|
export function steerRequestsDir(asyncDir: string): string {
|
|
77
115
|
return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
|
|
78
116
|
}
|
|
79
117
|
|
|
118
|
+
export function steerInboxClosedPath(asyncDir: string): string {
|
|
119
|
+
return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function closeSteerInbox(asyncDir: string, state: string): void {
|
|
123
|
+
writeAtomicJson(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
|
|
124
|
+
}
|
|
125
|
+
|
|
80
126
|
/** Per-child inbox consumed by the child prompt runtime inside the Pi process. */
|
|
81
127
|
export function stepSteerInboxDir(asyncDir: string, index: number): string {
|
|
128
|
+
assertChildIndex(index);
|
|
82
129
|
return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));
|
|
83
130
|
}
|
|
84
131
|
|
|
132
|
+
export function steerCapabilitiesDir(asyncDir: string): string {
|
|
133
|
+
return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function steerCapabilityPath(asyncDir: string, index: number): string {
|
|
137
|
+
assertChildIndex(index);
|
|
138
|
+
return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function steerAcksDir(asyncDir: string, index: number): string {
|
|
142
|
+
assertChildIndex(index);
|
|
143
|
+
return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function steerAckFileName(requestId: string): string {
|
|
147
|
+
return `${Buffer.from(requestId).toString("base64url")}.json`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function steerAckPathFromDir(dir: string, requestId: string): string {
|
|
151
|
+
if (!/^[^\s]+$/.test(requestId) || requestId.length > 256) throw new Error("steer acknowledgment requestId is invalid.");
|
|
152
|
+
return path.join(dir, steerAckFileName(requestId));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertChildIndex(index: number): void {
|
|
156
|
+
if (!Number.isInteger(index) || index < 0 || index > 1_000_000) throw new Error("steer child index must be a non-negative integer.");
|
|
157
|
+
}
|
|
158
|
+
|
|
85
159
|
function steerRequestFileName(request: SteerRequest): string {
|
|
86
160
|
return `${String(request.ts).padStart(13, "0")}-${Buffer.from(request.id).toString("base64url")}.json`;
|
|
87
161
|
}
|
|
88
162
|
|
|
163
|
+
function validSteerRequest(request: Partial<SteerRequest>): request is SteerRequest {
|
|
164
|
+
return request.type === "steer"
|
|
165
|
+
&& typeof request.id === "string"
|
|
166
|
+
&& /^[^\s]+$/.test(request.id)
|
|
167
|
+
&& request.id.length <= MAX_STEER_REQUEST_ID_LENGTH
|
|
168
|
+
&& typeof request.ts === "number"
|
|
169
|
+
&& Number.isFinite(request.ts)
|
|
170
|
+
&& request.ts > 0
|
|
171
|
+
&& typeof request.message === "string"
|
|
172
|
+
&& Boolean(request.message.trim())
|
|
173
|
+
&& Buffer.byteLength(request.message, "utf8") <= MAX_STEER_MESSAGE_BYTES
|
|
174
|
+
&& (request.targetIndex === undefined || (Number.isInteger(request.targetIndex) && request.targetIndex >= 0 && request.targetIndex <= 1_000_000))
|
|
175
|
+
&& (request.targetIndexes === undefined || (
|
|
176
|
+
request.targetIndex === undefined
|
|
177
|
+
&& Array.isArray(request.targetIndexes)
|
|
178
|
+
&& request.targetIndexes.length > 0
|
|
179
|
+
&& request.targetIndexes.length <= 1_000
|
|
180
|
+
&& request.targetIndexes.every((index) => Number.isInteger(index) && index >= 0 && index <= 1_000_000)
|
|
181
|
+
&& new Set(request.targetIndexes).size === request.targetIndexes.length
|
|
182
|
+
))
|
|
183
|
+
&& (request.source === undefined || (typeof request.source === "string" && Boolean(request.source.trim()) && request.source.length <= 256));
|
|
184
|
+
}
|
|
185
|
+
|
|
89
186
|
export function writeSteerRequestToDir(dir: string, request: SteerRequest): string {
|
|
187
|
+
if (!validSteerRequest(request)) throw new Error("steer request is malformed or exceeds transport limits.");
|
|
90
188
|
const requestPath = path.join(dir, steerRequestFileName(request));
|
|
91
189
|
writeAtomicJson(requestPath, request);
|
|
92
190
|
return requestPath;
|
|
93
191
|
}
|
|
94
192
|
|
|
193
|
+
export function writeSteerCapabilityAt(filePath: string, capability: Omit<SteerCapability, "type" | "protocolVersion">): string {
|
|
194
|
+
assertChildIndex(capability.index);
|
|
195
|
+
if (!Number.isInteger(capability.pid) || capability.pid <= 0) throw new Error("steer capability pid must be a positive integer.");
|
|
196
|
+
if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0) throw new Error("steer capability readyAt must be a finite timestamp.");
|
|
197
|
+
const record: SteerCapability = { type: "steer-capability", protocolVersion: 1, ...capability };
|
|
198
|
+
writeAtomicJson(filePath, record);
|
|
199
|
+
return filePath;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function writeSteerCapability(asyncDir: string, capability: Omit<SteerCapability, "type" | "protocolVersion">): string {
|
|
203
|
+
return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function writeSteerAckAt(filePath: string, ack: Omit<SteerAck, "type" | "protocolVersion">): string {
|
|
207
|
+
assertChildIndex(ack.index);
|
|
208
|
+
if (!/^[^\s]+$/.test(ack.requestId) || ack.requestId.length > 256) throw new Error("steer acknowledgment requestId is invalid.");
|
|
209
|
+
if (!Number.isFinite(ack.ts) || ack.ts <= 0) throw new Error("steer acknowledgment ts must be a finite timestamp.");
|
|
210
|
+
if (!ack.message.trim() || ack.message.length > 1000) throw new Error("steer acknowledgment message is invalid.");
|
|
211
|
+
const record: SteerAck = { type: "steer-ack", protocolVersion: 1, ...ack, message: ack.message.trim() };
|
|
212
|
+
writeAtomicJson(filePath, record);
|
|
213
|
+
return filePath;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function writeSteerAck(asyncDir: string, ack: Omit<SteerAck, "type" | "protocolVersion">): string {
|
|
217
|
+
return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);
|
|
218
|
+
}
|
|
219
|
+
|
|
95
220
|
/**
|
|
96
221
|
* Parent side: drop a portable interrupt request the runner's inbox watcher will
|
|
97
222
|
* pick up regardless of OS. Written atomically (temp + rename), dir auto-created.
|
|
@@ -118,46 +243,137 @@ export function requestAsyncTimeout(
|
|
|
118
243
|
return requestPath;
|
|
119
244
|
}
|
|
120
245
|
|
|
246
|
+
export function requestAsyncStop(
|
|
247
|
+
asyncDir: string,
|
|
248
|
+
payload: Omit<StopRequest, "type"> = {},
|
|
249
|
+
deps: { now?: () => number } = {},
|
|
250
|
+
): string {
|
|
251
|
+
const requestPath = stopRequestPath(asyncDir);
|
|
252
|
+
const request: StopRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "stop" };
|
|
253
|
+
writeAtomicJson(requestPath, request);
|
|
254
|
+
return requestPath;
|
|
255
|
+
}
|
|
256
|
+
|
|
121
257
|
export function requestAsyncSteer(
|
|
122
258
|
asyncDir: string,
|
|
123
|
-
payload: { message: string; targetIndex?: number; source?: string; id?: string; ts?: number },
|
|
259
|
+
payload: { message: string; targetIndex?: number; targetIndexes?: number[]; source?: string; id?: string; ts?: number },
|
|
124
260
|
deps: { now?: () => number; randomId?: () => string } = {},
|
|
125
261
|
): string {
|
|
126
262
|
const message = payload.message.trim();
|
|
127
263
|
if (!message) throw new Error("steer message must not be empty.");
|
|
128
|
-
if (
|
|
129
|
-
|
|
264
|
+
if (Buffer.byteLength(message, "utf8") > MAX_STEER_MESSAGE_BYTES) throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);
|
|
265
|
+
if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > 1_000_000)) {
|
|
266
|
+
throw new Error("steer targetIndex must be an integer between 0 and 1000000.");
|
|
130
267
|
}
|
|
268
|
+
if (payload.targetIndexes !== undefined && (
|
|
269
|
+
!Array.isArray(payload.targetIndexes)
|
|
270
|
+
|| payload.targetIndex !== undefined
|
|
271
|
+
|| payload.targetIndexes.length === 0
|
|
272
|
+
|| payload.targetIndexes.length > 1_000
|
|
273
|
+
|| payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > 1_000_000)
|
|
274
|
+
|| new Set(payload.targetIndexes).size !== payload.targetIndexes.length
|
|
275
|
+
)) {
|
|
276
|
+
throw new Error("steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.");
|
|
277
|
+
}
|
|
278
|
+
const closedPath = steerInboxClosedPath(asyncDir);
|
|
279
|
+
if (fs.existsSync(closedPath)) throw new Error("Async run no longer accepts steering requests.");
|
|
131
280
|
const request: SteerRequest = {
|
|
132
281
|
type: "steer",
|
|
133
282
|
id: payload.id ?? deps.randomId?.() ?? randomUUID(),
|
|
134
283
|
ts: payload.ts ?? deps.now?.() ?? Date.now(),
|
|
135
284
|
message,
|
|
136
285
|
...(payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {}),
|
|
286
|
+
...(payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {}),
|
|
137
287
|
...(payload.source ? { source: payload.source } : {}),
|
|
138
288
|
};
|
|
139
|
-
|
|
289
|
+
const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
|
|
290
|
+
if (fs.existsSync(closedPath)) {
|
|
291
|
+
fs.rmSync(requestPath, { force: true });
|
|
292
|
+
throw new Error("Async run stopped accepting steering before the request was committed.");
|
|
293
|
+
}
|
|
294
|
+
return requestPath;
|
|
140
295
|
}
|
|
141
296
|
|
|
142
297
|
export function enqueueStepSteer(asyncDir: string, index: number, request: SteerRequest): string {
|
|
143
|
-
|
|
144
|
-
|
|
298
|
+
assertChildIndex(index);
|
|
299
|
+
const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;
|
|
300
|
+
return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...singleTargetRequest, targetIndex: index, type: "steer" });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseSteerCapability(raw: unknown): SteerCapability | undefined {
|
|
304
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
305
|
+
const input = raw as Partial<SteerCapability>;
|
|
306
|
+
if (input.type !== "steer-capability" || input.protocolVersion !== 1) return undefined;
|
|
307
|
+
if (!Number.isInteger(input.index) || input.index < 0 || input.index > 1_000_000) return undefined;
|
|
308
|
+
if (!Number.isInteger(input.pid) || input.pid <= 0 || !Number.isFinite(input.readyAt) || input.readyAt <= 0 || typeof input.supported !== "boolean") return undefined;
|
|
309
|
+
return { type: "steer-capability", protocolVersion: 1, index: input.index, pid: input.pid, readyAt: input.readyAt, supported: input.supported };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function parseSteerAck(raw: unknown): SteerAck | undefined {
|
|
313
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
314
|
+
const input = raw as Partial<SteerAck>;
|
|
315
|
+
if (input.type !== "steer-ack" || input.protocolVersion !== 1 || typeof input.requestId !== "string" || !/^[^\s]+$/.test(input.requestId) || input.requestId.length > 256) return undefined;
|
|
316
|
+
if (!Number.isInteger(input.index) || input.index < 0 || input.index > 1_000_000 || !Number.isFinite(input.ts) || input.ts <= 0) return undefined;
|
|
317
|
+
if (input.state !== "delivered" && input.state !== "failed") return undefined;
|
|
318
|
+
if (typeof input.message !== "string" || !input.message.trim() || input.message.length > 1000) return undefined;
|
|
319
|
+
return { type: "steer-ack", protocolVersion: 1, requestId: input.requestId, index: input.index, ts: input.ts, state: input.state, message: input.message.trim() };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function readSteerCapability(asyncDir: string, index: number): SteerCapability | undefined {
|
|
323
|
+
try {
|
|
324
|
+
return parseSteerCapability(JSON.parse(fs.readFileSync(steerCapabilityPath(asyncDir, index), "utf-8")));
|
|
325
|
+
} catch {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function consumeSteerCapabilities(asyncDir: string, fsImpl: Pick<typeof fs, "existsSync" | "readdirSync" | "readFileSync"> = fs): SteerCapability[] {
|
|
331
|
+
const dir = steerCapabilitiesDir(asyncDir);
|
|
332
|
+
if (!fsImpl.existsSync(dir)) return [];
|
|
333
|
+
const capabilities: SteerCapability[] = [];
|
|
334
|
+
for (const entry of fsImpl.readdirSync(dir).filter((name) => /^\d+\.json$/.test(name)).sort()) {
|
|
335
|
+
try {
|
|
336
|
+
const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), "utf-8")));
|
|
337
|
+
if (capability) capabilities.push(capability);
|
|
338
|
+
} catch {
|
|
339
|
+
// A partially written or malformed capability is ignored until a valid one arrives.
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return capabilities;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function consumeSteerAcks(asyncDir: string, fsImpl: Pick<typeof fs, "existsSync" | "readdirSync" | "readFileSync" | "rmSync"> = fs): SteerAck[] {
|
|
346
|
+
const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);
|
|
347
|
+
if (!fsImpl.existsSync(root)) return [];
|
|
348
|
+
const acks: SteerAck[] = [];
|
|
349
|
+
let indexNames: string[];
|
|
350
|
+
try { indexNames = fsImpl.readdirSync(root).filter((name) => /^\d+$/.test(name)); } catch { return []; }
|
|
351
|
+
for (const indexName of indexNames) {
|
|
352
|
+
const dir = path.join(root, indexName);
|
|
353
|
+
let entries: string[];
|
|
354
|
+
try { entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort(); } catch { continue; }
|
|
355
|
+
for (const entry of entries) {
|
|
356
|
+
const target = path.join(dir, entry);
|
|
357
|
+
let ack: SteerAck | undefined;
|
|
358
|
+
try { ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8"))); } catch { ack = undefined; }
|
|
359
|
+
try { fsImpl.rmSync(target, { force: true }); } catch { continue; }
|
|
360
|
+
if (ack) acks.push(ack);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return acks;
|
|
145
364
|
}
|
|
146
365
|
|
|
147
366
|
function parseSteerRequest(raw: unknown): SteerRequest | undefined {
|
|
148
367
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
149
368
|
const input = raw as Partial<SteerRequest>;
|
|
150
|
-
if (input
|
|
151
|
-
if (typeof input.id !== "string" || !input.id.trim()) return undefined;
|
|
152
|
-
if (typeof input.ts !== "number" || !Number.isFinite(input.ts)) return undefined;
|
|
153
|
-
if (typeof input.message !== "string" || !input.message.trim()) return undefined;
|
|
154
|
-
if (input.targetIndex !== undefined && (!Number.isInteger(input.targetIndex) || input.targetIndex < 0)) return undefined;
|
|
369
|
+
if (!validSteerRequest(input)) return undefined;
|
|
155
370
|
return {
|
|
156
371
|
type: "steer",
|
|
157
372
|
id: input.id.trim(),
|
|
158
373
|
ts: input.ts,
|
|
159
374
|
message: input.message.trim(),
|
|
160
375
|
...(input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}),
|
|
376
|
+
...(input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {}),
|
|
161
377
|
...(typeof input.source === "string" && input.source.trim() ? { source: input.source } : {}),
|
|
162
378
|
};
|
|
163
379
|
}
|
|
@@ -220,6 +436,20 @@ export function consumeTimeoutRequest(
|
|
|
220
436
|
return true;
|
|
221
437
|
}
|
|
222
438
|
|
|
439
|
+
export function consumeStopRequest(
|
|
440
|
+
asyncDir: string,
|
|
441
|
+
fsImpl: Pick<typeof fs, "existsSync" | "rmSync"> = fs,
|
|
442
|
+
): boolean {
|
|
443
|
+
const requestPath = stopRequestPath(asyncDir);
|
|
444
|
+
if (!fsImpl.existsSync(requestPath)) return false;
|
|
445
|
+
try {
|
|
446
|
+
fsImpl.rmSync(requestPath, { force: true, recursive: true });
|
|
447
|
+
} catch {
|
|
448
|
+
// Already removed by a concurrent check — still counts as consumed.
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
223
453
|
/**
|
|
224
454
|
* Parent side: portable interrupt = authoritative file request + best-effort OS
|
|
225
455
|
* signal. The signal is only a latency optimization on Unix; ENOSYS on Windows
|
|
@@ -265,6 +495,17 @@ export function deliverTimeoutRequest(input: {
|
|
|
265
495
|
requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
266
496
|
}
|
|
267
497
|
|
|
498
|
+
export function deliverStopRequest(input: {
|
|
499
|
+
asyncDir: string;
|
|
500
|
+
pid?: number;
|
|
501
|
+
kill?: KillFn;
|
|
502
|
+
signal?: NodeJS.Signals;
|
|
503
|
+
now?: () => number;
|
|
504
|
+
source?: string;
|
|
505
|
+
}): void {
|
|
506
|
+
requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
507
|
+
}
|
|
508
|
+
|
|
268
509
|
/**
|
|
269
510
|
* Runner side: watch the control inbox and route interrupt requests into
|
|
270
511
|
* `onInterrupt`. Uses `fs.watch` when available plus an interval poll as a
|
|
@@ -276,7 +517,10 @@ export function watchAsyncControlInbox(
|
|
|
276
517
|
opts: {
|
|
277
518
|
onInterrupt: () => void;
|
|
278
519
|
onTimeout?: () => void;
|
|
520
|
+
onStop?: () => void;
|
|
279
521
|
onSteer?: (request: SteerRequest) => void;
|
|
522
|
+
onSteerCapability?: (capability: SteerCapability) => void;
|
|
523
|
+
onSteerAck?: (ack: SteerAck) => void;
|
|
280
524
|
pollIntervalMs?: number;
|
|
281
525
|
fs?: ControlChannelFs;
|
|
282
526
|
timers?: ControlChannelTimers;
|
|
@@ -295,9 +539,12 @@ export function watchAsyncControlInbox(
|
|
|
295
539
|
const check = (): void => {
|
|
296
540
|
if (disposed) return;
|
|
297
541
|
try {
|
|
542
|
+
if (consumeStopRequest(asyncDir, fsImpl)) opts.onStop?.();
|
|
298
543
|
if (consumeTimeoutRequest(asyncDir, fsImpl)) opts.onTimeout?.();
|
|
299
544
|
if (consumeInterruptRequest(asyncDir, fsImpl)) opts.onInterrupt();
|
|
300
545
|
for (const request of consumeSteerRequests(asyncDir, fsImpl)) opts.onSteer?.(request);
|
|
546
|
+
for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) opts.onSteerCapability?.(capability);
|
|
547
|
+
for (const ack of consumeSteerAcks(asyncDir, fsImpl)) opts.onSteerAck?.(ack);
|
|
301
548
|
} catch {
|
|
302
549
|
// Never let inbox errors crash the runner.
|
|
303
550
|
}
|
|
@@ -308,7 +555,7 @@ export function watchAsyncControlInbox(
|
|
|
308
555
|
|
|
309
556
|
let watcher: fs.FSWatcher | undefined;
|
|
310
557
|
try {
|
|
311
|
-
watcher = fsImpl.watch(dir, () => check());
|
|
558
|
+
watcher = fsImpl.watch(resolveWatchPath(dir, fsImpl.realpathSync.native), () => check());
|
|
312
559
|
watcher.on?.("error", () => {
|
|
313
560
|
// fs.watch can emit on transient FS errors; the interval poll keeps us live.
|
|
314
561
|
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "../../shared/types.ts";
|
|
17
17
|
import { readStatus } from "../../shared/utils.ts";
|
|
18
18
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
19
|
+
import { contextModeLabel, summarizeContextModes } from "../shared/context-mode.ts";
|
|
19
20
|
import { formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
|
|
20
21
|
|
|
21
22
|
const DEFAULT_TRANSCRIPT_LINES = 80;
|
|
@@ -23,6 +24,7 @@ const MAX_TRANSCRIPT_LINES = 500;
|
|
|
23
24
|
const TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
24
25
|
|
|
25
26
|
type ForegroundControl = SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
27
|
+
type ForegroundRun = NonNullable<SubagentState["foregroundRuns"]> extends Map<string, infer T> ? T : never;
|
|
26
28
|
|
|
27
29
|
interface FleetViewParams {
|
|
28
30
|
lines?: number;
|
|
@@ -254,6 +256,20 @@ function formatForegroundFleetLines(controls: ForegroundControl[]): string[] {
|
|
|
254
256
|
return lines;
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
function formatDetachedForegroundFleetLines(runs: ForegroundRun[]): string[] {
|
|
260
|
+
if (runs.length === 0) return [];
|
|
261
|
+
const lines = ["Detached foreground runs:"];
|
|
262
|
+
const ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
263
|
+
for (const run of ordered) {
|
|
264
|
+
const detachedChildren = run.children.filter((child) => child.status === "detached");
|
|
265
|
+
const childSummary = detachedChildren.map((child) => `${child.agent} #${child.index}`).join(", ");
|
|
266
|
+
lines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : ""}`);
|
|
267
|
+
lines.push(` status: subagent({ action: "status", id: "${run.runId}" })`);
|
|
268
|
+
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.`);
|
|
269
|
+
}
|
|
270
|
+
return lines;
|
|
271
|
+
}
|
|
272
|
+
|
|
257
273
|
function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
|
|
258
274
|
if (runs.length === 0) return [];
|
|
259
275
|
const lines = ["Async runs:"];
|
|
@@ -262,15 +278,17 @@ function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
|
|
|
262
278
|
const activity = formatActivityFacts(run);
|
|
263
279
|
const cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);
|
|
264
280
|
const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
|
|
265
|
-
|
|
281
|
+
const runContext = contextModeLabel(run.context);
|
|
282
|
+
lines.push(`- ${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${runContext ? ` ${runContext}` : ""} | ${progress}${pending} | ${cwd}`);
|
|
266
283
|
lines.push(` status: subagent({ action: "status", id: "${run.id}" })`);
|
|
267
284
|
lines.push(` transcript: subagent({ action: "status", id: "${run.id}", view: "transcript" })`);
|
|
268
285
|
for (const step of run.steps) {
|
|
269
286
|
const display = step.label ? `${step.label} (${step.agent})` : step.agent;
|
|
287
|
+
const stepContext = contextModeLabel(step.context);
|
|
270
288
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
271
289
|
const stepActivity = formatActivityFacts(step);
|
|
272
290
|
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
273
|
-
const parts = [`${step.index}. ${phase}${display}`, step.status, stepActivity, modelThinking].filter(Boolean);
|
|
291
|
+
const parts = [`${step.index}. ${phase}${display}${stepContext ? ` ${stepContext}` : ""}`, step.status, stepActivity, modelThinking].filter(Boolean);
|
|
274
292
|
lines.push(` ${parts.join(" | ")}`);
|
|
275
293
|
const output = path.join(run.asyncDir, `output-${step.index}.log`);
|
|
276
294
|
if (fs.existsSync(output)) lines.push(` output: ${shortenPath(output)}`);
|
|
@@ -316,7 +334,11 @@ export function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDe
|
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
const foregroundControls = deps.state ? [...deps.state.foregroundControls.values()] : [];
|
|
319
|
-
const
|
|
337
|
+
const activeForegroundIds = new Set(foregroundControls.map((control) => control.runId));
|
|
338
|
+
const detachedForegroundRuns = deps.state?.foregroundRuns
|
|
339
|
+
? [...deps.state.foregroundRuns.values()].filter((run) => run.sessionId === deps.state?.currentSessionId && !activeForegroundIds.has(run.runId) && run.children.some((child) => child.status === "detached"))
|
|
340
|
+
: [];
|
|
341
|
+
const total = foregroundControls.length + detachedForegroundRuns.length + asyncRuns.length;
|
|
320
342
|
if (total === 0) {
|
|
321
343
|
return {
|
|
322
344
|
content: [{ type: "text", text: "No active subagent fleet. Background runs that already finished are available through completion notifications or subagent({ action: \"status\", id: \"...\" })." }],
|
|
@@ -324,9 +346,11 @@ export function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDe
|
|
|
324
346
|
};
|
|
325
347
|
}
|
|
326
348
|
|
|
327
|
-
const lines = [`Subagent fleet: ${total}
|
|
349
|
+
const lines = [`Subagent fleet: ${total} tracked`, ""];
|
|
328
350
|
const foregroundLines = formatForegroundFleetLines(foregroundControls);
|
|
329
351
|
if (foregroundLines.length) lines.push(...foregroundLines, "");
|
|
352
|
+
const detachedForegroundLines = formatDetachedForegroundFleetLines(detachedForegroundRuns);
|
|
353
|
+
if (detachedForegroundLines.length) lines.push(...detachedForegroundLines, "");
|
|
330
354
|
const asyncLines = formatAsyncFleetLines(asyncRuns);
|
|
331
355
|
if (asyncLines.length) lines.push(...asyncLines, "");
|
|
332
356
|
lines.push("Commands:");
|
|
@@ -364,8 +388,9 @@ function selectTranscriptStep(status: AsyncStatus, options: TranscriptOptions):
|
|
|
364
388
|
function stepStateLine(mode: SubagentRunMode, index: number | undefined, step: AsyncJobStep | undefined): string | undefined {
|
|
365
389
|
if (index === undefined || !step) return undefined;
|
|
366
390
|
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
391
|
+
const context = contextModeLabel(step.context);
|
|
367
392
|
const parts = [
|
|
368
|
-
`${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step.agent})`,
|
|
393
|
+
`${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step.agent})${context ? ` ${context}` : ""}`,
|
|
369
394
|
step.status,
|
|
370
395
|
formatActivityFacts(step),
|
|
371
396
|
modelThinking,
|
|
@@ -407,10 +432,11 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
|
|
|
407
432
|
const sessionFile = selected.index !== undefined ? selected.step?.sessionFile : status.sessionFile;
|
|
408
433
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
409
434
|
|
|
435
|
+
const context = contextModeLabel(status.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)));
|
|
410
436
|
const lines = [
|
|
411
437
|
`Run: ${status.runId}`,
|
|
412
438
|
`State: ${status.state}`,
|
|
413
|
-
`Mode: ${status.mode}`,
|
|
439
|
+
`Mode: ${status.mode}${context ? ` ${context}` : ""}`,
|
|
414
440
|
stepStateLine(status.mode, selected.index, selected.step),
|
|
415
441
|
selected.hint,
|
|
416
442
|
].filter((line): line is string => Boolean(line));
|