@selesai/code 0.13.16 → 0.13.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +2 -14
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
- package/dist/bun/sandbox-env-setup.d.ts +1 -0
- package/dist/bun/sandbox-env-setup.js +3 -0
- package/dist/cli/auth-command.d.ts +23 -0
- package/dist/cli/auth-command.js +102 -0
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +11 -0
- package/dist/cli.js +2 -14
- package/dist/config.d.ts +3 -2
- package/dist/config.js +9 -4
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session.d.ts +2 -6
- package/dist/core/agent-session.js +15 -4
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/types.d.ts +16 -2
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/keybindings.d.ts +5 -0
- package/dist/core/keybindings.js +4 -0
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/model-config.d.ts +15 -0
- package/dist/core/model-config.js +3 -0
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.js +5 -2
- package/dist/core/provider-composer.js +8 -2
- package/dist/core/session-export.d.ts +3 -0
- package/dist/core/session-export.js +31 -0
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.js +40 -13
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/system-prompt.js +10 -10
- package/dist/core/tools/bash.js +3 -117
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.js +5 -163
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.js +10 -122
- package/dist/core/tools/renderers/bash.d.ts +11 -0
- package/dist/core/tools/renderers/bash.js +129 -0
- package/dist/core/tools/renderers/edit.d.ts +22 -0
- package/dist/core/tools/renderers/edit.js +166 -0
- package/dist/core/tools/renderers/find.d.ts +9 -0
- package/dist/core/tools/renderers/find.js +63 -0
- package/dist/core/tools/renderers/grep.d.ts +9 -0
- package/dist/core/tools/renderers/grep.js +68 -0
- package/dist/core/tools/renderers/index.d.ts +27 -0
- package/dist/core/tools/renderers/index.js +46 -0
- package/dist/core/tools/renderers/ls.d.ts +9 -0
- package/dist/core/tools/renderers/ls.js +57 -0
- package/dist/core/tools/renderers/read.d.ts +10 -0
- package/dist/core/tools/renderers/read.js +128 -0
- package/dist/core/tools/renderers/write.d.ts +9 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/write.js +5 -146
- package/dist/extensions/agent-browser.ts +1 -2
- package/dist/extensions/package.json +0 -1
- package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
- package/dist/extensions/pi-hermes-memory/README.md +16 -12
- package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
- package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
- package/dist/extensions/pi-hermes-memory/package.json +4 -4
- package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
- package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
- package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
- package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
- package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
- package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
- package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
- package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
- package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
- package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
- package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
- package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
- package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
- package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
- package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
- package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
- package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
- package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +16 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
- package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
- package/dist/extensions/pi-intercom/index.ts +1 -1
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +1 -6
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
- package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
- package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
- package/dist/extensions/pi-subagents/README.md +3 -3
- package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/docs/agents.md +32 -16
- package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
- package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
- package/dist/extensions/pi-subagents/docs/missions.md +2 -0
- package/dist/extensions/pi-subagents/docs/models.md +58 -1
- package/dist/extensions/pi-subagents/docs/observability.md +50 -12
- package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
- package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
- package/dist/extensions/pi-subagents/package-lock.json +747 -177
- package/dist/extensions/pi-subagents/package.json +6 -4
- package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
- package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
- package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
- package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
- package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
- package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
- package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
- package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
- package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
- package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
- package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
- package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
- package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
- package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
- package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
- package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
- package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
- package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
- package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
- package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
- package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
- package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
- package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
- package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
- package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
- package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
- package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
- package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
- package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
- package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
- package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
- package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
- package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
- package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
- package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
- package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
- package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
- package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
- package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
- package/dist/extensions/pi-web-agent/README.md +6 -5
- package/dist/extensions/pi-web-agent/src/backends/config.ts +12 -6
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +2 -2
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +4 -5
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +8 -8
- package/dist/extensions/pi-web-agent/src/extension.ts +3 -3
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +2 -2
- package/dist/extensions/pi-web-agent/src/search/tokenin.ts +14 -3
- package/dist/extensions/pi-zentui/README.md +17 -0
- package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
- package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
- package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
- package/dist/extensions/pi-zentui/package-lock.json +277 -517
- package/dist/extensions/pi-zentui/package.json +7 -4
- package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
- package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
- package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
- package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
- package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
- package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -1
- package/dist/modes/interactive/chat-viewport.d.ts +19 -0
- package/dist/modes/interactive/chat-viewport.js +27 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
- package/dist/modes/interactive/components/assistant-message.js +30 -13
- package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
- package/dist/modes/interactive/components/custom-editor.js +39 -1
- package/dist/modes/interactive/components/first-time-setup.js +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
- package/dist/modes/interactive/components/markdown-transform.js +18 -0
- package/dist/modes/interactive/components/model-selector.js +19 -17
- package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
- package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -20
- package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
- package/dist/modes/interactive/components/settings-submenu.js +164 -0
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.js +75 -18
- package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
- package/dist/modes/interactive/components/tool-execution.js +35 -33
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/interactive-mode.d.ts +13 -15
- package/dist/modes/interactive/interactive-mode.js +144 -80
- package/dist/modes/interactive/session-share.d.ts +15 -0
- package/dist/modes/interactive/session-share.js +183 -0
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
- package/dist/modes/interactive/theme/theme-json.js +129 -0
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/tui-renderer.d.ts +20 -0
- package/dist/modes/interactive/tui-renderer.js +65 -0
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/management-http.d.ts +24 -0
- package/dist/utils/management-http.js +53 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.js +8 -0
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.js +43 -21
- package/docs/docs.json +0 -4
- package/package.json +6 -6
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
- package/dist/extensions/web-agent-onboarding.test.ts +0 -415
- package/dist/extensions/web-agent-onboarding.ts +0 -222
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
- package/dist/skills/implanger/SKILL.md +0 -70
- package/dist/skills/output/SKILL.md +0 -49
- package/dist/skills/planger/SKILL.md +0 -167
- package/dist/skills/workflow/SKILL.md +0 -95
- package/docs/workflows.md +0 -18
- /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
+
import {
|
|
5
|
+
renderSupervisorReply,
|
|
6
|
+
renderSupervisorRequest,
|
|
7
|
+
type SupervisorReplyEntryData,
|
|
8
|
+
type SupervisorRequestMessageDetails,
|
|
9
|
+
} from "../../src/intercom/supervisor-ui.ts";
|
|
10
|
+
|
|
11
|
+
const identityTheme = {
|
|
12
|
+
fg: (_color: string, text: string) => text,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function renderRequest(details: SupervisorRequestMessageDetails, content: string, width = 100, expanded = true): string[] {
|
|
16
|
+
const component = renderSupervisorRequest({ content, details }, { expanded }, identityTheme as never);
|
|
17
|
+
assert.ok(component);
|
|
18
|
+
return component.render(width);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderReply(data: SupervisorReplyEntryData, width = 100): string[] {
|
|
22
|
+
const component = renderSupervisorReply({ data }, { expanded: true }, identityTheme as never);
|
|
23
|
+
assert.ok(component);
|
|
24
|
+
return component.render(width);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("native supervisor UI", () => {
|
|
28
|
+
it("renders request metadata, interview shape, and an exact reply hint", () => {
|
|
29
|
+
const requestId = "request-123456";
|
|
30
|
+
const output = renderRequest({
|
|
31
|
+
id: requestId,
|
|
32
|
+
reason: "interview_request",
|
|
33
|
+
expectsReply: true,
|
|
34
|
+
runId: "run-1845",
|
|
35
|
+
agent: "worker",
|
|
36
|
+
childIndex: 2,
|
|
37
|
+
childTarget: "child-worker",
|
|
38
|
+
replyHint: `subagent_supervisor({ action: "reply", replyTo: "${requestId}", message: "..." })`,
|
|
39
|
+
interview: { approved: "boolean", rationale: "string" },
|
|
40
|
+
requestBody: "Should this change be applied?",
|
|
41
|
+
}, [
|
|
42
|
+
"Subagent requests a structured supervisor interview.",
|
|
43
|
+
"Run: run-1845",
|
|
44
|
+
"Agent: worker",
|
|
45
|
+
"Child index: 2",
|
|
46
|
+
"Should this change be applied?",
|
|
47
|
+
`Reply with: subagent_supervisor({ action: "reply", replyTo: "${requestId}", message: "..." })`,
|
|
48
|
+
].join("\n"));
|
|
49
|
+
const text = output.join("\n");
|
|
50
|
+
|
|
51
|
+
assert.match(text, /Supervisor interview request/);
|
|
52
|
+
assert.match(text, /Reason: interview_request/);
|
|
53
|
+
assert.match(text, /Run: run-1845/);
|
|
54
|
+
assert.match(text, /Agent: worker/);
|
|
55
|
+
assert.match(text, /Child index: 2/);
|
|
56
|
+
assert.match(text, /Child target: child-worker/);
|
|
57
|
+
assert.match(text, new RegExp(`Request ID: ${requestId}`));
|
|
58
|
+
assert.match(text, new RegExp(`replyTo: "${requestId}"`));
|
|
59
|
+
assert.match(text, /Interview shape:/);
|
|
60
|
+
assert.match(text, /approved/);
|
|
61
|
+
assert.match(text, /Should this change be applied/);
|
|
62
|
+
assert.equal(text.match(/Run: run-1845/g)?.length, 1);
|
|
63
|
+
assert.equal(text.match(/Agent: worker/g)?.length, 1);
|
|
64
|
+
assert.equal(text.match(/Child index: 2/g)?.length, 1);
|
|
65
|
+
assert.equal(text.match(/Reply with:/g)?.length, 1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("omits reply hints for progress updates and renders durable reply metadata", () => {
|
|
69
|
+
const requestOutput = renderRequest({
|
|
70
|
+
id: "progress-1",
|
|
71
|
+
reason: "progress_update",
|
|
72
|
+
expectsReply: false,
|
|
73
|
+
runId: "run-progress",
|
|
74
|
+
agent: "reviewer",
|
|
75
|
+
childIndex: 0,
|
|
76
|
+
}, "Progress is continuing.");
|
|
77
|
+
assert.equal(requestOutput.join("\n").includes("Reply with:"), false);
|
|
78
|
+
|
|
79
|
+
const replyOutput = renderReply({
|
|
80
|
+
requestId: "request-1",
|
|
81
|
+
reason: "need_decision",
|
|
82
|
+
runId: "run-1",
|
|
83
|
+
agent: "worker",
|
|
84
|
+
childIndex: 1,
|
|
85
|
+
childTarget: "child-worker",
|
|
86
|
+
message: "Approved; continue.",
|
|
87
|
+
createdAt: 123,
|
|
88
|
+
});
|
|
89
|
+
const text = replyOutput.join("\n");
|
|
90
|
+
assert.match(text, /Supervisor reply to child/);
|
|
91
|
+
assert.match(text, /Reply to: request-1/);
|
|
92
|
+
assert.match(text, /Approved; continue\./);
|
|
93
|
+
assert.match(text, /child-worker/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("ignores malformed request and reply metadata", () => {
|
|
97
|
+
assert.equal(renderSupervisorRequest({ content: "request", details: { id: "request-1", expectsReply: true, replyHint: ["bad"] } }, { expanded: true }, identityTheme as never), undefined);
|
|
98
|
+
assert.equal(renderSupervisorReply({ data: { requestId: "request-1", runId: "run-1", agent: "worker", childIndex: 0, message: ["bad"], createdAt: 123 } }, { expanded: true }, identityTheme as never), undefined);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("sanitizes and bounds collapsed untrusted content at narrow widths", () => {
|
|
102
|
+
const output = renderRequest({
|
|
103
|
+
id: "request-long",
|
|
104
|
+
reason: "need_decision",
|
|
105
|
+
expectsReply: true,
|
|
106
|
+
runId: "run-long",
|
|
107
|
+
agent: "worker",
|
|
108
|
+
childIndex: 0,
|
|
109
|
+
}, `\u0001${"untrusted ".repeat(400)}`, 24, false);
|
|
110
|
+
assert.ok(output.length <= 39, `expected bounded rows, got ${output.length}`);
|
|
111
|
+
for (const line of output) assert.ok(visibleWidth(line) <= 24, `line exceeds width: ${line}`);
|
|
112
|
+
const text = output.join("\n");
|
|
113
|
+
assert.match(text, /truncated/);
|
|
114
|
+
assert.equal(text.includes("\u0001"), false);
|
|
115
|
+
assert.match(text, /\[U\+0001\]/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("keeps long blocking requests visible when expanded", () => {
|
|
119
|
+
const content = `${Array.from({ length: 45 }, (_, index) => `decision line ${index + 1}`).join("\n")}\n${"body-detail ".repeat(800)}END_BODY`;
|
|
120
|
+
const output = renderRequest({
|
|
121
|
+
id: "request-expanded",
|
|
122
|
+
reason: "need_decision",
|
|
123
|
+
expectsReply: true,
|
|
124
|
+
runId: "run-expanded",
|
|
125
|
+
agent: "worker",
|
|
126
|
+
childIndex: 0,
|
|
127
|
+
interview: { questions: Array.from({ length: 12 }, (_, index) => ({ id: `q${index + 1}`, question: `${"Question detail ".repeat(40)}${index === 11 ? "END_INTERVIEW" : index + 1}` })) },
|
|
128
|
+
}, content, 120, true);
|
|
129
|
+
const text = output.join("\n");
|
|
130
|
+
|
|
131
|
+
assert.ok(output.length > 39, `expected expanded rows, got ${output.length}`);
|
|
132
|
+
assert.match(text, /decision line 45/);
|
|
133
|
+
assert.match(text, /END_BODY/);
|
|
134
|
+
assert.match(text, /END_INTERVIEW/);
|
|
135
|
+
assert.doesNotMatch(text, /\[truncated\]/);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -2,8 +2,6 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_TOOL_BUDGET_BLOCK,
|
|
5
|
-
decodeToolBudgetEnv,
|
|
6
|
-
encodeToolBudgetEnv,
|
|
7
5
|
initialToolBudgetState,
|
|
8
6
|
shouldBlockToolForBudget,
|
|
9
7
|
toolBudgetBlockedMessage,
|
|
@@ -41,14 +39,6 @@ describe("tool-budget module", () => {
|
|
|
41
39
|
assert.equal(validateToolBudgetConfig({ hard: 4, block: [""] }).error, "toolBudget.block must contain non-empty tool names.");
|
|
42
40
|
});
|
|
43
41
|
|
|
44
|
-
it("serializes and decodes env config", () => {
|
|
45
|
-
const budget = { soft: 2, hard: 4, block: ["read"] };
|
|
46
|
-
assert.deepEqual(decodeToolBudgetEnv(encodeToolBudgetEnv(budget)), budget);
|
|
47
|
-
const zeroBudget = { hard: 0, block: "*" as const };
|
|
48
|
-
assert.throws(() => decodeToolBudgetEnv(encodeToolBudgetEnv(zeroBudget)), /SELESAI_SUBAGENT_TOOL_BUDGET\.hard must be an integer >= 1/);
|
|
49
|
-
assert.deepEqual(decodeToolBudgetEnv(encodeToolBudgetEnv(zeroBudget), { allowZero: true }), zeroBudget);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
42
|
it("tracks state and block decisions", () => {
|
|
53
43
|
const budget = { soft: 2, hard: 3, block: ["read"] };
|
|
54
44
|
assert.deepEqual(initialToolBudgetState(budget), { soft: 2, hard: 3, block: ["read"], toolCount: 0, outcome: "within-budget" });
|
|
@@ -64,4 +54,4 @@ describe("tool-budget module", () => {
|
|
|
64
54
|
assert.match(toolBudgetSoftNudge(budget, 2), /soft limit reached after 2 tool calls/);
|
|
65
55
|
assert.match(toolBudgetBlockedMessage(budget, "read", 4), /'read' tool is blocked/);
|
|
66
56
|
});
|
|
67
|
-
});
|
|
57
|
+
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
FULL_SUBAGENT_TOOL_DESCRIPTION,
|
|
13
13
|
SUBAGENT_SAFETY_GUIDANCE,
|
|
14
14
|
} from "../../src/extension/tool-description.ts";
|
|
15
|
-
import { SUBAGENT_CHILD_ENV
|
|
15
|
+
import { SUBAGENT_CHILD_ENV } from "../../src/runs/shared/child-runtime-config.ts";
|
|
16
16
|
|
|
17
17
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
18
18
|
|
|
@@ -23,7 +23,6 @@ function escapeRegex(value: string): string {
|
|
|
23
23
|
function parentToolEnv(agentDir?: string): NodeJS.ProcessEnv {
|
|
24
24
|
const env = { ...process.env };
|
|
25
25
|
delete env[SUBAGENT_CHILD_ENV];
|
|
26
|
-
delete env[SUBAGENT_FANOUT_CHILD_ENV];
|
|
27
26
|
if (agentDir) env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
28
27
|
return env;
|
|
29
28
|
}
|
|
@@ -39,6 +38,13 @@ describe("registered subagent tool description", () => {
|
|
|
39
38
|
assert.equal(metadata.promptGuidelines, undefined);
|
|
40
39
|
});
|
|
41
40
|
|
|
41
|
+
it("explains managed worktree baseRef restrictions in every built-in description", () => {
|
|
42
|
+
for (const description of [FULL_SUBAGENT_TOOL_DESCRIPTION, COMPACT_SUBAGENT_TOOL_DESCRIPTION]) {
|
|
43
|
+
assert.match(description, /baseRef.*HEAD.*named ref.*40\/64-character commit IDs.*revision expressions.*unsupported/);
|
|
44
|
+
assert.match(description, /Omitted baseRef defaults to HEAD resolved at worktree allocation/);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
42
48
|
it("keeps the full description when configured", () => {
|
|
43
49
|
const description = buildSubagentToolDescription({ toolDescriptionMode: "full" });
|
|
44
50
|
assert.equal(buildSubagentToolPromptMetadata({ toolDescriptionMode: "full" }).promptSnippet, undefined);
|
|
@@ -77,6 +83,7 @@ describe("registered subagent tool description", () => {
|
|
|
77
83
|
assert.match(description, /suffix on the model string.*off\/minimal\/low\/medium\/high\/xhigh\/max/i);
|
|
78
84
|
assert.match(description, /suffix wins over the agent's thinking default/i);
|
|
79
85
|
assert.match(description, /watchdog\.configure' and is ignored on dispatch/i);
|
|
86
|
+
assert.match(description, /lane infrastructure blocker.*explicit owner approval/i);
|
|
80
87
|
});
|
|
81
88
|
|
|
82
89
|
it("offers a compact mode that keeps the two-tier contract and safety guidance", () => {
|
|
@@ -108,6 +115,7 @@ describe("registered subagent tool description", () => {
|
|
|
108
115
|
assert.match(description, /Per-run thinking is a suffix on the model string.*off\/minimal\/low\/medium\/high\/xhigh\/max/i);
|
|
109
116
|
assert.match(description, /suffix wins over the agent's thinking default/i);
|
|
110
117
|
assert.match(description, /watchdog\.configure' and is ignored on dispatch/i);
|
|
118
|
+
assert.match(description, /lane infrastructure blocker.*explicit owner approval/i);
|
|
111
119
|
assert.ok(description.length < FULL_SUBAGENT_TOOL_DESCRIPTION.length);
|
|
112
120
|
});
|
|
113
121
|
|
|
@@ -153,6 +161,18 @@ describe("registered subagent tool description", () => {
|
|
|
153
161
|
assert.match(description, /status\.json/);
|
|
154
162
|
});
|
|
155
163
|
|
|
164
|
+
it("deduplicates compact placeholder safety guidance in custom descriptions", () => {
|
|
165
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-compact-custom-"));
|
|
166
|
+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-agent-"));
|
|
167
|
+
fs.mkdirSync(path.join(cwd, ".selesai"), { recursive: true });
|
|
168
|
+
fs.writeFileSync(path.join(cwd, ".selesai", "subagent-tool-description.md"), "{{compactDescription}}", "utf-8");
|
|
169
|
+
|
|
170
|
+
const description = buildSubagentToolDescription({ toolDescriptionMode: "custom" }, { cwd, agentDir });
|
|
171
|
+
|
|
172
|
+
assert.equal(description.split("lane infrastructure blocker").length - 1, 1);
|
|
173
|
+
assert.ok(description.endsWith(SUBAGENT_SAFETY_GUIDANCE));
|
|
174
|
+
});
|
|
175
|
+
|
|
156
176
|
it("keeps mandatory safety guidance last when custom prose embeds it before an override", () => {
|
|
157
177
|
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-injection-"));
|
|
158
178
|
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-agent-"));
|
|
@@ -261,7 +281,7 @@ describe("registered subagent tool description", () => {
|
|
|
261
281
|
fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify(config), "utf-8");
|
|
262
282
|
}
|
|
263
283
|
|
|
264
|
-
it("registers full, compact, custom, and fallback descriptions from extension config", () => {
|
|
284
|
+
it("registers split, full, compact, custom, and fallback descriptions from extension config", () => {
|
|
265
285
|
const defaultAgentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-default-"));
|
|
266
286
|
writeExtensionConfig(defaultAgentDir, {});
|
|
267
287
|
const defaultTool = readRegisteredTool(defaultAgentDir);
|
|
@@ -3,10 +3,16 @@ import { describe, it } from "node:test";
|
|
|
3
3
|
import { toWaitCompletion } from "../../src/runs/background/wait-completions.ts";
|
|
4
4
|
|
|
5
5
|
describe("workflow wait completion projection", () => {
|
|
6
|
+
it("omits absent and malformed receipt references", () => {
|
|
7
|
+
for (const workflowReceipt of [undefined, null, [], "path", { path: "" }, { path: 42 }]) {
|
|
8
|
+
assert.equal("workflowReceiptPath" in toWaitCompletion({ workflowReceipt }, "run"), false);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
6
11
|
it("retains the bounded workflow-child summary and excludes result output", () => {
|
|
7
12
|
const completion = toWaitCompletion({
|
|
8
13
|
agent: "workflow",
|
|
9
14
|
mode: "workflow",
|
|
15
|
+
workflowReceipt: { path: "/opaque/published-receipt.json", receipt: { output: "must not be copied" } },
|
|
10
16
|
state: "complete",
|
|
11
17
|
success: true,
|
|
12
18
|
workflowChildren: {
|
|
@@ -29,6 +35,7 @@ describe("workflow wait completion projection", () => {
|
|
|
29
35
|
}, "workflow-1");
|
|
30
36
|
|
|
31
37
|
assert.equal(completion.workflowChildren?.children[0]?.childId, "review");
|
|
38
|
+
assert.equal(completion.workflowReceiptPath, "/opaque/published-receipt.json");
|
|
32
39
|
assert.deepEqual(completion.results?.[0]?.usage, { input: 10, output: 2, cacheRead: 30, cacheWrite: 0, cost: 0.04, turns: 1 });
|
|
33
40
|
assert.equal(completion.results?.[0]?.sessionFile, "/sessions/run-1.jsonl");
|
|
34
41
|
assert.doesNotMatch(JSON.stringify(completion), /must not be copied/);
|
|
@@ -107,4 +114,4 @@ describe("workflow wait completion projection", () => {
|
|
|
107
114
|
it("rejects a summary bound to another completion", () => {
|
|
108
115
|
assert.throws(() => toWaitCompletion({ workflowChildren: { version: 1, parentToolCallId: "tool", workflowRunId: "other", inventoryComplete: true, workflowState: "completed", children: [] } }, "run"), /does not match its completion run id/);
|
|
109
116
|
});
|
|
110
|
-
});
|
|
117
|
+
});
|
|
@@ -77,6 +77,24 @@ describe("watchdog tool actions", () => {
|
|
|
77
77
|
assert.equal(result.isError, undefined);
|
|
78
78
|
assert.equal(runtime.getSnapshot(tempProject).config.main.model, "anthropic/claude-opus-4-8");
|
|
79
79
|
assert.equal(runtime.getSnapshot(tempProject).config.main.thinking, "low");
|
|
80
|
+
const off = handleWatchdogToolAction("watchdog.configure", { thinking: false }, ctx, runtime);
|
|
81
|
+
assert.equal(off.isError, undefined);
|
|
82
|
+
assert.equal(runtime.getSnapshot(tempProject).config.main.thinking, false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("rejects transport-permitted true thinking without changing session or settings", () => {
|
|
86
|
+
const runtime = new MainWatchdogRuntime({ cwd: tempProject });
|
|
87
|
+
const ctx = createCtx({ provider: "openai-codex", id: "gpt-5.5" });
|
|
88
|
+
const before = runtime.getSnapshot(tempProject).config.main;
|
|
89
|
+
for (const scope of ["session", "user"]) {
|
|
90
|
+
// SAFETY: Deliberately pass invalid tool JSON to prove the false-only runtime contract.
|
|
91
|
+
const result = handleWatchdogToolAction("watchdog.configure", { scope, thinking: true as never }, ctx, runtime);
|
|
92
|
+
assert.equal(result.isError, true);
|
|
93
|
+
assert.match(text(result), /Unsupported watchdog thinking 'true'/);
|
|
94
|
+
assert.deepEqual(runtime.getSnapshot(tempProject).config.main, before);
|
|
95
|
+
}
|
|
96
|
+
assert.equal(fs.existsSync(path.join(tempHome, ".selesai", "agent", "settings.json")), false);
|
|
97
|
+
assert.equal(fs.existsSync(path.join(tempProject, ".selesai", "settings.json")), false);
|
|
80
98
|
});
|
|
81
99
|
|
|
82
100
|
it("persists the recommended model when user scope is explicit", () => {
|
|
@@ -88,4 +106,4 @@ describe("watchdog tool actions", () => {
|
|
|
88
106
|
assert.equal(settings.subagents.watchdog.main.model, "openai-codex/gpt-5.5");
|
|
89
107
|
assert.equal(settings.subagents.watchdog.main.thinking, "high");
|
|
90
108
|
});
|
|
91
|
-
});
|
|
109
|
+
});
|
|
@@ -7,6 +7,7 @@ import { describe, it } from "node:test";
|
|
|
7
7
|
import { buildWorkflowChatProgressRows, isSameGitRepository, resolveWorkflowChatProgress } from "../../src/workflows/chat-progress.ts";
|
|
8
8
|
import { renderSubagentResult } from "../../src/tui/render.ts";
|
|
9
9
|
import { bindMissionWorkflowChildAsyncLaunch, createSubagentExecutor, foregroundResultIntercomStatus, missionWorkflowChildStatus, runMissionWorkflowChild, shouldSuppressRoutineResultIntercom } from "../../src/runs/foreground/subagent-executor.ts";
|
|
10
|
+
import { encodeIndexSegment } from "../../src/runs/background/index-segment.ts";
|
|
10
11
|
import { readMissionBinding } from "../../src/missions/lifecycle.ts";
|
|
11
12
|
import { createMission, readMission } from "../../src/missions/store.ts";
|
|
12
13
|
import { DIRS, type Details, type SingleResult, type SubagentState } from "../../src/shared/types.ts";
|
|
@@ -110,12 +111,76 @@ describe("workflow chat progress policy", () => {
|
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
describe("workflow chat progress rendering", () => {
|
|
113
|
-
|
|
114
|
+
for (const scenario of ["explicit off", "non-repository auto", "cross-repository auto"] as const) {
|
|
115
|
+
it(`emits foreground lifecycle updates before settlement with ${scenario}`, async () => {
|
|
116
|
+
const root = scenario === "non-repository auto"
|
|
117
|
+
? fs.mkdtempSync(path.join(os.tmpdir(), "pi-workflow-progress-headless-"))
|
|
118
|
+
: createRepo("pi-workflow-progress-off-");
|
|
119
|
+
const other = scenario === "cross-repository auto" ? createRepo("pi-workflow-progress-cross-") : undefined;
|
|
120
|
+
try {
|
|
121
|
+
const updates: Details[] = [];
|
|
122
|
+
let settled = false;
|
|
123
|
+
const result = await createExecutor().execute(
|
|
124
|
+
"wf-headless",
|
|
125
|
+
{
|
|
126
|
+
workflowScript: `return await runs.run("scout", { agent: "missing-agent", task: "scan" });`,
|
|
127
|
+
async: false,
|
|
128
|
+
chatProgress: scenario === "explicit off" ? "off" : "auto",
|
|
129
|
+
...(other ? { cwd: other } : {}),
|
|
130
|
+
},
|
|
131
|
+
new AbortController().signal,
|
|
132
|
+
(update) => {
|
|
133
|
+
assert.equal(settled, false);
|
|
134
|
+
updates.push(structuredClone(update.details));
|
|
135
|
+
},
|
|
136
|
+
ctx(root),
|
|
137
|
+
).then((result) => { settled = true; return result; });
|
|
138
|
+
assert.equal(result.isError, true);
|
|
139
|
+
assert.ok(updates.every((details) => details.chatProgress?.mode === "off"));
|
|
140
|
+
const started = updates.find((details) => details.workflowChildren?.children[0]?.state === "running");
|
|
141
|
+
assert.ok(started, "expected running child inventory before failure");
|
|
142
|
+
assert.equal(started.mode, "workflow");
|
|
143
|
+
assert.equal(started.runId, result.details.runId);
|
|
144
|
+
assert.equal(started.workflowChildren?.parentToolCallId, "wf-headless");
|
|
145
|
+
assert.equal(started.workflowChildren?.workflowRunId, result.details.runId);
|
|
146
|
+
assert.equal(started.workflowChildren?.inventoryComplete, false);
|
|
147
|
+
assert.equal(started.workflow?.trace[0]?.key, "scout");
|
|
148
|
+
assert.ok(updates.some((details) => details.workflow?.trace.some((entry) => entry.state === "failed")));
|
|
149
|
+
assert.equal(result.details.workflowChildren?.inventoryComplete, true);
|
|
150
|
+
} finally {
|
|
151
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
152
|
+
if (other) fs.rmSync(other, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
it("emits headless workflow values before returning final output without an update callback requirement", async () => {
|
|
158
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-workflow-progress-emits-"));
|
|
159
|
+
try {
|
|
160
|
+
const updates: Details[] = [];
|
|
161
|
+
const params = { workflowScript: `emit({ phase: "checking" }); return "done";`, async: false, chatProgress: "off" as const };
|
|
162
|
+
const result = await createExecutor().execute("wf-emits", params, undefined, (update) => {
|
|
163
|
+
assert.equal(update.details.workflow?.value, undefined);
|
|
164
|
+
updates.push(structuredClone(update.details));
|
|
165
|
+
}, ctx(root));
|
|
166
|
+
assert.ok(updates.some((details) => details.workflow?.emits.some((value) => (value as { phase?: string }).phase === "checking")));
|
|
167
|
+
assert.equal(result.details.workflow?.value, "done");
|
|
168
|
+
assert.equal(result.isError, undefined);
|
|
169
|
+
const withoutCallback = await createExecutor().execute("wf-no-callback", params, undefined, undefined, ctx(root));
|
|
170
|
+
assert.equal(withoutCallback.details.workflow?.value, "done");
|
|
171
|
+
} finally {
|
|
172
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("emits live-card updates with bounded workflow ids", async () => {
|
|
114
177
|
const repo = createRepo("pi-workflow-progress-executor-");
|
|
178
|
+
const toolCallId = `wf-live-${"x".repeat(300)}`;
|
|
179
|
+
const workflowRunId = encodeIndexSegment(toolCallId);
|
|
115
180
|
try {
|
|
116
181
|
const updates: Array<{ details?: Details }> = [];
|
|
117
182
|
const result = await createExecutor().execute(
|
|
118
|
-
|
|
183
|
+
toolCallId,
|
|
119
184
|
{ workflowScript: `return await runs.run("scout", { agent: "missing-agent", task: "scan", phase: "Validation", label: "Find renderer seam" });`, async: false },
|
|
120
185
|
new AbortController().signal,
|
|
121
186
|
(update) => updates.push(update),
|
|
@@ -124,16 +189,19 @@ describe("workflow chat progress rendering", () => {
|
|
|
124
189
|
assert.equal(result.isError, true);
|
|
125
190
|
const liveUpdate = updates.find((update) => update.details?.chatProgress?.mode === "live-card");
|
|
126
191
|
assert.ok(liveUpdate, "expected a live-card update");
|
|
192
|
+
assert.equal(liveUpdate.details?.runId, workflowRunId);
|
|
193
|
+
assert.equal(liveUpdate.details?.workflowChildren?.workflowRunId, workflowRunId);
|
|
127
194
|
assert.equal(liveUpdate.details?.workflow?.trace[0]?.key, "scout");
|
|
128
195
|
assert.equal(liveUpdate.details?.workflow?.trace[0]?.phase, "Validation");
|
|
129
196
|
assert.equal(liveUpdate.details?.workflow?.trace[0]?.label, "Find renderer seam");
|
|
130
197
|
const ledgerChild = result.details.mission?.workflowChildren[0];
|
|
131
198
|
assert.equal(ledgerChild?.key, "scout");
|
|
132
|
-
assert.equal(ledgerChild?.workflowRunId,
|
|
199
|
+
assert.equal(ledgerChild?.workflowRunId, workflowRunId);
|
|
133
200
|
assert.equal(ledgerChild?.agent, "missing-agent");
|
|
134
201
|
assert.equal(ledgerChild?.phase, "Validation");
|
|
135
202
|
assert.equal(ledgerChild?.status, "failed");
|
|
136
203
|
assert.equal(ledgerChild?.heartbeat?.status, "failed");
|
|
204
|
+
assert.equal(result.details.workflowChildren?.workflowRunId, workflowRunId);
|
|
137
205
|
} finally {
|
|
138
206
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
139
207
|
}
|
|
@@ -567,4 +635,4 @@ describe("workflow chat progress rendering", () => {
|
|
|
567
635
|
assert.match(text, /final answer/);
|
|
568
636
|
assert.doesNotMatch(text, /workflow wf_done/);
|
|
569
637
|
});
|
|
570
|
-
});
|
|
638
|
+
});
|
|
@@ -2,7 +2,7 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
4
|
import { formatWorkflowChecklistBottleneck, formatWorkflowChecklistText, projectWorkflowChecklist } from "../../src/workflows/workflow-checklist.ts";
|
|
5
|
-
import type {
|
|
5
|
+
import type { HostStepNode, WorkflowGraphSnapshot } from "../../src/shared/types.ts";
|
|
6
6
|
|
|
7
7
|
function graph(): WorkflowGraphSnapshot {
|
|
8
8
|
return {
|
|
@@ -50,7 +50,7 @@ test("workflow checklist fuses graph phases with loaded child state without dupl
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
test("workflow checklist preserves explicit host monitor verdicts and trace lane identity", () => {
|
|
53
|
-
const host:
|
|
53
|
+
const host: HostStepNode = {
|
|
54
54
|
version: 1,
|
|
55
55
|
kind: "host-step",
|
|
56
56
|
monitorKind: "ci",
|
|
@@ -81,8 +81,22 @@ test("workflow checklist preserves explicit host monitor verdicts and trace lane
|
|
|
81
81
|
assert.match(formatWorkflowChecklistBottleneck(projection.bottleneck) ?? "", /CI/);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
test("workflow checklist renders a failed item error only once when item details are included", () => {
|
|
85
|
+
const projection = projectWorkflowChecklist({
|
|
86
|
+
steps: [{ workflowKey: "review", agent: "reviewer", status: "failed", error: "review failed with details" }],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const expanded = formatWorkflowChecklistText(projection).join("\n");
|
|
90
|
+
assert.equal(expanded.match(/review failed with details/g)?.length, 1);
|
|
91
|
+
assert.doesNotMatch(expanded, /bottleneck .*error:review failed with details/);
|
|
92
|
+
|
|
93
|
+
const collapsed = formatWorkflowChecklistText(projection, "", { includeItems: false }).join("\n");
|
|
94
|
+
assert.equal(collapsed.match(/review failed with details/g)?.length, 1);
|
|
95
|
+
assert.match(collapsed, /bottleneck .*error:review failed with details/);
|
|
96
|
+
});
|
|
97
|
+
|
|
84
98
|
test("workflow checklist counts one host monitor when host status and trace share an id", () => {
|
|
85
|
-
const host:
|
|
99
|
+
const host: HostStepNode = {
|
|
86
100
|
version: 1,
|
|
87
101
|
kind: "host-step",
|
|
88
102
|
monitorKind: "ci",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { it } from "node:test";
|
|
3
|
+
import { workflowChildActivity, workflowChildProgress, workflowChildSummary, parseWorkflowChildSummary } from "../../src/workflows/workflow-child-summary.ts";
|
|
4
|
+
import type { AgentProgress } from "../../src/shared/types.ts";
|
|
5
|
+
|
|
6
|
+
const progress = { agent: "worker", status: "running", model: "provider/model", thinking: "high", currentTool: "read", currentToolStartedAt: 100, lastActivityAt: 120, durationMs: 20, toolCount: 1, turnCount: 2, tokens: 10, inputTokens: 8, outputTokens: 2, task: "private", currentToolArgs: "private", recentOutput: ["private"] } as AgentProgress;
|
|
7
|
+
const input = { parentToolCallId: "tool", workflowRunId: "wf", workflowState: "running" as const, inventoryComplete: false, trace: ["a", "b"].map((key) => ({ operation: "run" as const, key, state: "started" as const })) };
|
|
8
|
+
|
|
9
|
+
it("projects independent bounded snapshots by stable key and clears terminal activity", () => {
|
|
10
|
+
const snapshots = new Map([["b", workflowChildProgress(progress)], ["a", workflowChildProgress({ ...progress, currentTool: "bash", toolCount: 9 })]]);
|
|
11
|
+
const summary = workflowChildSummary({ ...input, progress: snapshots });
|
|
12
|
+
assert.deepEqual(summary.children.map((row) => [row.childId, row.activity?.currentTool, row.activity?.toolCount]), [["a", "bash", 9], ["b", "read", 1]]);
|
|
13
|
+
assert.equal(summary.children[0].model, progress.model);
|
|
14
|
+
assert.equal(summary.children[0].thinking, "high");
|
|
15
|
+
assert.equal(JSON.stringify(summary).includes("private"), false);
|
|
16
|
+
assert.deepEqual(parseWorkflowChildSummary(summary), summary);
|
|
17
|
+
snapshots.set("a", workflowChildProgress({ ...progress, currentTool: undefined, currentToolStartedAt: undefined }));
|
|
18
|
+
assert.equal(workflowChildSummary({ ...input, progress: snapshots }).children[0].activity?.currentTool, undefined);
|
|
19
|
+
assert.equal(summary.children[0].activity?.currentTool, "bash", "published snapshots stay immutable");
|
|
20
|
+
assert.ok(workflowChildSummary({ ...input, inventoryComplete: true, workflowState: "failed", progress: snapshots }).children.every((row) => row.activity === undefined));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("bounds UTF-8 and JSON bytes and rejects malformed activity strictly", () => {
|
|
24
|
+
assert.equal(workflowChildActivity({ currentTool: "界".repeat(86) }).currentTool, undefined);
|
|
25
|
+
assert.equal(workflowChildActivity({ currentTool: "界".repeat(85) }).currentTool?.length, 85);
|
|
26
|
+
const activity = workflowChildActivity({ ...progress, currentTool: "\u0000".repeat(256), tokens: Number.MAX_VALUE });
|
|
27
|
+
assert.ok(Buffer.byteLength(JSON.stringify(activity)) < 2048);
|
|
28
|
+
assert.deepEqual(workflowChildActivity({ tokens: NaN, toolCount: -1, durationMs: Infinity }), {});
|
|
29
|
+
const summary = workflowChildSummary({ ...input, progress: new Map([["a", workflowChildProgress(progress)]]) });
|
|
30
|
+
for (const invalid of [null, [], { tokens: -1 }, { tokens: Infinity }, { tokens: "1" }, { currentTool: "界".repeat(86) }, { recentOutput: [] }, { currentToolArgs: "secret" }]) {
|
|
31
|
+
assert.throws(() => parseWorkflowChildSummary({ ...summary, children: [{ ...summary.children[0], activity: invalid }] }), /activity/);
|
|
32
|
+
}
|
|
33
|
+
assert.throws(() => parseWorkflowChildSummary({ ...summary, children: [{ ...summary.children[0], state: "completed" }] }), /running state/);
|
|
34
|
+
});
|
|
@@ -5,6 +5,7 @@ import { describe, it } from "node:test";
|
|
|
5
5
|
import { applyDetachedChildToPausedWorkflow, promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "../../src/runs/foreground/workflow-detach-reconcile.ts";
|
|
6
6
|
import { DIRS, type AsyncStatus, type IntercomEventBus, type SubagentState } from "../../src/shared/types.ts";
|
|
7
7
|
import { buildWorkflowReceipt, writeWorkflowReceipt } from "../../src/workflows/workflow-receipt.ts";
|
|
8
|
+
import { buildCompletionDetails, formatSingleCompletion, parseSubagentNotifyContent, type CompletionNotification } from "../../src/runs/background/notify.ts";
|
|
8
9
|
|
|
9
10
|
function pausedWorkflow(childRunId: string, extra?: Partial<NonNullable<AsyncStatus["steps"]>[number]>): AsyncStatus {
|
|
10
11
|
return {
|
|
@@ -176,10 +177,12 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
|
|
|
176
177
|
const state = {
|
|
177
178
|
asyncJobs: new Map([[workflowRunId, { asyncId: workflowRunId, asyncDir, status: "paused" as const }]]),
|
|
178
179
|
} as SubagentState;
|
|
180
|
+
let emitted: CompletionNotification | undefined;
|
|
179
181
|
assert.equal(reconcileDetachedWorkflowChildCompletion({
|
|
180
182
|
state,
|
|
181
183
|
workflowRunId,
|
|
182
184
|
childRunId: "child-1",
|
|
185
|
+
events: { emit: (_name, payload) => { emitted = payload as CompletionNotification; } } as IntercomEventBus,
|
|
183
186
|
result: { index: 0, agent: "worker", task: `Write your findings to exactly this path: ${requestedPath}`, exitCode: 0, sessionFile, savedOutputPath: savedPath, usage: { input: 100, output: 50, cacheRead: 25, cacheWrite: 5, cost: 0.001, turns: 1 } },
|
|
184
187
|
}), true);
|
|
185
188
|
const published = JSON.parse(fs.readFileSync(path.join(DIRS.results, `${workflowRunId}.json`), "utf-8")) as { state?: string; success?: boolean; summary?: string; error?: string; sessionId?: string; workflowResolution?: string; recovery?: unknown[]; results?: Array<{ outputReference?: string; outputPathMapping?: unknown }>; workflowReceipt?: { receipt?: { state?: string; workflowResolution?: string; recovery?: unknown[]; entries?: Record<string, { resumability?: { state?: string; reason?: string } }> } } };
|
|
@@ -200,6 +203,11 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
|
|
|
200
203
|
assert.equal(published.workflowReceipt?.receipt?.workflowResolution, "settled-awaiting-resume");
|
|
201
204
|
assert.deepEqual(published.workflowReceipt?.receipt?.recovery, published.recovery);
|
|
202
205
|
assert.equal(published.workflowReceipt?.receipt?.entries?.detaches?.resumability?.state, "resumable");
|
|
206
|
+
assert.ok(emitted);
|
|
207
|
+
const notification = parseSubagentNotifyContent(formatSingleCompletion(buildCompletionDetails(emitted)));
|
|
208
|
+
const publishedPath = (published.workflowReceipt as { path: string }).path;
|
|
209
|
+
assert.equal(notification?.workflowReceiptPath, publishedPath);
|
|
210
|
+
assert.deepEqual(JSON.parse(fs.readFileSync(notification.workflowReceiptPath!, "utf8")), published.workflowReceipt?.receipt);
|
|
203
211
|
const events = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8");
|
|
204
212
|
assert.match(events, /"type":"subagent.workflow.completed"/);
|
|
205
213
|
assert.match(events, /"state":"failed"/);
|
|
@@ -578,7 +586,8 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
|
|
|
578
586
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
579
587
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
580
588
|
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
581
|
-
const status = { ...pausedWorkflow("child-1"), runId: workflowRunId, sessionId: "session-1" };
|
|
589
|
+
const status = { ...pausedWorkflow("child-1"), runId: workflowRunId, sessionId: "session-1", workflowReceiptPath: "/stale/receipt.json" };
|
|
590
|
+
fs.writeFileSync(path.join(DIRS.results, `${workflowRunId}.json`), JSON.stringify({ workflowReceipt: { path: status.workflowReceiptPath, receipt: {} } }));
|
|
582
591
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify(status), "utf-8");
|
|
583
592
|
fs.writeFileSync(path.join(asyncDir, "workflow-receipt.json"), JSON.stringify({ version: 1, workflowRunId, state: "paused", createdAt: 1, entries: { detaches: { key: "wrong" } } }), "utf-8");
|
|
584
593
|
const state = {
|
|
@@ -597,6 +606,7 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
|
|
|
597
606
|
assert.equal(published.success, false);
|
|
598
607
|
assert.match(published.error ?? "", /evidence-persistence-failed/);
|
|
599
608
|
assert.equal(published.workflowReceipt, undefined);
|
|
609
|
+
assert.equal(JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf8")).workflowReceiptPath, undefined);
|
|
600
610
|
const events = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8");
|
|
601
611
|
assert.match(events, /"type":"subagent.workflow.receipt_write_failed"/);
|
|
602
612
|
assert.match(events, /"type":"subagent.workflow.completed"/);
|
|
@@ -774,4 +784,4 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
|
|
|
774
784
|
assert.equal(published.workflowResolution, undefined);
|
|
775
785
|
assert.equal(published.recovery, undefined);
|
|
776
786
|
});
|
|
777
|
-
});
|
|
787
|
+
});
|
|
@@ -4,7 +4,7 @@ import { prepareWorkflowLaunchParams, promptAuditRedoParams, resolveRevivalContr
|
|
|
4
4
|
import { resolveControlConfig } from "../../src/runs/shared/subagent-control.ts";
|
|
5
5
|
|
|
6
6
|
describe("workflow launch params", () => {
|
|
7
|
-
it("
|
|
7
|
+
it("preserves omitted workflow child async defaults and awaits background resolution", () => {
|
|
8
8
|
assert.deepEqual(
|
|
9
9
|
prepareWorkflowLaunchParams(
|
|
10
10
|
{},
|
|
@@ -15,13 +15,52 @@ describe("workflow launch params", () => {
|
|
|
15
15
|
{
|
|
16
16
|
agent: "worker",
|
|
17
17
|
task: "Run",
|
|
18
|
-
|
|
18
|
+
workflowAwaitAsync: true,
|
|
19
19
|
workflowParentRunId: "workflow-run",
|
|
20
20
|
workflowKey: "run",
|
|
21
21
|
},
|
|
22
22
|
);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
it("forwards workflow baseRef only to launches where it can affect allocation", () => {
|
|
26
|
+
assert.equal(
|
|
27
|
+
prepareWorkflowLaunchParams(
|
|
28
|
+
{ baseRef: "refs/heads/release" },
|
|
29
|
+
{ agent: "worker", task: "Run" },
|
|
30
|
+
"workflow-run",
|
|
31
|
+
"run",
|
|
32
|
+
).baseRef,
|
|
33
|
+
"refs/heads/release",
|
|
34
|
+
);
|
|
35
|
+
assert.equal(
|
|
36
|
+
prepareWorkflowLaunchParams(
|
|
37
|
+
{ baseRef: "refs/heads/release" },
|
|
38
|
+
{ resume: "retained-run", task: "Continue" },
|
|
39
|
+
"workflow-run",
|
|
40
|
+
"resume",
|
|
41
|
+
).baseRef,
|
|
42
|
+
undefined,
|
|
43
|
+
);
|
|
44
|
+
assert.equal(
|
|
45
|
+
prepareWorkflowLaunchParams(
|
|
46
|
+
{ baseRef: "refs/heads/release" },
|
|
47
|
+
{ resume: "retained-run", task: "Continue", baseRef: "refs/heads/topic" },
|
|
48
|
+
"workflow-run",
|
|
49
|
+
"resume-explicit",
|
|
50
|
+
).baseRef,
|
|
51
|
+
"refs/heads/topic",
|
|
52
|
+
);
|
|
53
|
+
assert.equal(
|
|
54
|
+
prepareWorkflowLaunchParams(
|
|
55
|
+
{ baseRef: "refs/heads/release" },
|
|
56
|
+
{ agent: "worker", task: "Run", baseRef: "refs/heads/topic" },
|
|
57
|
+
"workflow-run",
|
|
58
|
+
"override",
|
|
59
|
+
).baseRef,
|
|
60
|
+
"refs/heads/topic",
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
25
64
|
it("does not forward workflow capacity overrides to children", () => {
|
|
26
65
|
const params = prepareWorkflowLaunchParams(
|
|
27
66
|
{ globalConcurrencyLimit: 2, maxSubagentSpawnsPerRun: 3 },
|
|
@@ -92,7 +131,8 @@ describe("workflow launch params", () => {
|
|
|
92
131
|
"run",
|
|
93
132
|
{ parentDeadlineAt },
|
|
94
133
|
);
|
|
95
|
-
assert.equal(params.async,
|
|
134
|
+
assert.equal(params.async, undefined);
|
|
135
|
+
assert.equal(params.workflowAwaitAsync, true);
|
|
96
136
|
assert.equal(params.timeoutMs, undefined);
|
|
97
137
|
assert.equal(params.workflowParentDeadlineAt, parentDeadlineAt);
|
|
98
138
|
});
|
|
@@ -199,7 +239,7 @@ describe("workflow launch params", () => {
|
|
|
199
239
|
agent: "worker",
|
|
200
240
|
task: "Run",
|
|
201
241
|
intercomBridge: { mode: "off" },
|
|
202
|
-
|
|
242
|
+
workflowAwaitAsync: true,
|
|
203
243
|
workflowParentRunId: "workflow-run",
|
|
204
244
|
workflowKey: "isolated",
|
|
205
245
|
},
|
|
@@ -226,7 +266,7 @@ describe("workflow launch params", () => {
|
|
|
226
266
|
agent: "worker",
|
|
227
267
|
task: "Implement",
|
|
228
268
|
worktree: true,
|
|
229
|
-
|
|
269
|
+
workflowAwaitAsync: true,
|
|
230
270
|
workflowParentRunId: "workflow-run",
|
|
231
271
|
workflowKey: "gated",
|
|
232
272
|
acceptance: { level: "verified", verify: [{ id: "gate", command: "npm test" }] },
|
|
@@ -404,4 +444,4 @@ describe("workflow launch params", () => {
|
|
|
404
444
|
assert.equal(sanitized, "a".repeat(120));
|
|
405
445
|
});
|
|
406
446
|
});
|
|
407
|
-
});
|
|
447
|
+
});
|