@selesai/code 0.5.28 → 0.5.29
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/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +117 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +20 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +11 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/llama/provider.d.ts.map +1 -1
- package/dist/core/llama/provider.js +8 -3
- package/dist/core/llama/provider.js.map +1 -1
- package/dist/core/model-config.d.ts +30 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +20 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -4
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +13 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +24 -11
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +66 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +34 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +3 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
- package/dist/extensions/pi-intercom/README.md +102 -39
- package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
- package/dist/extensions/pi-intercom/broker/client.ts +315 -12
- package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
- package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
- package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
- package/dist/extensions/pi-intercom/config.test.ts +93 -0
- package/dist/extensions/pi-intercom/config.ts +55 -6
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
- package/dist/extensions/pi-intercom/cwd.ts +31 -0
- package/dist/extensions/pi-intercom/extension-api.ts +44 -0
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
- package/dist/extensions/pi-intercom/format-context.ts +32 -0
- package/dist/extensions/pi-intercom/index.ts +742 -145
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
- package/dist/extensions/pi-intercom/package.json +15 -5
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
- package/dist/extensions/pi-intercom/types.ts +94 -4
- package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
- package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
- package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +85 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +146 -52
- package/dist/extensions/pi-subagents/agents/architect.md +1 -0
- package/dist/extensions/pi-subagents/agents/builder.md +1 -0
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +3 -3
- package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +21 -989
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +282 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
- package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
- package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +63 -16
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +29 -4
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +143 -43
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
- package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +155 -10
- package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
- package/dist/extensions/pi-subagents/src/tui/render.ts +27 -28
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +118 -12
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +102 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +11 -11
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +10 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +46 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +34 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +3 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +43 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +3 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +10 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -46
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +4 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +72 -64
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +1 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +14 -5
- package/docs/environment-variables.md +86 -0
- package/docs/extensions.md +15 -5
- package/docs/index.md +1 -0
- package/docs/models.md +9 -2
- package/docs/providers.md +21 -2
- package/docs/rpc.md +23 -6
- package/docs/session-format.md +2 -0
- package/docs/settings.md +1 -1
- package/docs/usage.md +0 -13
- package/examples/extensions/custom-compaction.ts +5 -2
- package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +11 -3
- package/examples/extensions/message-renderer.ts +3 -3
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +5 -2
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/package.json +15 -6
- package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -15,7 +15,7 @@ import * as os from "node:os";
|
|
|
15
15
|
import * as path from "node:path";
|
|
16
16
|
import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, tryImport } from "../support/helpers.ts";
|
|
17
17
|
import type { MockPi } from "../support/helpers.ts";
|
|
18
|
-
import { deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest } from "../../src/runs/background/control-channel.ts";
|
|
18
|
+
import { deliverCheckpointDecisionRequest, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest } from "../../src/runs/background/control-channel.ts";
|
|
19
19
|
import { waitForSubagents } from "../../src/runs/background/subagent-wait.ts";
|
|
20
20
|
import { writeAtomicJson } from "../../src/shared/atomic-json.ts";
|
|
21
21
|
import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
|
|
@@ -26,10 +26,35 @@ import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
|
|
|
26
26
|
import { discoverAgents } from "../../src/agents/agents.ts";
|
|
27
27
|
import { runSync } from "../../src/runs/foreground/execution.ts";
|
|
28
28
|
|
|
29
|
+
interface LaunchResolvedExtensions {
|
|
30
|
+
version?: number;
|
|
31
|
+
source?: string;
|
|
32
|
+
disableAmbientExtensions?: boolean;
|
|
33
|
+
runtime?: string[];
|
|
34
|
+
configured?: string[];
|
|
35
|
+
effective?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RuntimeAcknowledgedExtensions {
|
|
39
|
+
version?: number;
|
|
40
|
+
source?: string;
|
|
41
|
+
ids?: string[];
|
|
42
|
+
omitted?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface UsageBudgetState {
|
|
46
|
+
version?: number;
|
|
47
|
+
source?: string;
|
|
48
|
+
exhausted?: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
tokens?: { used?: number; hard?: number; exhausted?: boolean };
|
|
51
|
+
costUsd?: { used?: number; hard?: number; exhausted?: boolean };
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
interface AsyncExecutionResult {
|
|
30
55
|
content: Array<{ text?: string }>;
|
|
31
56
|
isError?: boolean;
|
|
32
|
-
details: { asyncId?: string; asyncDir?: string; launchContractDigest?: string };
|
|
57
|
+
details: { asyncId?: string; asyncDir?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; usageBudget?: UsageBudgetState; checkpoint?: { name?: string; status?: string } };
|
|
33
58
|
}
|
|
34
59
|
|
|
35
60
|
interface AsyncResultPayload {
|
|
@@ -40,6 +65,8 @@ interface AsyncResultPayload {
|
|
|
40
65
|
sessionId?: string;
|
|
41
66
|
mode?: string;
|
|
42
67
|
launchContractDigest?: string;
|
|
68
|
+
launchResolvedExtensions?: LaunchResolvedExtensions;
|
|
69
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions;
|
|
43
70
|
summary?: string;
|
|
44
71
|
error?: string;
|
|
45
72
|
timeoutMs?: number;
|
|
@@ -51,7 +78,9 @@ interface AsyncResultPayload {
|
|
|
51
78
|
wrapUpRequested?: boolean;
|
|
52
79
|
totalTokens?: { input: number; output: number; total: number };
|
|
53
80
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
54
|
-
|
|
81
|
+
usageBudget?: UsageBudgetState;
|
|
82
|
+
checkpoint?: { name?: string; status?: string };
|
|
83
|
+
results: Array<{ agent?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string }; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
|
|
55
84
|
outputs?: Record<string, { text?: string; structured?: unknown }>;
|
|
56
85
|
workflowGraph?: { nodes?: Array<{ kind?: string; label?: string; phase?: string; status?: string; acceptanceStatus?: string; error?: string; outputName?: string; structured?: boolean; children?: Array<{ label?: string; outputName?: string; itemKey?: string; status?: string; acceptanceStatus?: string; error?: string }> }> };
|
|
57
86
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
@@ -68,6 +97,8 @@ interface AsyncStatusPayload {
|
|
|
68
97
|
currentPath?: string;
|
|
69
98
|
state?: string;
|
|
70
99
|
launchContractDigest?: string;
|
|
100
|
+
launchResolvedExtensions?: LaunchResolvedExtensions;
|
|
101
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions;
|
|
71
102
|
error?: string;
|
|
72
103
|
timeoutMs?: number;
|
|
73
104
|
deadlineAt?: number;
|
|
@@ -78,6 +109,8 @@ interface AsyncStatusPayload {
|
|
|
78
109
|
wrapUpRequested?: boolean;
|
|
79
110
|
totalTokens?: { total: number };
|
|
80
111
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
112
|
+
usageBudget?: UsageBudgetState;
|
|
113
|
+
checkpoint?: { name?: string; status?: string; message?: string };
|
|
81
114
|
parallelGroups?: Array<{ start: number; count: number; stepIndex: number }>;
|
|
82
115
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
83
116
|
capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] };
|
|
@@ -100,6 +133,8 @@ interface AsyncStatusPayload {
|
|
|
100
133
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
101
134
|
agentContract?: { version: 1 };
|
|
102
135
|
launchContractDigest?: string;
|
|
136
|
+
launchResolvedExtensions?: LaunchResolvedExtensions;
|
|
137
|
+
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions;
|
|
103
138
|
execution?: { status?: string; success?: boolean; exitCode?: number };
|
|
104
139
|
effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } };
|
|
105
140
|
acceptance?: { status?: string };
|
|
@@ -292,6 +327,19 @@ async function waitForAsyncResultFile(id: string, timeoutMs = 15_000): Promise<s
|
|
|
292
327
|
return resultPath;
|
|
293
328
|
}
|
|
294
329
|
|
|
330
|
+
async function waitForAsyncState(id: string, predicate: (status: AsyncStatusPayload) => boolean, timeoutMs = 10_000): Promise<AsyncStatusPayload> {
|
|
331
|
+
const statusPath = path.join(ASYNC_DIR, id, "status.json");
|
|
332
|
+
const deadline = Date.now() + timeoutMs;
|
|
333
|
+
while (Date.now() <= deadline) {
|
|
334
|
+
if (fs.existsSync(statusPath)) {
|
|
335
|
+
const status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
336
|
+
if (predicate(status)) return status;
|
|
337
|
+
}
|
|
338
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
339
|
+
}
|
|
340
|
+
assert.fail(`Timed out waiting for async status: ${statusPath}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
295
343
|
async function waitForDeferredTurnBudget(id: string, timeoutMs = 10_000): Promise<AsyncStatusPayload> {
|
|
296
344
|
const statusPath = path.join(ASYNC_DIR, id, "status.json");
|
|
297
345
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -435,6 +483,26 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
435
483
|
assert.equal(payload.results[0]?.output, "你好 from fragmented async JSON");
|
|
436
484
|
});
|
|
437
485
|
|
|
486
|
+
it("persists absent output provenance when async lifecycle text is synthetic", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
487
|
+
mockPi.onCall({ jsonl: [mockAssistantMessage("", "tool_use")], stderr: "mock child failure", exitCode: 1 });
|
|
488
|
+
const id = `async-output-absent-${Date.now().toString(36)}`;
|
|
489
|
+
launchProtocolTest(id);
|
|
490
|
+
const payload = await readAsyncPayload(id);
|
|
491
|
+
assert.equal(payload.success, false);
|
|
492
|
+
assert.equal(payload.results[0]?.outputState, "absent");
|
|
493
|
+
assert.match(payload.results[0]?.error ?? "", /mock child failure/);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("persists present output provenance when async failure follows partial output", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
497
|
+
mockPi.onCall({ output: "usable partial answer", stderr: "mock post-output failure", exitCode: 1 });
|
|
498
|
+
const id = `async-output-present-${Date.now().toString(36)}`;
|
|
499
|
+
launchProtocolTest(id);
|
|
500
|
+
const payload = await readAsyncPayload(id);
|
|
501
|
+
assert.equal(payload.success, false);
|
|
502
|
+
assert.equal(payload.results[0]?.outputState, "present");
|
|
503
|
+
assert.equal(payload.results[0]?.output, "usable partial answer");
|
|
504
|
+
});
|
|
505
|
+
|
|
438
506
|
it("matches preflight launch digest in equivalent foreground and async execution", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
439
507
|
const agentName = `contract-worker-${Date.now().toString(36)}`;
|
|
440
508
|
const task = "Compare the resolved launch inputs.";
|
|
@@ -473,12 +541,16 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
473
541
|
});
|
|
474
542
|
|
|
475
543
|
it("persists the actual launch digest in async status and result metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
476
|
-
mockPi.onCall({
|
|
544
|
+
mockPi.onCall({
|
|
545
|
+
output: "digest-bound async done",
|
|
546
|
+
runtimeAcknowledgedExtensions: { version: 1, source: "child-runtime", ids: ["ext.async"], omitted: 0 },
|
|
547
|
+
});
|
|
477
548
|
const id = `async-launch-digest-${Date.now().toString(36)}`;
|
|
549
|
+
const privateExtension = path.join(tempDir, "extensions", "private-extension.ts");
|
|
478
550
|
const launch = executeAsyncSingle(id, {
|
|
479
551
|
agent: "worker",
|
|
480
552
|
task: "Exercise launch digest reporting",
|
|
481
|
-
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
553
|
+
agentConfig: makeAgent("worker", { completionGuard: false, extensions: [privateExtension] }),
|
|
482
554
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
483
555
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
484
556
|
shareEnabled: false,
|
|
@@ -493,6 +565,140 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
493
565
|
assert.equal(payload.results[0]?.launchContractDigest, launch.details.launchContractDigest);
|
|
494
566
|
assert.equal(status.launchContractDigest, launch.details.launchContractDigest);
|
|
495
567
|
assert.equal(status.steps?.[0]?.launchContractDigest, launch.details.launchContractDigest);
|
|
568
|
+
assert.equal(launch.details.launchResolvedExtensions?.source, "launch-resolved");
|
|
569
|
+
assert.equal(launch.details.launchResolvedExtensions?.disableAmbientExtensions, true);
|
|
570
|
+
assert.deepEqual(payload.launchResolvedExtensions, launch.details.launchResolvedExtensions);
|
|
571
|
+
assert.deepEqual(payload.results[0]?.launchResolvedExtensions, launch.details.launchResolvedExtensions);
|
|
572
|
+
assert.deepEqual(status.launchResolvedExtensions, launch.details.launchResolvedExtensions);
|
|
573
|
+
assert.deepEqual(status.steps?.[0]?.launchResolvedExtensions, launch.details.launchResolvedExtensions);
|
|
574
|
+
const runtimeAck = { version: 1, source: "child-runtime", ids: ["ext.async"], omitted: 0 };
|
|
575
|
+
assert.deepEqual(payload.runtimeAcknowledgedExtensions, runtimeAck);
|
|
576
|
+
assert.deepEqual(payload.results[0]?.runtimeAcknowledgedExtensions, runtimeAck);
|
|
577
|
+
assert.deepEqual(status.runtimeAcknowledgedExtensions, runtimeAck);
|
|
578
|
+
assert.deepEqual(status.steps?.[0]?.runtimeAcknowledgedExtensions, runtimeAck);
|
|
579
|
+
assert.ok(!JSON.stringify(launch.details.launchResolvedExtensions).includes(tempDir), "projection should not expose raw extension paths");
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("async chain checkpoints pause before the next child until approved", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
583
|
+
mockPi.onCall({ output: "analysis done" });
|
|
584
|
+
mockPi.onCall({ output: "implementation done" });
|
|
585
|
+
const id = `async-checkpoint-approve-${Date.now().toString(36)}`;
|
|
586
|
+
const launch = executeAsyncChain(id, {
|
|
587
|
+
chain: [
|
|
588
|
+
{ agent: "analyst", task: "Analyze" },
|
|
589
|
+
{ checkpoint: "review", message: "Approve implementation?" },
|
|
590
|
+
{ agent: "reporter", task: "Summarize {previous}" },
|
|
591
|
+
],
|
|
592
|
+
agents: [makeAgent("analyst"), makeAgent("reporter")],
|
|
593
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
594
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
595
|
+
shareEnabled: false,
|
|
596
|
+
maxSubagentDepth: 2,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
const paused = await waitForAsyncState(id, (status) => status.state === "paused" && status.checkpoint?.name === "review");
|
|
600
|
+
assert.equal(launch.isError, undefined);
|
|
601
|
+
assert.equal(paused.steps?.[1]?.status, "paused");
|
|
602
|
+
assert.equal(mockPi.callCount(), 1);
|
|
603
|
+
|
|
604
|
+
deliverCheckpointDecisionRequest({ asyncDir: path.join(ASYNC_DIR, id), decision: "approved" });
|
|
605
|
+
const payload = await readAsyncPayload(id);
|
|
606
|
+
assert.equal(payload.success, true);
|
|
607
|
+
assert.equal(payload.state, "complete");
|
|
608
|
+
assert.equal(payload.checkpoint?.status, "approved");
|
|
609
|
+
assert.equal(payload.results.length, 2);
|
|
610
|
+
assert.equal(mockPi.callCount(), 2);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("foreground checkpoint decisions use current-session control actions", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
|
|
614
|
+
mockPi.onCall({ output: "analysis done" });
|
|
615
|
+
const executor = makeAsyncExecutor([makeAgent("analyst"), makeAgent("worker")]);
|
|
616
|
+
const ctx = makeMinimalCtx(tempDir);
|
|
617
|
+
ctx.sessionManager.getSessionId = () => "session-foreground-checkpoint";
|
|
618
|
+
const id = `foreground-checkpoint-${Date.now().toString(36)}`;
|
|
619
|
+
|
|
620
|
+
const paused = await executor.execute(id, {
|
|
621
|
+
chain: [
|
|
622
|
+
{ agent: "analyst", task: "Analyze" },
|
|
623
|
+
{ checkpoint: "review", message: "Approve implementation?" },
|
|
624
|
+
{ agent: "worker", task: "Implement" },
|
|
625
|
+
],
|
|
626
|
+
}, new AbortController().signal, undefined, ctx) as AsyncExecutionResult;
|
|
627
|
+
|
|
628
|
+
assert.equal(paused.isError, undefined);
|
|
629
|
+
assert.equal(paused.details.checkpoint?.status, "pending");
|
|
630
|
+
assert.equal(mockPi.callCount(), 1);
|
|
631
|
+
const runId = (paused.details as AsyncExecutionResult["details"] & { runId?: string }).runId;
|
|
632
|
+
assert.ok(runId);
|
|
633
|
+
|
|
634
|
+
const approved = await executor.execute(`${id}-approve`, { action: "approve-checkpoint", id: runId }, new AbortController().signal, undefined, ctx) as AsyncExecutionResult;
|
|
635
|
+
assert.equal(approved.isError, undefined);
|
|
636
|
+
assert.equal(approved.details.checkpoint?.name, "review");
|
|
637
|
+
assert.equal(approved.details.checkpoint?.status, "approved");
|
|
638
|
+
assert.equal(mockPi.callCount(), 1);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("async chain checkpoint rejection is an explicit terminal state", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
642
|
+
mockPi.onCall({ output: "analysis done" });
|
|
643
|
+
mockPi.onCall({ output: "should not run" });
|
|
644
|
+
const id = `async-checkpoint-reject-${Date.now().toString(36)}`;
|
|
645
|
+
executeAsyncChain(id, {
|
|
646
|
+
chain: [
|
|
647
|
+
{ agent: "analyst", task: "Analyze" },
|
|
648
|
+
{ checkpoint: "review" },
|
|
649
|
+
{ agent: "worker", task: "Implement" },
|
|
650
|
+
],
|
|
651
|
+
agents: [makeAgent("analyst"), makeAgent("worker")],
|
|
652
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
653
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
654
|
+
shareEnabled: false,
|
|
655
|
+
maxSubagentDepth: 2,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
await waitForAsyncState(id, (status) => status.state === "paused" && status.checkpoint?.name === "review");
|
|
659
|
+
deliverCheckpointDecisionRequest({ asyncDir: path.join(ASYNC_DIR, id), decision: "rejected" });
|
|
660
|
+
const payload = await readAsyncPayload(id);
|
|
661
|
+
assert.equal(payload.success, false);
|
|
662
|
+
assert.equal(payload.state, "rejected");
|
|
663
|
+
assert.equal(payload.checkpoint?.status, "rejected");
|
|
664
|
+
assert.equal(mockPi.callCount(), 1);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("background parallel groups report usage budget state and block queued children", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
668
|
+
mockPi.onCall({ output: "first async result" });
|
|
669
|
+
const id = `async-usage-budget-${Date.now().toString(36)}`;
|
|
670
|
+
const launch = executeAsyncChain(id, {
|
|
671
|
+
chain: [{
|
|
672
|
+
parallel: [
|
|
673
|
+
{ agent: "first", task: "First task" },
|
|
674
|
+
{ agent: "second", task: "Second task" },
|
|
675
|
+
],
|
|
676
|
+
concurrency: 1,
|
|
677
|
+
}],
|
|
678
|
+
resultMode: "parallel",
|
|
679
|
+
usageBudget: { tokens: { hard: 10 } },
|
|
680
|
+
agents: [makeAgent("first"), makeAgent("second")],
|
|
681
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
682
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
683
|
+
shareEnabled: false,
|
|
684
|
+
maxSubagentDepth: 2,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
assert.equal(launch.details.usageBudget?.exhausted, false);
|
|
688
|
+
const payload = await readAsyncPayload(id);
|
|
689
|
+
const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
690
|
+
assert.equal(payload.success, false);
|
|
691
|
+
assert.equal(payload.state, "failed");
|
|
692
|
+
assert.match(payload.error ?? payload.summary ?? "", /Usage budget exhausted/);
|
|
693
|
+
assert.equal(payload.results.length, 2);
|
|
694
|
+
assert.equal(payload.results[1]?.skipped, true);
|
|
695
|
+
assert.match(payload.results[1]?.error ?? "", /Usage budget exhausted/);
|
|
696
|
+
assert.equal(mockPi.callCount(), 1);
|
|
697
|
+
assert.equal(payload.usageBudget?.exhausted, true);
|
|
698
|
+
assert.equal(payload.usageBudget?.reason, "tokens");
|
|
699
|
+
assert.equal(status.usageBudget?.exhausted, true);
|
|
700
|
+
assert.equal(status.steps?.[0]?.status, "complete");
|
|
701
|
+
assert.equal(status.steps?.[1]?.status, "failed");
|
|
496
702
|
});
|
|
497
703
|
|
|
498
704
|
it("background fails with protocol_output_limit for an oversized stdout line", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -1349,13 +1555,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1349
1555
|
tempArtifactsDir: tempDir,
|
|
1350
1556
|
getSubagentSessionRoot: () => tempDir,
|
|
1351
1557
|
expandTilde: (p: string) => p,
|
|
1352
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
1558
|
+
discoverAgents: () => ({ agents: [makeAgent("builder", { defaultProgress: true })] }),
|
|
1353
1559
|
});
|
|
1354
1560
|
|
|
1355
1561
|
const result = await executor.execute(
|
|
1356
1562
|
"async-parallel-fields",
|
|
1357
1563
|
{
|
|
1358
|
-
tasks: [{ agent: "
|
|
1564
|
+
tasks: [{ agent: "builder", task: "Do async work", output: "async-top-output.md", reads: ["input.md"] }],
|
|
1359
1565
|
async: true,
|
|
1360
1566
|
clarify: false,
|
|
1361
1567
|
},
|
|
@@ -1862,6 +2068,39 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1862
2068
|
assert.equal(payload.workflowGraph?.nodes?.[2]?.flatIndex, 3);
|
|
1863
2069
|
});
|
|
1864
2070
|
|
|
2071
|
+
it("async dynamic fanout blocks queued children when hard reported usage is exhausted", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2072
|
+
mockPi.onCall({ output: "targets", structuredOutput: { items: [{ path: "src/a.ts" }, { path: "src/b.ts" }] } });
|
|
2073
|
+
mockPi.onCall({ matchArgIncludes: "Review src/a.ts", output: "review-a", structuredOutput: { ok: "a" } });
|
|
2074
|
+
const id = `async-dynamic-usage-budget-${Date.now().toString(36)}`;
|
|
2075
|
+
executeAsyncChain(id, {
|
|
2076
|
+
chain: [
|
|
2077
|
+
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2078
|
+
{
|
|
2079
|
+
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2080
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
|
|
2081
|
+
collect: { as: "reviews" },
|
|
2082
|
+
concurrency: 1,
|
|
2083
|
+
},
|
|
2084
|
+
],
|
|
2085
|
+
usageBudget: { tokens: { hard: 200 } },
|
|
2086
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2087
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-budget" },
|
|
2088
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2089
|
+
shareEnabled: false,
|
|
2090
|
+
maxSubagentDepth: 2,
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
const payload = await readAsyncPayload(id);
|
|
2094
|
+
const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
2095
|
+
assert.equal(mockPi.callCount(), 2);
|
|
2096
|
+
assert.equal(payload.success, false);
|
|
2097
|
+
assert.equal(payload.usageBudget?.exhausted, true);
|
|
2098
|
+
assert.equal(status.steps?.[1]?.status, "complete");
|
|
2099
|
+
assert.equal(status.steps?.[2]?.status, "failed");
|
|
2100
|
+
assert.match(status.steps?.[2]?.error ?? "", /Usage budget exhausted/);
|
|
2101
|
+
assert.equal(payload.results.find((result) => result.agent === "reviewer" && result.skipped)?.skipped, true);
|
|
2102
|
+
});
|
|
2103
|
+
|
|
1865
2104
|
it("rejects a shared explicit output before dynamic fanout children start", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1866
2105
|
mockPi.onCall({ matchArgIncludes: "Produce targets", output: "targets", structuredOutput: { items: [{ path: "src/a.ts" }, { path: "src/b.ts" }] } });
|
|
1867
2106
|
const id = `async-dynamic-explicit-output-${Date.now().toString(36)}`;
|
|
@@ -2360,6 +2599,43 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2360
2599
|
assert.equal(mockPi.callCount(), 2);
|
|
2361
2600
|
});
|
|
2362
2601
|
|
|
2602
|
+
it("background runs retry a zero-activity startup exit on the same model", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2603
|
+
mockPi.onCall({ exitCode: 1 });
|
|
2604
|
+
mockPi.onCall({ output: "Recovered asynchronously after startup race" });
|
|
2605
|
+
const id = `async-startup-retry-${Date.now().toString(36)}`;
|
|
2606
|
+
executeAsyncSingle(id, {
|
|
2607
|
+
agent: "worker",
|
|
2608
|
+
task: "Do work",
|
|
2609
|
+
agentConfig: makeAgent("worker", {
|
|
2610
|
+
model: "openai/gpt-5-mini:high",
|
|
2611
|
+
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2612
|
+
}),
|
|
2613
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
2614
|
+
availableModels: [
|
|
2615
|
+
{ provider: "openai", id: "gpt-5-mini", fullId: "openai/gpt-5-mini" },
|
|
2616
|
+
{ provider: "anthropic", id: "claude-sonnet-4", fullId: "anthropic/claude-sonnet-4" },
|
|
2617
|
+
],
|
|
2618
|
+
artifactConfig: {
|
|
2619
|
+
enabled: false,
|
|
2620
|
+
includeInput: false,
|
|
2621
|
+
includeOutput: false,
|
|
2622
|
+
includeJsonl: false,
|
|
2623
|
+
includeMetadata: false,
|
|
2624
|
+
cleanupDays: 7,
|
|
2625
|
+
},
|
|
2626
|
+
shareEnabled: false,
|
|
2627
|
+
maxSubagentDepth: 2,
|
|
2628
|
+
});
|
|
2629
|
+
|
|
2630
|
+
const payload = JSON.parse(fs.readFileSync(await waitForAsyncResultFile(id), "utf-8")) as AsyncResultPayload;
|
|
2631
|
+
assert.equal(payload.success, true);
|
|
2632
|
+
assert.equal(payload.results[0]?.model, "openai/gpt-5-mini:high");
|
|
2633
|
+
assert.deepEqual(payload.results[0]?.attemptedModels, ["openai/gpt-5-mini:high"]);
|
|
2634
|
+
assert.deepEqual(payload.results[0]?.modelAttempts?.map((attempt) => attempt.success), [false, true]);
|
|
2635
|
+
assert.match(payload.results[0]?.output ?? "", /\[startup-retry\].*Recovered asynchronously after startup race/s);
|
|
2636
|
+
assert.equal(mockPi.callCount(), 2);
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2363
2639
|
it("background runs retry the fallback model when the provider stream ends without finish_reason", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2364
2640
|
mockPi.onCall({
|
|
2365
2641
|
jsonl: [{
|
|
@@ -3143,6 +3419,71 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3143
3419
|
assert.deepEqual(payload.results[0].attemptedModels, ["github-copilot/gpt-5-mini"]);
|
|
3144
3420
|
});
|
|
3145
3421
|
|
|
3422
|
+
it("async executor keeps the last parent session model after continuation drops ctx.model", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
3423
|
+
mockPi.onCall({ output: "Done asynchronously" });
|
|
3424
|
+
const state = {
|
|
3425
|
+
baseCwd: tempDir,
|
|
3426
|
+
currentSessionId: null,
|
|
3427
|
+
asyncJobs: new Map(),
|
|
3428
|
+
foregroundControls: new Map(),
|
|
3429
|
+
lastForegroundControlId: null,
|
|
3430
|
+
};
|
|
3431
|
+
const executor = createSubagentExecutor!({
|
|
3432
|
+
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
3433
|
+
state,
|
|
3434
|
+
config: {},
|
|
3435
|
+
asyncByDefault: false,
|
|
3436
|
+
tempArtifactsDir: tempDir,
|
|
3437
|
+
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
3438
|
+
expandTilde: (p: string) => p,
|
|
3439
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
3440
|
+
});
|
|
3441
|
+
const initialCtx = makeMinimalCtx(tempDir);
|
|
3442
|
+
initialCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3443
|
+
initialCtx.model = { provider: "deepseek", id: "deepseek-v4-flash" };
|
|
3444
|
+
await executor.execute("prime-parent-model", { action: "list" }, new AbortController().signal, undefined, initialCtx);
|
|
3445
|
+
|
|
3446
|
+
const continuedCtx = makeMinimalCtx(tempDir);
|
|
3447
|
+
continuedCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3448
|
+
const launch = await executor.execute(
|
|
3449
|
+
"continued-async-child",
|
|
3450
|
+
{ agent: "worker", task: "Do work", async: true, acceptance: false },
|
|
3451
|
+
new AbortController().signal,
|
|
3452
|
+
undefined,
|
|
3453
|
+
continuedCtx,
|
|
3454
|
+
) as AsyncExecutionResult;
|
|
3455
|
+
assert.equal(launch.isError, undefined);
|
|
3456
|
+
assert.ok(launch.details.asyncId);
|
|
3457
|
+
|
|
3458
|
+
const payload = await readAsyncPayload(launch.details.asyncId);
|
|
3459
|
+
assert.equal(payload.success, true);
|
|
3460
|
+
assert.equal(payload.results[0]?.model, "deepseek/deepseek-v4-flash");
|
|
3461
|
+
const args = readMockPiArgs(mockPi, 0);
|
|
3462
|
+
assert.equal(args[args.indexOf("--model") + 1], "deepseek/deepseek-v4-flash");
|
|
3463
|
+
});
|
|
3464
|
+
|
|
3465
|
+
it("foreground chains keep the last parent session model after continuation drops ctx.model", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
|
|
3466
|
+
mockPi.onCall({ output: "Done foreground" });
|
|
3467
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })]);
|
|
3468
|
+
const initialCtx = makeMinimalCtx(tempDir);
|
|
3469
|
+
initialCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3470
|
+
initialCtx.model = { provider: "deepseek", id: "deepseek-v4-flash" };
|
|
3471
|
+
await executor.execute("prime-parent-model", { action: "list" }, new AbortController().signal, undefined, initialCtx);
|
|
3472
|
+
|
|
3473
|
+
const continuedCtx = makeMinimalCtx(tempDir);
|
|
3474
|
+
continuedCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3475
|
+
const result = await executor.execute(
|
|
3476
|
+
"continued-foreground-chain",
|
|
3477
|
+
{ chain: [{ agent: "worker", task: "Do work" }], acceptance: false },
|
|
3478
|
+
new AbortController().signal,
|
|
3479
|
+
undefined,
|
|
3480
|
+
continuedCtx,
|
|
3481
|
+
);
|
|
3482
|
+
assert.equal(result.isError, undefined);
|
|
3483
|
+
const args = readMockPiArgs(mockPi, 0);
|
|
3484
|
+
assert.equal(args[args.indexOf("--model") + 1], "deepseek/deepseek-v4-flash");
|
|
3485
|
+
});
|
|
3486
|
+
|
|
3146
3487
|
it("background single runs inherit the parent session model when no model is set", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3147
3488
|
mockPi.onCall({ output: "Done asynchronously" });
|
|
3148
3489
|
|
|
@@ -114,6 +114,27 @@ function createUiContext() {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
describe("async job tracker", { skip: !available ? "pi packages not available" : undefined }, () => {
|
|
117
|
+
it("drops stale captured extension contexts instead of crashing background timers", () => {
|
|
118
|
+
const asyncRoot = createTempDir("pi-async-job-tracker-stale-context-");
|
|
119
|
+
try {
|
|
120
|
+
const state = createState();
|
|
121
|
+
const recorder = createEventRecorder();
|
|
122
|
+
const tracker = trackerMod!.createAsyncJobTracker(recorder.pi, state as never, asyncRoot);
|
|
123
|
+
(state as { lastUiContext: unknown }).lastUiContext = {
|
|
124
|
+
get hasUI() {
|
|
125
|
+
throw new Error("This extension ctx is stale after session replacement or reload.");
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
tracker.handleStarted({ id: "run-stale-context", asyncDir: path.join(asyncRoot, "run-stale-context"), agent: "worker" });
|
|
130
|
+
|
|
131
|
+
assert.equal(state.lastUiContext, null);
|
|
132
|
+
if (state.poller) clearInterval(state.poller);
|
|
133
|
+
} finally {
|
|
134
|
+
removeTempDir(asyncRoot);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
117
138
|
it("removes completed jobs after retention and requests a rerender", async () => {
|
|
118
139
|
const asyncRoot = createTempDir("pi-async-job-tracker-");
|
|
119
140
|
try {
|
|
@@ -856,6 +877,69 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
856
877
|
}
|
|
857
878
|
});
|
|
858
879
|
|
|
880
|
+
it("resynchronizes when a saved cursor resumes inside an oversized JSONL record", async () => {
|
|
881
|
+
const asyncRoot = createTempDir("pi-async-job-tracker-");
|
|
882
|
+
const originalError = console.error;
|
|
883
|
+
const errors: unknown[][] = [];
|
|
884
|
+
console.error = (...args: unknown[]) => {
|
|
885
|
+
errors.push(args);
|
|
886
|
+
};
|
|
887
|
+
try {
|
|
888
|
+
const runDir = path.join(asyncRoot, "run-midline-oversized-control");
|
|
889
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
890
|
+
fs.writeFileSync(path.join(runDir, "status.json"), JSON.stringify({
|
|
891
|
+
runId: "run-midline-oversized-control",
|
|
892
|
+
mode: "single",
|
|
893
|
+
state: "running",
|
|
894
|
+
startedAt: Date.now() - 1000,
|
|
895
|
+
lastUpdate: Date.now(),
|
|
896
|
+
steps: [{ agent: "worker", status: "running" }],
|
|
897
|
+
}), "utf-8");
|
|
898
|
+
const largeDiagnostic = JSON.stringify({
|
|
899
|
+
type: "message_update",
|
|
900
|
+
message: { role: "assistant", content: [{ type: "text", text: "x".repeat(2_500_000) }] },
|
|
901
|
+
});
|
|
902
|
+
const controlEvent = JSON.stringify({
|
|
903
|
+
type: "subagent.control",
|
|
904
|
+
channels: ["event"],
|
|
905
|
+
event: {
|
|
906
|
+
type: "needs_attention",
|
|
907
|
+
to: "needs_attention",
|
|
908
|
+
ts: 123,
|
|
909
|
+
runId: "run-midline-oversized-control",
|
|
910
|
+
agent: "worker",
|
|
911
|
+
message: "worker needs attention",
|
|
912
|
+
},
|
|
913
|
+
});
|
|
914
|
+
fs.writeFileSync(path.join(runDir, "events.jsonl"), `${largeDiagnostic}\n${controlEvent}\n`, "utf-8");
|
|
915
|
+
|
|
916
|
+
const state = createState();
|
|
917
|
+
state.asyncJobs.set("run-midline-oversized-control", {
|
|
918
|
+
asyncId: "run-midline-oversized-control",
|
|
919
|
+
asyncDir: runDir,
|
|
920
|
+
status: "running",
|
|
921
|
+
agents: ["worker"],
|
|
922
|
+
startedAt: Date.now() - 1000,
|
|
923
|
+
updatedAt: Date.now(),
|
|
924
|
+
controlEventCursor: 0,
|
|
925
|
+
});
|
|
926
|
+
const recorder = createEventRecorder();
|
|
927
|
+
const tracker = trackerMod!.createAsyncJobTracker(recorder.pi, state as never, asyncRoot, {
|
|
928
|
+
pollIntervalMs: 10,
|
|
929
|
+
});
|
|
930
|
+
tracker.ensurePoller();
|
|
931
|
+
|
|
932
|
+
await waitForCondition(
|
|
933
|
+
() => recorder.events.some((event) => event.channel === "subagent:control-event"),
|
|
934
|
+
"control event after oversized diagnostic",
|
|
935
|
+
);
|
|
936
|
+
assert.equal(errors.some((args) => String(args[0]).includes("Ignoring malformed async control event")), false);
|
|
937
|
+
} finally {
|
|
938
|
+
console.error = originalError;
|
|
939
|
+
removeTempDir(asyncRoot);
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
|
|
859
943
|
it("does not tail-skip control events for newly tracked large logs", async () => {
|
|
860
944
|
const asyncRoot = createTempDir("pi-async-job-tracker-");
|
|
861
945
|
try {
|
|
@@ -27,8 +27,8 @@ describe("async status helpers", () => {
|
|
|
27
27
|
currentStep: 1,
|
|
28
28
|
outputFile,
|
|
29
29
|
steps: [
|
|
30
|
-
{ agent: "scout", status: "complete", durationMs: 10 },
|
|
31
|
-
{ agent: "worker", status: "running", durationMs: 20 },
|
|
30
|
+
{ agent: "scout", status: "complete", durationMs: 10, description: "Inspect auth only" },
|
|
31
|
+
{ agent: "worker", status: "running", durationMs: 20, description: "Patch billing only" },
|
|
32
32
|
],
|
|
33
33
|
});
|
|
34
34
|
createAsyncDir(root, "run-b", {
|
|
@@ -47,6 +47,8 @@ describe("async status helpers", () => {
|
|
|
47
47
|
assert.equal(runs[0]?.steps.length, 2);
|
|
48
48
|
assert.equal(runs[0]?.steps[1]?.agent, "worker");
|
|
49
49
|
assert.equal(runs[0]?.steps[1]?.status, "running");
|
|
50
|
+
assert.equal(runs[0]?.steps[0]?.description, "Inspect auth only");
|
|
51
|
+
assert.equal(runs[0]?.steps[1]?.description, "Patch billing only");
|
|
50
52
|
assert.match(formatAsyncRunList(runs), /output: .*output-1\.log/);
|
|
51
53
|
} finally {
|
|
52
54
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -349,9 +349,9 @@ describe("chain clarify model display", { skip: !available ? "pi packages not av
|
|
|
349
349
|
assert.match(initial, /\[Enter\] Run • \[Esc\] Cancel/);
|
|
350
350
|
if (mode === "single") {
|
|
351
351
|
assert.match(initial, /\[w\]Output/);
|
|
352
|
-
assert.doesNotMatch(initial, /\[
|
|
352
|
+
assert.doesNotMatch(initial, /\[↑↓\/jk\] Navigate/);
|
|
353
353
|
} else {
|
|
354
|
-
assert.match(initial, /\[
|
|
354
|
+
assert.match(initial, /\[↑↓\/jk\] Navigate/);
|
|
355
355
|
}
|
|
356
356
|
if (mode === "chain") {
|
|
357
357
|
assert.match(initial, /\[w\]Output/);
|
|
@@ -368,4 +368,53 @@ describe("chain clarify model display", { skip: !available ? "pi packages not av
|
|
|
368
368
|
assert.match(component.render(84).map(stripAnsi).join("\n"), /\[b\]Background:ON/);
|
|
369
369
|
}
|
|
370
370
|
});
|
|
371
|
+
|
|
372
|
+
it("navigates non-editing chain steps with j and k", () => {
|
|
373
|
+
const agents = ["scout", "worker"].map((name) => ({
|
|
374
|
+
name,
|
|
375
|
+
description: "",
|
|
376
|
+
systemPrompt: "",
|
|
377
|
+
systemPromptMode: "replace",
|
|
378
|
+
inheritProjectContext: false,
|
|
379
|
+
inheritSkills: false,
|
|
380
|
+
source: "user",
|
|
381
|
+
filePath: `${name}.md`,
|
|
382
|
+
}));
|
|
383
|
+
const component = new ChainClarifyComponent(
|
|
384
|
+
{ requestRender() {} },
|
|
385
|
+
{ fg(_key: string, text: string) { return text; } },
|
|
386
|
+
agents,
|
|
387
|
+
["Scout", "Implement"],
|
|
388
|
+
"Task",
|
|
389
|
+
undefined,
|
|
390
|
+
agents.map(() => ({ output: false, outputMode: "inline", reads: false, progress: false, skills: [], model: undefined })),
|
|
391
|
+
[],
|
|
392
|
+
undefined,
|
|
393
|
+
[{ name: "jk-skill", source: "user" }],
|
|
394
|
+
() => {},
|
|
395
|
+
"chain",
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
component.handleInput("j");
|
|
399
|
+
assert.equal(component.selectedStep, 1);
|
|
400
|
+
component.handleInput("k");
|
|
401
|
+
assert.equal(component.selectedStep, 0);
|
|
402
|
+
|
|
403
|
+
component.handleInput("e");
|
|
404
|
+
component.handleInput("j");
|
|
405
|
+
component.handleInput("k");
|
|
406
|
+
assert.match(component.render(84).map(stripAnsi).join("\n"), /jkScout/);
|
|
407
|
+
component.handleInput("\x1b");
|
|
408
|
+
|
|
409
|
+
component.handleInput("m");
|
|
410
|
+
component.handleInput("j");
|
|
411
|
+
component.handleInput("k");
|
|
412
|
+
assert.match(component.render(84).map(stripAnsi).join("\n"), /Search: jk/);
|
|
413
|
+
component.handleInput("\x1b");
|
|
414
|
+
|
|
415
|
+
component.handleInput("s");
|
|
416
|
+
component.handleInput("j");
|
|
417
|
+
component.handleInput("k");
|
|
418
|
+
assert.match(component.render(84).map(stripAnsi).join("\n"), /Search: jk/);
|
|
419
|
+
});
|
|
371
420
|
});
|