@selesai/code 0.8.3 → 0.8.5
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 +15 -0
- package/README.md +7 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/defaults/models.json +41 -55
- package/dist/defaults/settings.json +7 -8
- package/dist/extensions/handoff-new.ts +1 -1
- package/dist/extensions/inline-skills.test.ts +1 -0
- package/dist/extensions/inline-skills.ts +1 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
- package/dist/extensions/pi-intercom/README.md +34 -15
- package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
- package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
- package/dist/extensions/pi-intercom/broker/client.ts +78 -150
- package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
- package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
- package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
- package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
- package/dist/extensions/pi-intercom/config.test.ts +8 -10
- package/dist/extensions/pi-intercom/config.ts +3 -3
- package/dist/extensions/pi-intercom/index.ts +218 -147
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
- package/dist/extensions/pi-intercom/package.json +3 -2
- package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
- package/dist/extensions/pi-intercom/project-agent.ts +324 -0
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
- package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
- package/dist/extensions/pi-intercom/types.ts +3 -1
- package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
- package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +14 -1
- package/dist/extensions/pi-powerline-footer/README.md +20 -37
- package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
- package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
- package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
- package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
- package/dist/extensions/pi-powerline-footer/index.ts +394 -317
- package/dist/extensions/pi-powerline-footer/package.json +1 -1
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -12
- package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
- package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
- package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
- package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
- package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
- package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +116 -0
- package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
- package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
- package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
- package/dist/extensions/pi-powerline-footer/types.ts +1 -2
- package/dist/extensions/pi-subagents/CHANGELOG.md +225 -14
- package/dist/extensions/pi-subagents/README.md +66 -1613
- package/dist/extensions/pi-subagents/agents/builder.md +0 -1
- package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
- package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
- package/dist/extensions/pi-subagents/agents/scout.md +50 -0
- package/dist/extensions/pi-subagents/agents/worker.md +58 -0
- package/dist/extensions/pi-subagents/docs/configuration.md +372 -0
- package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
- package/dist/extensions/pi-subagents/docs/missions.md +119 -0
- package/dist/extensions/pi-subagents/docs/models.md +192 -0
- package/dist/extensions/pi-subagents/docs/observability.md +174 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
- package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
- package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
- package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
- package/dist/extensions/pi-subagents/package-lock.json +152 -194
- package/dist/extensions/pi-subagents/package.json +9 -10
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
- package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +38 -19
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
- package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
- package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +62 -8
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
- package/dist/extensions/pi-subagents/src/extension/index.ts +140 -50
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +77 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +18 -24
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
- package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
- package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
- package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -17
- package/dist/extensions/pi-subagents/src/missions/store.ts +151 -9
- package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +260 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +46 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +158 -36
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +26 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -6
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +26 -3
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +233 -101
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +40 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +188 -46
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1365 -252
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +178 -26
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +32 -5
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +509 -117
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -5
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +20 -8
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
- package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
- package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
- package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
- package/dist/extensions/pi-subagents/src/shared/types.ts +192 -3
- package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +46 -34
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
- package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +37 -21
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +375 -42
- package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
- package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +332 -27
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +102 -15
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +507 -306
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +124 -58
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +167 -113
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +139 -58
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +324 -59
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +98 -22
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1122 -96
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +194 -59
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +118 -99
- package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +18 -3
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +99 -40
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +94 -23
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +70 -52
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +30 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +91 -47
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +468 -38
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +14 -4
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +7 -6
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +81 -7
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +42 -1
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
- package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +440 -92
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +24 -8
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +59 -6
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +51 -0
- package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +111 -31
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +65 -50
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
- package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +60 -44
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +130 -92
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +65 -26
- package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +138 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +68 -7
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
- package/dist/extensions/workflow/extension.ts +21 -19
- package/dist/extensions/workflow/modes.ts +65 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +48 -1
- package/dist/skills/pi-subagents/SKILL.md +20 -15
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
- package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
- package/docs/plans/workflow-autoloop-reference.md +192 -0
- package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
- package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
- package/docs/quickstart.md +27 -43
- package/docs/settings.md +4 -0
- package/docs/usage.md +1 -0
- package/package.json +2 -1
- package/dist/extensions/workflow/adapter.ts +0 -949
- package/dist/extensions/workflow/modes/loop.ts +0 -74
- package/dist/extensions/workflow/modes/prototype.ts +0 -170
- package/dist/extensions/workflow/modes/quicktype.ts +0 -150
- package/dist/extensions/workflow/modes/task.ts +0 -105
- package/dist/extensions/workflow/run-state.ts +0 -124
- package/dist/extensions/workflow/state-machine.ts +0 -484
- package/dist/extensions/workflow/validators.ts +0 -42
|
@@ -21,22 +21,16 @@ const childEnvKeys = [
|
|
|
21
21
|
"SELESAI_SUBAGENT_INTERCOM_SESSION_NAME",
|
|
22
22
|
"SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR",
|
|
23
23
|
] as const;
|
|
24
|
-
const sharedHomeDir = mkdtempSync(path.join(tmpdir(), "
|
|
24
|
+
const sharedHomeDir = mkdtempSync(path.join(tmpdir(), "pih-"));
|
|
25
25
|
const previousHome = process.env.HOME;
|
|
26
26
|
const previousUserProfile = process.env.USERPROFILE;
|
|
27
|
-
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
28
27
|
process.env.HOME = sharedHomeDir;
|
|
29
28
|
process.env.USERPROFILE = sharedHomeDir;
|
|
30
|
-
process.env.SELESAI_CODING_AGENT_DIR = sharedHomeDir;
|
|
31
29
|
const { IntercomClient } = await import("./broker/client.ts");
|
|
32
30
|
const { getTsxCliPath } = await import("./broker/spawn.ts");
|
|
33
31
|
process.on("exit", () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (previousUserProfile === undefined) delete process.env.USERPROFILE;
|
|
37
|
-
else process.env.USERPROFILE = previousUserProfile;
|
|
38
|
-
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
39
|
-
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
32
|
+
process.env.HOME = previousHome;
|
|
33
|
+
process.env.USERPROFILE = previousUserProfile;
|
|
40
34
|
rmSync(sharedHomeDir, { recursive: true, force: true });
|
|
41
35
|
});
|
|
42
36
|
|
|
@@ -473,6 +467,37 @@ async function waitForSessionModel(client: InstanceType<typeof IntercomClient>,
|
|
|
473
467
|
throw new Error(`Timed out waiting for ${name} model ${model}; saw ${JSON.stringify(sessions.map((session) => ({ name: session.name, model: session.model })))}`);
|
|
474
468
|
}
|
|
475
469
|
|
|
470
|
+
async function withConfirmSendEnabled<T>(fn: () => T | Promise<T>): Promise<T> {
|
|
471
|
+
const { getIntercomDirPath } = await import("./broker/paths.ts");
|
|
472
|
+
const { getConfigPath } = await import("./config.ts");
|
|
473
|
+
const { mkdirSync, writeFileSync, existsSync, rmSync: removeSync } = await import("node:fs");
|
|
474
|
+
const intercomDir = getIntercomDirPath();
|
|
475
|
+
mkdirSync(intercomDir, { recursive: true });
|
|
476
|
+
const configPath = getConfigPath(intercomDir);
|
|
477
|
+
const existed = existsSync(configPath);
|
|
478
|
+
writeFileSync(configPath, JSON.stringify({ confirmSend: true }), "utf-8");
|
|
479
|
+
try {
|
|
480
|
+
return await fn();
|
|
481
|
+
} finally {
|
|
482
|
+
if (existed) {
|
|
483
|
+
writeFileSync(configPath, JSON.stringify({ confirmSend: false }), "utf-8");
|
|
484
|
+
} else {
|
|
485
|
+
removeSync(configPath, { force: true });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function withAskTimeoutMs<T>(timeoutMs: number, fn: () => T | Promise<T>): Promise<T> {
|
|
491
|
+
const previous = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
|
|
492
|
+
process.env.PI_INTERCOM_ASK_TIMEOUT_MS = String(timeoutMs);
|
|
493
|
+
try {
|
|
494
|
+
return await fn();
|
|
495
|
+
} finally {
|
|
496
|
+
if (previous === undefined) delete process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
|
|
497
|
+
else process.env.PI_INTERCOM_ASK_TIMEOUT_MS = previous;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
476
501
|
async function waitForSessionId(client: InstanceType<typeof IntercomClient>, sessionId: string): Promise<SessionInfo> {
|
|
477
502
|
const deadline = Date.now() + 2000;
|
|
478
503
|
while (Date.now() < deadline) {
|
|
@@ -652,6 +677,33 @@ test("broker times out sockets that unregister and go idle", { concurrency: fals
|
|
|
652
677
|
}
|
|
653
678
|
});
|
|
654
679
|
|
|
680
|
+
test("unnamed sessions use a collision-resistant runtime alias", { concurrency: false }, async () => {
|
|
681
|
+
const { planner, cleanup } = await setupClients();
|
|
682
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
683
|
+
const firstSessionId = "019fe418-248e-7447-9379-fdce6e91dcba";
|
|
684
|
+
const secondSessionId = "019fe418-248e-7abc-8123-111111111111";
|
|
685
|
+
const firstHarness = createExtensionHarness("", { sessionId: firstSessionId });
|
|
686
|
+
const secondHarness = createExtensionHarness("", { sessionId: secondSessionId });
|
|
687
|
+
|
|
688
|
+
try {
|
|
689
|
+
piIntercomExtension(firstHarness.pi as never);
|
|
690
|
+
piIntercomExtension(secondHarness.pi as never);
|
|
691
|
+
await firstHarness.emitLifecycle("session_start");
|
|
692
|
+
await secondHarness.emitLifecycle("session_start");
|
|
693
|
+
const first = await waitForSessionId(planner, firstSessionId);
|
|
694
|
+
const second = await waitForSessionId(planner, secondSessionId);
|
|
695
|
+
assert.equal(first.name, "subagent-chat-019fe418-248e-7447");
|
|
696
|
+
assert.equal(second.name, "subagent-chat-019fe418-248e-7abc");
|
|
697
|
+
assert.equal(first.runtimeFallbackAlias, true);
|
|
698
|
+
assert.equal(second.runtimeFallbackAlias, true);
|
|
699
|
+
assert.notEqual(first.name, second.name);
|
|
700
|
+
} finally {
|
|
701
|
+
await firstHarness.emitLifecycle("session_shutdown");
|
|
702
|
+
await secondHarness.emitLifecycle("session_shutdown");
|
|
703
|
+
await cleanup();
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
655
707
|
test("broker coalesces no-op presence floods", { concurrency: false }, async () => {
|
|
656
708
|
const { planner, cleanup } = await setupClients();
|
|
657
709
|
const worker = new IntercomClient();
|
|
@@ -885,7 +937,7 @@ test("intercom-id inserts a stable handoff snippet into the editor", { concurren
|
|
|
885
937
|
}
|
|
886
938
|
});
|
|
887
939
|
|
|
888
|
-
test("intercom tool
|
|
940
|
+
test("intercom tool shows unique ID prefixes when names collide", { concurrency: false }, async () => {
|
|
889
941
|
const { cleanup } = await setupClients();
|
|
890
942
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
891
943
|
const twinA = new IntercomClient();
|
|
@@ -893,19 +945,29 @@ test("intercom tool points at the short id when names collide", { concurrency: f
|
|
|
893
945
|
const harness = createExtensionHarness("collision-sender");
|
|
894
946
|
|
|
895
947
|
try {
|
|
896
|
-
await twinA.connect({ name: "twin", cwd: repoDir, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() }, "
|
|
897
|
-
await twinB.connect({ name: "twin", cwd: repoDir
|
|
948
|
+
await twinA.connect({ name: "twin", cwd: repoDir, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() }, "019fc92c-066f-755e-95d8-50ebb030d40d");
|
|
949
|
+
await twinB.connect({ name: "twin", cwd: `${repoDir}/other`, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() }, "019fc92c-b5f7-7536-b715-e41a4a6e9eb5");
|
|
898
950
|
piIntercomExtension(harness.pi as never);
|
|
899
951
|
await harness.emitLifecycle("session_start");
|
|
900
952
|
|
|
901
953
|
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
954
|
+
const listed = await intercomTool.execute("list-twin", { action: "list" }, new AbortController().signal, undefined, harness.ctx);
|
|
955
|
+
const listText = listed.content.map((part) => (part as { text?: string }).text ?? "").join("");
|
|
956
|
+
assert.match(listText, /019fc92c-066f/);
|
|
957
|
+
assert.match(listText, /019fc92c-b5f7/);
|
|
958
|
+
|
|
959
|
+
const listedCwd = await intercomTool.execute("list-cwd-twin", { action: "list-cwd" }, new AbortController().signal, undefined, harness.ctx);
|
|
960
|
+
const listCwdText = listedCwd.content.map((part) => (part as { text?: string }).text ?? "").join("");
|
|
961
|
+
assert.match(listCwdText, /019fc92c-066f/);
|
|
962
|
+
assert.doesNotMatch(listCwdText, /019fc92c-b5f7/);
|
|
963
|
+
|
|
902
964
|
const result = await intercomTool.execute("send-twin", { action: "send", to: "twin", message: "which one?" }, new AbortController().signal, undefined, harness.ctx);
|
|
903
965
|
|
|
904
966
|
assert.equal(result.details?.error, true);
|
|
905
967
|
const text = result.content.map((part) => (part as { text?: string }).text ?? "").join("");
|
|
906
968
|
assert.match(text, /parentheses/);
|
|
907
|
-
assert.match(text, /
|
|
908
|
-
assert.match(text, /
|
|
969
|
+
assert.match(text, /019fc92c-066f/);
|
|
970
|
+
assert.match(text, /019fc92c-b5f7/);
|
|
909
971
|
await harness.emitLifecycle("session_shutdown");
|
|
910
972
|
} finally {
|
|
911
973
|
await twinA.disconnect().catch(() => undefined);
|
|
@@ -1014,7 +1076,7 @@ test("intercom tool renders compact call and result rows", async () => {
|
|
|
1014
1076
|
to: "planner",
|
|
1015
1077
|
message: "Need a decision before I continue with this implementation.",
|
|
1016
1078
|
attachments: [{ type: "snippet", name: "note.ts", content: "const ok = true;" }],
|
|
1017
|
-
}, renderTheme, {})), /intercom ask → planner \(1 attachment\)\n
|
|
1079
|
+
}, renderTheme, {})), /intercom ask → planner \(1 attachment\)\n {2}Need a decision/);
|
|
1018
1080
|
|
|
1019
1081
|
const resultText = renderToText(intercomTool.renderResult({
|
|
1020
1082
|
content: [{ type: "text", text: "Message sent to planner" }],
|
|
@@ -1073,7 +1135,7 @@ test("contact supervisor tool renders reason and reply state", async () => {
|
|
|
1073
1135
|
reason: "interview_request",
|
|
1074
1136
|
message: "Please answer these before I continue.",
|
|
1075
1137
|
interview: { title: "API migration", questions: [] },
|
|
1076
|
-
}, renderTheme, {})), /contact_supervisor interview_request API migration\n
|
|
1138
|
+
}, renderTheme, {})), /contact_supervisor interview_request API migration\n {2}Please answer/);
|
|
1077
1139
|
|
|
1078
1140
|
const warningText = renderToText(supervisorTool.renderResult({
|
|
1079
1141
|
content: [{ type: "text", text: "Reply from supervisor:\nUse stable API" }],
|
|
@@ -1150,28 +1212,7 @@ test("idle name poll propagates /name changes without other activity", { concurr
|
|
|
1150
1212
|
}
|
|
1151
1213
|
});
|
|
1152
1214
|
|
|
1153
|
-
test("
|
|
1154
|
-
const { planner, cleanup } = await setupClients();
|
|
1155
|
-
let sessionName = "event-name-before";
|
|
1156
|
-
const harness = createExtensionHarness(() => sessionName, { hasUI: true });
|
|
1157
|
-
|
|
1158
|
-
try {
|
|
1159
|
-
await withChildOrchestratorEnv({ namePollMs: "60000" }, async () => {
|
|
1160
|
-
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1161
|
-
piIntercomExtension(harness.pi as never);
|
|
1162
|
-
await harness.emitLifecycle("session_start");
|
|
1163
|
-
await waitForSessionByName(planner, "event-name-before");
|
|
1164
|
-
sessionName = "event-name-after";
|
|
1165
|
-
await harness.emitLifecycle("session_info_changed", { name: sessionName });
|
|
1166
|
-
await waitForSessionByName(planner, "event-name-after");
|
|
1167
|
-
});
|
|
1168
|
-
} finally {
|
|
1169
|
-
await harness.emitLifecycle("session_shutdown");
|
|
1170
|
-
await cleanup();
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
test("turn_start re-registers when Selesai replaces the session context", { concurrency: false }, async () => {
|
|
1215
|
+
test("turn_start re-registers when Pi replaces the session context", { concurrency: false }, async () => {
|
|
1175
1216
|
const { planner, cleanup } = await setupClients();
|
|
1176
1217
|
let sessionName = "fork-before";
|
|
1177
1218
|
let sessionId = "session-fork-before";
|
|
@@ -1195,7 +1236,7 @@ test("turn_start re-registers when Selesai replaces the session context", { conc
|
|
|
1195
1236
|
}
|
|
1196
1237
|
});
|
|
1197
1238
|
|
|
1198
|
-
test("busy interactive sessions
|
|
1239
|
+
test("busy interactive sessions steer top-level asks without aborting", { concurrency: false }, async () => {
|
|
1199
1240
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1200
1241
|
const { planner, cleanup } = await setupClients();
|
|
1201
1242
|
let abortCount = 0;
|
|
@@ -1213,30 +1254,59 @@ test("busy interactive sessions idle-gate top-level asks without aborting", { co
|
|
|
1213
1254
|
const target = await waitForSessionByName(planner, "interactive-worker");
|
|
1214
1255
|
|
|
1215
1256
|
const delivered = await planner.send(target.id, {
|
|
1216
|
-
messageId:
|
|
1257
|
+
messageId: 'interactive-busy-"ask',
|
|
1217
1258
|
text: "Can you respond after your current turn?",
|
|
1218
1259
|
expectsReply: true,
|
|
1219
1260
|
});
|
|
1220
1261
|
assert.equal(delivered.delivered, true);
|
|
1221
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1262
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1222
1263
|
assert.equal(abortCount, 0);
|
|
1223
|
-
assert.equal(harness.sentMessages.length,
|
|
1264
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1265
|
+
assert.equal(harness.sentMessages[0]?.message.customType, "intercom_message");
|
|
1266
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1267
|
+
assert.match(harness.sentMessages[0]?.message.content ?? "", /Can you respond after your current turn/);
|
|
1268
|
+
assert.match(harness.sentMessages[0]?.message.content ?? "", /replyTo: "interactive-busy-\\"ask"/);
|
|
1269
|
+
|
|
1270
|
+
await harness.emitLifecycle("turn_end");
|
|
1271
|
+
assert.equal(harness.sentMessages.length, 1, "turn end must not inject the steered message again");
|
|
1224
1272
|
|
|
1225
1273
|
idle = true;
|
|
1226
1274
|
await harness.emitLifecycle("agent_end");
|
|
1227
1275
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1228
1276
|
|
|
1229
1277
|
assert.equal(abortCount, 0);
|
|
1230
|
-
assert.equal(harness.sentMessages.length, 1);
|
|
1231
|
-
assert.equal(harness.sentMessages[0]?.message.customType, "intercom_message");
|
|
1232
|
-
assert.equal(harness.sentMessages[0]?.options?.triggerTurn, true);
|
|
1233
|
-
assert.match(harness.sentMessages[0]?.message.content ?? "", /Can you respond after your current turn/);
|
|
1278
|
+
assert.equal(harness.sentMessages.length, 1, "agent end must not inject the steered message again");
|
|
1234
1279
|
} finally {
|
|
1235
1280
|
await harness.emitLifecycle("session_shutdown");
|
|
1236
1281
|
await cleanup();
|
|
1237
1282
|
}
|
|
1238
1283
|
});
|
|
1239
1284
|
|
|
1285
|
+
test("idle interactive sessions trigger a new turn immediately", { concurrency: false }, async () => {
|
|
1286
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1287
|
+
const { planner, cleanup } = await setupClients();
|
|
1288
|
+
const harness = createExtensionHarness("idle-trigger-worker", {
|
|
1289
|
+
hasUI: true,
|
|
1290
|
+
isIdle: () => true,
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
try {
|
|
1294
|
+
piIntercomExtension(harness.pi as never);
|
|
1295
|
+
await harness.emitLifecycle("session_start");
|
|
1296
|
+
const worker = await waitForSessionByName(planner, "idle-trigger-worker");
|
|
1297
|
+
|
|
1298
|
+
assert.equal((await planner.send(worker.id, { messageId: "idle-trigger", text: "Handle this now" })).delivered, true);
|
|
1299
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1300
|
+
|
|
1301
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1302
|
+
assert.equal(harness.sentMessages[0]?.options?.triggerTurn, true);
|
|
1303
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, undefined);
|
|
1304
|
+
} finally {
|
|
1305
|
+
await harness.emitLifecycle("session_shutdown");
|
|
1306
|
+
await cleanup();
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1240
1310
|
test("duplicate inbound message IDs inject once with visible delivery metadata", { concurrency: false }, async () => {
|
|
1241
1311
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1242
1312
|
const { planner, cleanup } = await setupClients();
|
|
@@ -1283,7 +1353,7 @@ test("duplicate inbound message IDs inject once with visible delivery metadata",
|
|
|
1283
1353
|
}
|
|
1284
1354
|
});
|
|
1285
1355
|
|
|
1286
|
-
test("busy interactive sessions
|
|
1356
|
+
test("busy interactive sessions steer same-sender messages in sequence order", { concurrency: false }, async () => {
|
|
1287
1357
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1288
1358
|
const { planner, cleanup } = await setupClients();
|
|
1289
1359
|
let idle = false;
|
|
@@ -1303,26 +1373,25 @@ test("busy interactive sessions keep same-sender queued messages in sequence ord
|
|
|
1303
1373
|
receipts.set(receipt.messageId, statuses);
|
|
1304
1374
|
});
|
|
1305
1375
|
|
|
1306
|
-
assert.equal((await planner.send(worker.id, { messageId: "sequence-1", text: "First
|
|
1307
|
-
assert.equal((await planner.send(worker.id, { messageId: "sequence-2", text: "Second
|
|
1308
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1309
|
-
assert.equal(harness.sentMessages.length,
|
|
1376
|
+
assert.equal((await planner.send(worker.id, { messageId: "sequence-1", text: "First steered message" })).delivered, true);
|
|
1377
|
+
assert.equal((await planner.send(worker.id, { messageId: "sequence-2", text: "Second steered message" })).delivered, true);
|
|
1378
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1379
|
+
assert.equal(harness.sentMessages.length, 2);
|
|
1380
|
+
assert.match(harness.sentMessages[0]?.message.content ?? "", /First steered message/);
|
|
1381
|
+
assert.match(harness.sentMessages[1]?.message.content ?? "", /Second steered message/);
|
|
1382
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1383
|
+
assert.equal(harness.sentMessages[1]?.options?.deliverAs, "steer");
|
|
1310
1384
|
|
|
1311
1385
|
idle = true;
|
|
1312
1386
|
await harness.emitLifecycle("agent_end");
|
|
1313
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1314
|
-
|
|
1315
|
-
assert.equal(harness.sentMessages.length, 2);
|
|
1316
|
-
assert.match(harness.sentMessages[0]?.message.content ?? "", /First queued message/);
|
|
1317
|
-
assert.match(harness.sentMessages[1]?.message.content ?? "", /Second queued message/);
|
|
1318
|
-
assert.equal(harness.sentMessages[0]?.options?.triggerTurn, true);
|
|
1319
|
-
assert.equal(harness.sentMessages[1]?.options?.deliverAs, "followUp");
|
|
1387
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1388
|
+
assert.equal(harness.sentMessages.length, 2, "steered messages must not be injected again at idle");
|
|
1320
1389
|
const firstDetails = harness.sentMessages[0]?.message.details as { message?: Message } | undefined;
|
|
1321
1390
|
const secondDetails = harness.sentMessages[1]?.message.details as { message?: Message } | undefined;
|
|
1322
1391
|
assert.equal(firstDetails?.message?.senderSequence, 1);
|
|
1323
1392
|
assert.equal(secondDetails?.message?.senderSequence, 2);
|
|
1324
|
-
assert.deepEqual(receipts.get("sequence-1"), ["receiver_received", "acknowledged", "
|
|
1325
|
-
assert.deepEqual(receipts.get("sequence-2"), ["receiver_received", "acknowledged", "
|
|
1393
|
+
assert.deepEqual(receipts.get("sequence-1"), ["receiver_received", "acknowledged", "injected"]);
|
|
1394
|
+
assert.deepEqual(receipts.get("sequence-2"), ["receiver_received", "acknowledged", "injected"]);
|
|
1326
1395
|
unsubscribeReceipts();
|
|
1327
1396
|
} finally {
|
|
1328
1397
|
await harness.emitLifecycle("session_shutdown");
|
|
@@ -1330,7 +1399,7 @@ test("busy interactive sessions keep same-sender queued messages in sequence ord
|
|
|
1330
1399
|
}
|
|
1331
1400
|
});
|
|
1332
1401
|
|
|
1333
|
-
test("explicit cancel
|
|
1402
|
+
test("explicit cancel acknowledges that a steered inbound message may already be processed", { concurrency: false }, async () => {
|
|
1334
1403
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1335
1404
|
const { planner, cleanup } = await setupClients();
|
|
1336
1405
|
let idle = false;
|
|
@@ -1345,21 +1414,22 @@ test("explicit cancel removes a queued inbound message before injection", { conc
|
|
|
1345
1414
|
const worker = await waitForSessionByName(planner, "cancel-worker");
|
|
1346
1415
|
const receipts: string[] = [];
|
|
1347
1416
|
const unsubscribeReceipts = planner.onMessageReceipt((_from, receipt) => {
|
|
1348
|
-
if (receipt.messageId === "cancel-
|
|
1417
|
+
if (receipt.messageId === "cancel-steered") receipts.push(receipt.status);
|
|
1349
1418
|
});
|
|
1350
1419
|
|
|
1351
|
-
assert.equal((await planner.send(worker.id, { messageId: "cancel-
|
|
1352
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1353
|
-
assert.equal(harness.sentMessages.length,
|
|
1354
|
-
assert.equal(
|
|
1355
|
-
await
|
|
1420
|
+
assert.equal((await planner.send(worker.id, { messageId: "cancel-steered", text: "Cancel after steering", expectsReply: true })).delivered, true);
|
|
1421
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1422
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1423
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1424
|
+
assert.equal((await planner.cancelMessage("cancel-steered")).delivered, true);
|
|
1425
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1356
1426
|
|
|
1357
1427
|
idle = true;
|
|
1358
1428
|
await harness.emitLifecycle("agent_end");
|
|
1359
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1429
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1360
1430
|
|
|
1361
|
-
assert.equal(harness.sentMessages.length,
|
|
1362
|
-
assert.deepEqual(receipts, ["receiver_received", "acknowledged", "
|
|
1431
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1432
|
+
assert.deepEqual(receipts, ["receiver_received", "acknowledged", "injected", "cancellation_requested"]);
|
|
1363
1433
|
unsubscribeReceipts();
|
|
1364
1434
|
} finally {
|
|
1365
1435
|
await harness.emitLifecycle("session_shutdown");
|
|
@@ -1390,8 +1460,9 @@ test("intercom cancel action requests cancellation for a sent message", { concur
|
|
|
1390
1460
|
const messageId = String(sendResult.details?.messageId);
|
|
1391
1461
|
assert.equal(sendResult.details?.delivered, true);
|
|
1392
1462
|
assert.notEqual(messageId, "undefined");
|
|
1393
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1394
|
-
assert.equal(receiverHarness.sentMessages.length,
|
|
1463
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1464
|
+
assert.equal(receiverHarness.sentMessages.length, 1);
|
|
1465
|
+
assert.equal(receiverHarness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1395
1466
|
|
|
1396
1467
|
const cancelResult = await intercomTool.execute("cancel-message", { action: "cancel", messageId }, new AbortController().signal, undefined, senderHarness.ctx);
|
|
1397
1468
|
assert.equal(cancelResult.details?.delivered, true);
|
|
@@ -1399,8 +1470,8 @@ test("intercom cancel action requests cancellation for a sent message", { concur
|
|
|
1399
1470
|
|
|
1400
1471
|
idle = true;
|
|
1401
1472
|
await receiverHarness.emitLifecycle("agent_end");
|
|
1402
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1403
|
-
assert.equal(receiverHarness.sentMessages.length,
|
|
1473
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1474
|
+
assert.equal(receiverHarness.sentMessages.length, 1);
|
|
1404
1475
|
} finally {
|
|
1405
1476
|
await senderHarness.emitLifecycle("session_shutdown");
|
|
1406
1477
|
await receiverHarness.emitLifecycle("session_shutdown");
|
|
@@ -1408,7 +1479,7 @@ test("intercom cancel action requests cancellation for a sent message", { concur
|
|
|
1408
1479
|
}
|
|
1409
1480
|
});
|
|
1410
1481
|
|
|
1411
|
-
test("same-sender supersede
|
|
1482
|
+
test("same-sender supersede reports an already-steered inbound message", { concurrency: false }, async () => {
|
|
1412
1483
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1413
1484
|
const { planner, cleanup } = await setupClients();
|
|
1414
1485
|
let idle = false;
|
|
@@ -1428,22 +1499,26 @@ test("same-sender supersede replaces a queued inbound message", { concurrency: f
|
|
|
1428
1499
|
receipts.set(receipt.messageId, statuses);
|
|
1429
1500
|
});
|
|
1430
1501
|
|
|
1431
|
-
assert.equal((await planner.send(worker.id, { messageId: "superseded-message", text: "Old
|
|
1432
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1502
|
+
assert.equal((await planner.send(worker.id, { messageId: "superseded-message", text: "Old steered message", expectsReply: true })).delivered, true);
|
|
1503
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1433
1504
|
const replacement = await planner.send(worker.id, { messageId: "replacement-message", text: "Replacement message", supersedes: "superseded-message" });
|
|
1434
1505
|
assert.equal(replacement.delivered, true);
|
|
1506
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1507
|
+
|
|
1508
|
+
assert.equal(harness.sentMessages.length, 2);
|
|
1509
|
+
assert.match(harness.sentMessages[0]?.message.content ?? "", /Old steered message/);
|
|
1510
|
+
assert.match(harness.sentMessages[1]?.message.content ?? "", /Replacement message/);
|
|
1511
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1512
|
+
assert.equal(harness.sentMessages[1]?.options?.deliverAs, "steer");
|
|
1513
|
+
const details = harness.sentMessages[1]?.message.details as { message?: Message } | undefined;
|
|
1514
|
+
assert.equal(details?.message?.supersedes, "superseded-message");
|
|
1435
1515
|
|
|
1436
1516
|
idle = true;
|
|
1437
1517
|
await harness.emitLifecycle("agent_end");
|
|
1438
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1439
|
-
|
|
1440
|
-
assert.
|
|
1441
|
-
assert.
|
|
1442
|
-
assert.match(harness.sentMessages[0]?.message.content ?? "", /Replacement message/);
|
|
1443
|
-
const details = harness.sentMessages[0]?.message.details as { message?: Message } | undefined;
|
|
1444
|
-
assert.equal(details?.message?.supersedes, "superseded-message");
|
|
1445
|
-
assert.deepEqual(receipts.get("superseded-message"), ["receiver_received", "acknowledged", "queued", "superseded"]);
|
|
1446
|
-
assert.deepEqual(receipts.get("replacement-message"), ["receiver_received", "acknowledged", "queued", "injected"]);
|
|
1518
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1519
|
+
assert.equal(harness.sentMessages.length, 2);
|
|
1520
|
+
assert.deepEqual(receipts.get("superseded-message"), ["receiver_received", "acknowledged", "injected", "superseded"]);
|
|
1521
|
+
assert.deepEqual(receipts.get("replacement-message"), ["receiver_received", "acknowledged", "injected"]);
|
|
1447
1522
|
unsubscribeReceipts();
|
|
1448
1523
|
} finally {
|
|
1449
1524
|
await harness.emitLifecycle("session_shutdown");
|
|
@@ -1454,7 +1529,7 @@ test("same-sender supersede replaces a queued inbound message", { concurrency: f
|
|
|
1454
1529
|
test("supersede is scoped to the same sender and receiver", { concurrency: false }, async () => {
|
|
1455
1530
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1456
1531
|
const { planner, orchestrator, cleanup } = await setupClients();
|
|
1457
|
-
|
|
1532
|
+
const idle = false;
|
|
1458
1533
|
const firstHarness = createExtensionHarness("supersede-first", {
|
|
1459
1534
|
hasUI: true,
|
|
1460
1535
|
isIdle: () => idle,
|
|
@@ -1488,7 +1563,7 @@ test("supersede is scoped to the same sender and receiver", { concurrency: false
|
|
|
1488
1563
|
}
|
|
1489
1564
|
});
|
|
1490
1565
|
|
|
1491
|
-
test("replied
|
|
1566
|
+
test("replied steered asks are not injected again after the current turn", { concurrency: false }, async () => {
|
|
1492
1567
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1493
1568
|
const { planner, cleanup } = await setupClients();
|
|
1494
1569
|
let idle = false;
|
|
@@ -1509,8 +1584,9 @@ test("replied idle-gated asks are discarded before delivery", { concurrency: fal
|
|
|
1509
1584
|
text: "Can you answer before this turn ends?",
|
|
1510
1585
|
expectsReply: true,
|
|
1511
1586
|
})).delivered, true);
|
|
1512
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1513
|
-
assert.equal(harness.sentMessages.length,
|
|
1587
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1588
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1589
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1514
1590
|
|
|
1515
1591
|
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
1516
1592
|
const result = await intercomTool.execute("reply-while-busy", {
|
|
@@ -1523,8 +1599,8 @@ test("replied idle-gated asks are discarded before delivery", { concurrency: fal
|
|
|
1523
1599
|
|
|
1524
1600
|
idle = true;
|
|
1525
1601
|
await harness.emitLifecycle("agent_end");
|
|
1526
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1527
|
-
assert.equal(harness.sentMessages.length,
|
|
1602
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1603
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1528
1604
|
} finally {
|
|
1529
1605
|
await harness.emitLifecycle("session_shutdown");
|
|
1530
1606
|
await cleanup();
|
|
@@ -1593,7 +1669,7 @@ test("stale overlay work stops after same-session restart", { concurrency: false
|
|
|
1593
1669
|
}
|
|
1594
1670
|
});
|
|
1595
1671
|
|
|
1596
|
-
test("
|
|
1672
|
+
test("steered inbound messages are not reinjected after shutdown", { concurrency: false }, async () => {
|
|
1597
1673
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1598
1674
|
const { planner, cleanup } = await setupClients();
|
|
1599
1675
|
let idle = false;
|
|
@@ -1613,20 +1689,21 @@ test("queued inbound messages are discarded after shutdown", { concurrency: fals
|
|
|
1613
1689
|
|
|
1614
1690
|
const delivered = await planner.send(target.id, {
|
|
1615
1691
|
messageId: "disposed-ask",
|
|
1616
|
-
text: "This should
|
|
1692
|
+
text: "This should be steered before shutdown.",
|
|
1617
1693
|
expectsReply: true,
|
|
1618
1694
|
});
|
|
1619
1695
|
assert.equal(delivered.delivered, true);
|
|
1620
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1621
|
-
assert.equal(harness.sentMessages.length,
|
|
1696
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1697
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1698
|
+
assert.equal(harness.sentMessages[0]?.options?.deliverAs, "steer");
|
|
1622
1699
|
|
|
1623
1700
|
await harness.emitLifecycle("session_shutdown");
|
|
1624
1701
|
idle = true;
|
|
1625
1702
|
await harness.emitLifecycle("agent_end");
|
|
1626
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1703
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1627
1704
|
|
|
1628
|
-
assert.equal(harness.sentMessages.length,
|
|
1629
|
-
assert.deepEqual(receipts, ["receiver_received", "acknowledged", "
|
|
1705
|
+
assert.equal(harness.sentMessages.length, 1);
|
|
1706
|
+
assert.deepEqual(receipts, ["receiver_received", "acknowledged", "injected"]);
|
|
1630
1707
|
unsubscribeReceipts();
|
|
1631
1708
|
} finally {
|
|
1632
1709
|
await cleanup();
|
|
@@ -1772,7 +1849,7 @@ test("child supervisor tool resolves target and includes run metadata", { concur
|
|
|
1772
1849
|
assert.match(interviewMessage.content.text, /\[context\] \(info\) Migration context/);
|
|
1773
1850
|
assert.match(interviewMessage.content.text, /Info questions are context-only/);
|
|
1774
1851
|
assert.match(interviewMessage.content.text, /\[api\] \(single\) Which API should I target\?/);
|
|
1775
|
-
assert.match(interviewMessage.content.text, /
|
|
1852
|
+
assert.match(interviewMessage.content.text, / {3}- Stable API/);
|
|
1776
1853
|
assert.match(interviewMessage.content.text, /\[notes\] \(text\) Any constraints to preserve\?/);
|
|
1777
1854
|
assert.match(interviewMessage.content.text, /"responses"/);
|
|
1778
1855
|
assert.doesNotMatch(interviewMessage.content.text, /"id": "context"/);
|
|
@@ -1891,7 +1968,7 @@ test("child supervisor tool rejects invalid reasons and interview payloads", asy
|
|
|
1891
1968
|
});
|
|
1892
1969
|
});
|
|
1893
1970
|
|
|
1894
|
-
test("child supervisor
|
|
1971
|
+
test("child supervisor blocking requests fail fast when the supervisor is disconnected", { concurrency: false }, async () => {
|
|
1895
1972
|
const { default: piIntercomExtension } = await import("./index.ts");
|
|
1896
1973
|
const { cleanup } = await setupClients();
|
|
1897
1974
|
|
|
@@ -1913,11 +1990,11 @@ test("child supervisor tool preserves delivery failure reasons", { concurrency:
|
|
|
1913
1990
|
|
|
1914
1991
|
const askResult = await supervisorTool.execute("ask-1", { reason: "need_decision", message: "Which path?" }, new AbortController().signal, undefined, harness.ctx);
|
|
1915
1992
|
assert.equal(askResult.details?.error, true);
|
|
1916
|
-
assert.match(askResult.content[0]?.text ?? "", /
|
|
1993
|
+
assert.match(askResult.content[0]?.text ?? "", /not currently connected/);
|
|
1917
1994
|
|
|
1918
1995
|
const secondAskResult = await supervisorTool.execute("ask-2", { reason: "need_decision", message: "Still blocked." }, new AbortController().signal, undefined, harness.ctx);
|
|
1919
1996
|
assert.equal(secondAskResult.details?.error, true);
|
|
1920
|
-
assert.match(secondAskResult.content[0]?.text ?? "", /
|
|
1997
|
+
assert.match(secondAskResult.content[0]?.text ?? "", /not currently connected/);
|
|
1921
1998
|
assert.doesNotMatch(secondAskResult.content[0]?.text ?? "", /Already waiting/);
|
|
1922
1999
|
await harness.emitLifecycle("session_shutdown");
|
|
1923
2000
|
});
|
|
@@ -2269,6 +2346,88 @@ test("intercom reply targets exact replyTo when multiple asks are pending", { co
|
|
|
2269
2346
|
}
|
|
2270
2347
|
});
|
|
2271
2348
|
|
|
2349
|
+
test("intercom reply targets one of multiple pending asks by short session ID", { concurrency: false }, async () => {
|
|
2350
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2351
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
2352
|
+
const harness = createExtensionHarness("reply-short-id-worker");
|
|
2353
|
+
|
|
2354
|
+
try {
|
|
2355
|
+
piIntercomExtension(harness.pi as never);
|
|
2356
|
+
await harness.emitLifecycle("session_start");
|
|
2357
|
+
const worker = await waitForSessionByName(planner, "reply-short-id-worker");
|
|
2358
|
+
|
|
2359
|
+
assert.equal((await planner.send(worker.id, { messageId: "reply-short-id-1", text: "First?", expectsReply: true })).delivered, true);
|
|
2360
|
+
assert.equal((await orchestrator.send(worker.id, { messageId: "reply-short-id-2", text: "Second?", expectsReply: true })).delivered, true);
|
|
2361
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2362
|
+
|
|
2363
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
2364
|
+
const replyReceived = waitForReply(planner, "reply-short-id-1");
|
|
2365
|
+
const result = await intercomTool.execute("reply-short-id", {
|
|
2366
|
+
action: "reply",
|
|
2367
|
+
to: planner.sessionId!.slice(0, 8),
|
|
2368
|
+
message: "First answer.",
|
|
2369
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
2370
|
+
assert.equal(result.details?.delivered, true);
|
|
2371
|
+
assert.equal((await replyReceived).message.content.text, "First answer.");
|
|
2372
|
+
|
|
2373
|
+
const pending = await intercomTool.execute("pending-after-short-id", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
2374
|
+
assert.doesNotMatch(pending.content[0]?.text ?? "", /reply-short-id-1/);
|
|
2375
|
+
assert.match(pending.content[0]?.text ?? "", /reply-short-id-2/);
|
|
2376
|
+
} finally {
|
|
2377
|
+
await harness.emitLifecycle("session_shutdown");
|
|
2378
|
+
await cleanup();
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
test("a short-ID reply unblocks the original ask when another ask is pending", { concurrency: false }, async () => {
|
|
2383
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2384
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
2385
|
+
const askerHarness = createExtensionHarness("short-id-asker", { sessionId: "asker123-session" });
|
|
2386
|
+
const replierHarness = createExtensionHarness("short-id-replier", { sessionId: "replier-session" });
|
|
2387
|
+
|
|
2388
|
+
try {
|
|
2389
|
+
piIntercomExtension(askerHarness.pi as never);
|
|
2390
|
+
piIntercomExtension(replierHarness.pi as never);
|
|
2391
|
+
await askerHarness.emitLifecycle("session_start");
|
|
2392
|
+
await replierHarness.emitLifecycle("session_start");
|
|
2393
|
+
const asker = await waitForSessionByName(planner, "short-id-asker");
|
|
2394
|
+
const replier = await waitForSessionByName(planner, "short-id-replier");
|
|
2395
|
+
const askerTool = askerHarness.tools.find((tool) => tool.name === "intercom")!;
|
|
2396
|
+
const replierTool = replierHarness.tools.find((tool) => tool.name === "intercom")!;
|
|
2397
|
+
|
|
2398
|
+
const originalAsk = askerTool.execute("ask-for-work", {
|
|
2399
|
+
action: "ask",
|
|
2400
|
+
to: replier.id,
|
|
2401
|
+
message: "Is any work pending?",
|
|
2402
|
+
}, new AbortController().signal, undefined, askerHarness.ctx);
|
|
2403
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2404
|
+
assert.equal((await orchestrator.send(replier.id, {
|
|
2405
|
+
messageId: "another-pending-ask",
|
|
2406
|
+
text: "A separate pending question",
|
|
2407
|
+
expectsReply: true,
|
|
2408
|
+
})).delivered, true);
|
|
2409
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2410
|
+
|
|
2411
|
+
const reply = await replierTool.execute("reply-to-work-ask", {
|
|
2412
|
+
action: "reply",
|
|
2413
|
+
to: asker.id.slice(0, 8),
|
|
2414
|
+
message: "No work is pending.",
|
|
2415
|
+
}, new AbortController().signal, undefined, replierHarness.ctx);
|
|
2416
|
+
assert.equal(reply.details?.delivered, true, reply.content.map((part) => part.text).join("\n"));
|
|
2417
|
+
|
|
2418
|
+
const result = await originalAsk;
|
|
2419
|
+
assert.doesNotMatch(result.content[0]?.text ?? "", /No reply from/);
|
|
2420
|
+
assert.match(result.content[0]?.text ?? "", /No work is pending/);
|
|
2421
|
+
|
|
2422
|
+
const pending = await replierTool.execute("remaining-pending", { action: "pending" }, new AbortController().signal, undefined, replierHarness.ctx);
|
|
2423
|
+
assert.match(pending.content[0]?.text ?? "", /another-pending-ask/);
|
|
2424
|
+
} finally {
|
|
2425
|
+
await askerHarness.emitLifecycle("session_shutdown");
|
|
2426
|
+
await replierHarness.emitLifecycle("session_shutdown");
|
|
2427
|
+
await cleanup();
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2272
2431
|
test("broker queues replies to recently disconnected named senders", { concurrency: false }, async () => {
|
|
2273
2432
|
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2274
2433
|
const replacement = new IntercomClient();
|
|
@@ -2309,6 +2468,243 @@ test("broker queues replies to recently disconnected named senders", { concurren
|
|
|
2309
2468
|
}
|
|
2310
2469
|
});
|
|
2311
2470
|
|
|
2471
|
+
test("broker rejects blocking asks to disconnected targets", { concurrency: false }, async () => {
|
|
2472
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2473
|
+
|
|
2474
|
+
try {
|
|
2475
|
+
const disconnectedId = planner.sessionId!;
|
|
2476
|
+
await planner.disconnect();
|
|
2477
|
+
const result = await orchestrator.send(disconnectedId, {
|
|
2478
|
+
messageId: "offline-broker-ask",
|
|
2479
|
+
text: "Do not queue this blocking request.",
|
|
2480
|
+
expectsReply: true,
|
|
2481
|
+
});
|
|
2482
|
+
assert.equal(result.delivered, false);
|
|
2483
|
+
assert.match(result.reason ?? "", /not currently connected/);
|
|
2484
|
+
assert.match(result.reason ?? "", /not queued/);
|
|
2485
|
+
} finally {
|
|
2486
|
+
await cleanup();
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
|
|
2490
|
+
test("broker never remaps a disconnected mailbox back to the sending session", { concurrency: false }, async () => {
|
|
2491
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2492
|
+
const sender = new IntercomClient();
|
|
2493
|
+
const replacement = new IntercomClient();
|
|
2494
|
+
|
|
2495
|
+
try {
|
|
2496
|
+
const disconnectedId = planner.sessionId!;
|
|
2497
|
+
await planner.disconnect();
|
|
2498
|
+
await sender.connect({
|
|
2499
|
+
name: "planner",
|
|
2500
|
+
cwd: repoDir,
|
|
2501
|
+
model: "test-model",
|
|
2502
|
+
pid: process.pid,
|
|
2503
|
+
startedAt: Date.now(),
|
|
2504
|
+
lastActivity: Date.now(),
|
|
2505
|
+
});
|
|
2506
|
+
const senderId = sender.sessionId!;
|
|
2507
|
+
|
|
2508
|
+
const selfDeliveries: Message[] = [];
|
|
2509
|
+
sender.on("message", (_from: SessionInfo, message: Message) => selfDeliveries.push(message));
|
|
2510
|
+
const result = await sender.send(disconnectedId, {
|
|
2511
|
+
messageId: "no-self-mailbox-remap",
|
|
2512
|
+
text: "Queue this for the disconnected session.",
|
|
2513
|
+
});
|
|
2514
|
+
assert.equal(result.delivered, true);
|
|
2515
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2516
|
+
assert.deepEqual(selfDeliveries, []);
|
|
2517
|
+
|
|
2518
|
+
await sender.disconnect();
|
|
2519
|
+
await sender.connect({
|
|
2520
|
+
name: "planner",
|
|
2521
|
+
cwd: repoDir,
|
|
2522
|
+
model: "test-model",
|
|
2523
|
+
pid: process.pid,
|
|
2524
|
+
startedAt: Date.now(),
|
|
2525
|
+
lastActivity: Date.now(),
|
|
2526
|
+
}, senderId);
|
|
2527
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2528
|
+
assert.deepEqual(selfDeliveries, []);
|
|
2529
|
+
|
|
2530
|
+
const queuedMessage = once(replacement, "message") as Promise<[SessionInfo, Message]>;
|
|
2531
|
+
await replacement.connect({
|
|
2532
|
+
name: "planner",
|
|
2533
|
+
cwd: repoDir,
|
|
2534
|
+
model: "test-model",
|
|
2535
|
+
pid: process.pid,
|
|
2536
|
+
startedAt: Date.now(),
|
|
2537
|
+
lastActivity: Date.now(),
|
|
2538
|
+
}, disconnectedId);
|
|
2539
|
+
const [, message] = await queuedMessage;
|
|
2540
|
+
assert.equal(message.id, "no-self-mailbox-remap");
|
|
2541
|
+
} finally {
|
|
2542
|
+
await sender.disconnect().catch(() => undefined);
|
|
2543
|
+
await replacement.disconnect().catch(() => undefined);
|
|
2544
|
+
await cleanup();
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2548
|
+
test("broker does not treat runtime fallback aliases as reconnect identities", { concurrency: false }, async () => {
|
|
2549
|
+
const { orchestrator, cleanup } = await setupClients();
|
|
2550
|
+
const original = new IntercomClient();
|
|
2551
|
+
const unrelated = new IntercomClient();
|
|
2552
|
+
const replacement = new IntercomClient();
|
|
2553
|
+
const fallbackAlias = "subagent-chat-019fe418-248e-7447";
|
|
2554
|
+
const originalId = "runtime-fallback-original";
|
|
2555
|
+
|
|
2556
|
+
try {
|
|
2557
|
+
await original.connect({
|
|
2558
|
+
name: fallbackAlias,
|
|
2559
|
+
runtimeFallbackAlias: true,
|
|
2560
|
+
cwd: repoDir,
|
|
2561
|
+
model: "test-model",
|
|
2562
|
+
pid: process.pid,
|
|
2563
|
+
startedAt: Date.now(),
|
|
2564
|
+
lastActivity: Date.now(),
|
|
2565
|
+
}, originalId);
|
|
2566
|
+
await original.disconnect();
|
|
2567
|
+
|
|
2568
|
+
const unrelatedDeliveries: Message[] = [];
|
|
2569
|
+
unrelated.on("message", (_from: SessionInfo, message: Message) => unrelatedDeliveries.push(message));
|
|
2570
|
+
await unrelated.connect({
|
|
2571
|
+
name: fallbackAlias,
|
|
2572
|
+
runtimeFallbackAlias: true,
|
|
2573
|
+
cwd: repoDir,
|
|
2574
|
+
model: "test-model",
|
|
2575
|
+
pid: process.pid,
|
|
2576
|
+
startedAt: Date.now(),
|
|
2577
|
+
lastActivity: Date.now(),
|
|
2578
|
+
});
|
|
2579
|
+
|
|
2580
|
+
assert.equal((await orchestrator.send(originalId, {
|
|
2581
|
+
messageId: "fallback-alias-mail",
|
|
2582
|
+
text: "Only the original session should receive this.",
|
|
2583
|
+
})).delivered, true);
|
|
2584
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2585
|
+
assert.deepEqual(unrelatedDeliveries, []);
|
|
2586
|
+
|
|
2587
|
+
const queuedMessage = once(replacement, "message") as Promise<[SessionInfo, Message]>;
|
|
2588
|
+
await replacement.connect({
|
|
2589
|
+
name: fallbackAlias,
|
|
2590
|
+
runtimeFallbackAlias: true,
|
|
2591
|
+
cwd: repoDir,
|
|
2592
|
+
model: "test-model",
|
|
2593
|
+
pid: process.pid,
|
|
2594
|
+
startedAt: Date.now(),
|
|
2595
|
+
lastActivity: Date.now(),
|
|
2596
|
+
}, originalId);
|
|
2597
|
+
const [, message] = await queuedMessage;
|
|
2598
|
+
assert.equal(message.id, "fallback-alias-mail");
|
|
2599
|
+
} finally {
|
|
2600
|
+
await original.disconnect().catch(() => undefined);
|
|
2601
|
+
await unrelated.disconnect().catch(() => undefined);
|
|
2602
|
+
await replacement.disconnect().catch(() => undefined);
|
|
2603
|
+
await cleanup();
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
test("broker does not deliver explicit mailbox mail to a matching fallback alias", { concurrency: false }, async () => {
|
|
2608
|
+
const { orchestrator, cleanup } = await setupClients();
|
|
2609
|
+
const original = new IntercomClient();
|
|
2610
|
+
const fallback = new IntercomClient();
|
|
2611
|
+
const replacement = new IntercomClient();
|
|
2612
|
+
const sharedName = "subagent-chat-shared-worker";
|
|
2613
|
+
const originalId = "explicit-mailbox-original";
|
|
2614
|
+
|
|
2615
|
+
try {
|
|
2616
|
+
await original.connect({
|
|
2617
|
+
name: sharedName,
|
|
2618
|
+
runtimeFallbackAlias: false,
|
|
2619
|
+
cwd: repoDir,
|
|
2620
|
+
model: "test-model",
|
|
2621
|
+
pid: process.pid,
|
|
2622
|
+
startedAt: Date.now(),
|
|
2623
|
+
lastActivity: Date.now(),
|
|
2624
|
+
}, originalId);
|
|
2625
|
+
await original.disconnect();
|
|
2626
|
+
|
|
2627
|
+
const fallbackDeliveries: Message[] = [];
|
|
2628
|
+
fallback.on("message", (_from: SessionInfo, message: Message) => fallbackDeliveries.push(message));
|
|
2629
|
+
await fallback.connect({
|
|
2630
|
+
name: sharedName,
|
|
2631
|
+
runtimeFallbackAlias: true,
|
|
2632
|
+
cwd: repoDir,
|
|
2633
|
+
model: "test-model",
|
|
2634
|
+
pid: process.pid,
|
|
2635
|
+
startedAt: Date.now(),
|
|
2636
|
+
lastActivity: Date.now(),
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
assert.equal((await orchestrator.send(originalId, {
|
|
2640
|
+
messageId: "explicit-mailbox-not-fallback",
|
|
2641
|
+
text: "Keep this message for the explicit identity.",
|
|
2642
|
+
})).delivered, true);
|
|
2643
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2644
|
+
assert.deepEqual(fallbackDeliveries, []);
|
|
2645
|
+
|
|
2646
|
+
const queuedMessage = once(replacement, "message") as Promise<[SessionInfo, Message]>;
|
|
2647
|
+
await replacement.connect({
|
|
2648
|
+
name: sharedName,
|
|
2649
|
+
runtimeFallbackAlias: false,
|
|
2650
|
+
cwd: repoDir,
|
|
2651
|
+
model: "test-model",
|
|
2652
|
+
pid: process.pid,
|
|
2653
|
+
startedAt: Date.now(),
|
|
2654
|
+
lastActivity: Date.now(),
|
|
2655
|
+
}, originalId);
|
|
2656
|
+
const [, message] = await queuedMessage;
|
|
2657
|
+
assert.equal(message.id, "explicit-mailbox-not-fallback");
|
|
2658
|
+
} finally {
|
|
2659
|
+
await original.disconnect().catch(() => undefined);
|
|
2660
|
+
await fallback.disconnect().catch(() => undefined);
|
|
2661
|
+
await replacement.disconnect().catch(() => undefined);
|
|
2662
|
+
await cleanup();
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
test("broker preserves mailbox reconnects for explicit subagent-chat names", { concurrency: false }, async () => {
|
|
2667
|
+
const { orchestrator, cleanup } = await setupClients();
|
|
2668
|
+
const original = new IntercomClient();
|
|
2669
|
+
const replacement = new IntercomClient();
|
|
2670
|
+
const explicitName = "subagent-chat-explicit-worker";
|
|
2671
|
+
const originalId = "explicit-subagent-chat-original";
|
|
2672
|
+
|
|
2673
|
+
try {
|
|
2674
|
+
await original.connect({
|
|
2675
|
+
name: explicitName,
|
|
2676
|
+
runtimeFallbackAlias: false,
|
|
2677
|
+
cwd: repoDir,
|
|
2678
|
+
model: "test-model",
|
|
2679
|
+
pid: process.pid,
|
|
2680
|
+
startedAt: Date.now(),
|
|
2681
|
+
lastActivity: Date.now(),
|
|
2682
|
+
}, originalId);
|
|
2683
|
+
await original.disconnect();
|
|
2684
|
+
|
|
2685
|
+
const delivered = once(replacement, "message") as Promise<[SessionInfo, Message]>;
|
|
2686
|
+
await replacement.connect({
|
|
2687
|
+
name: explicitName,
|
|
2688
|
+
runtimeFallbackAlias: false,
|
|
2689
|
+
cwd: repoDir,
|
|
2690
|
+
model: "test-model",
|
|
2691
|
+
pid: process.pid,
|
|
2692
|
+
startedAt: Date.now(),
|
|
2693
|
+
lastActivity: Date.now(),
|
|
2694
|
+
});
|
|
2695
|
+
assert.equal((await orchestrator.send(originalId, {
|
|
2696
|
+
messageId: "explicit-subagent-chat-mail",
|
|
2697
|
+
text: "Explicit names keep mailbox reconnect semantics.",
|
|
2698
|
+
})).delivered, true);
|
|
2699
|
+
const [, message] = await delivered;
|
|
2700
|
+
assert.equal(message.id, "explicit-subagent-chat-mail");
|
|
2701
|
+
} finally {
|
|
2702
|
+
await original.disconnect().catch(() => undefined);
|
|
2703
|
+
await replacement.disconnect().catch(() => undefined);
|
|
2704
|
+
await cleanup();
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
|
|
2312
2708
|
test("broker delivers old-id replies to an already reconnected same-name sender", { concurrency: false }, async () => {
|
|
2313
2709
|
const { planner, orchestrator, cleanup } = await setupClients();
|
|
2314
2710
|
const replacement = new IntercomClient();
|
|
@@ -2644,3 +3040,395 @@ test("presence carries context usage to peers, and an explicit null clears a sta
|
|
|
2644
3040
|
await cleanup();
|
|
2645
3041
|
}
|
|
2646
3042
|
});
|
|
3043
|
+
|
|
3044
|
+
test("public send infers a reply from the sole pending ask, resolves the waiter, and dismisses it", { concurrency: false }, async () => {
|
|
3045
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3046
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3047
|
+
const harness = createExtensionHarness("infer-send-worker");
|
|
3048
|
+
|
|
3049
|
+
try {
|
|
3050
|
+
piIntercomExtension(harness.pi as never);
|
|
3051
|
+
await harness.emitLifecycle("session_start");
|
|
3052
|
+
const worker = await waitForSessionByName(orchestrator, "infer-send-worker");
|
|
3053
|
+
|
|
3054
|
+
const askId = "infer-ask-1";
|
|
3055
|
+
assert.equal((await planner.send(worker.id, { messageId: askId, text: "What's next?", expectsReply: true })).delivered, true);
|
|
3056
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3057
|
+
|
|
3058
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3059
|
+
const replyReceived = waitForReply(planner, askId);
|
|
3060
|
+
const result = await intercomTool.execute("infer-send", {
|
|
3061
|
+
action: "send",
|
|
3062
|
+
to: "planner",
|
|
3063
|
+
message: "Ship it.",
|
|
3064
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3065
|
+
|
|
3066
|
+
assert.notEqual(result.details?.error, true);
|
|
3067
|
+
assert.equal(result.details?.delivered, true);
|
|
3068
|
+
assert.equal(result.details?.replyTo, askId);
|
|
3069
|
+
assert.match(result.content[0]?.text ?? "", /Reply sent to planner \(inferred from pending ask\)/);
|
|
3070
|
+
|
|
3071
|
+
const reply = await replyReceived;
|
|
3072
|
+
assert.equal(reply.message.content.text, "Ship it.");
|
|
3073
|
+
assert.equal(reply.message.replyTo, askId);
|
|
3074
|
+
|
|
3075
|
+
const sentEntry = harness.entries.find((entry) => entry.type === "intercom_sent");
|
|
3076
|
+
assert.equal((sentEntry?.data as { message: { replyTo?: string } }).message.replyTo, askId);
|
|
3077
|
+
|
|
3078
|
+
const pending = await intercomTool.execute("pending-after-infer", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3079
|
+
assert.equal(pending.content[0]?.text, "No unresolved inbound asks.");
|
|
3080
|
+
} finally {
|
|
3081
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3082
|
+
await cleanup();
|
|
3083
|
+
}
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
test("an intentional unrelated public notification during a sole pending ask is still inferred as its reply", { concurrency: false }, async () => {
|
|
3087
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3088
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3089
|
+
const harness = createExtensionHarness("notify-during-ask-worker");
|
|
3090
|
+
|
|
3091
|
+
try {
|
|
3092
|
+
piIntercomExtension(harness.pi as never);
|
|
3093
|
+
await harness.emitLifecycle("session_start");
|
|
3094
|
+
const worker = await waitForSessionByName(orchestrator, "notify-during-ask-worker");
|
|
3095
|
+
|
|
3096
|
+
const askId = "notify-ask-1";
|
|
3097
|
+
assert.equal((await planner.send(worker.id, { messageId: askId, text: "Can you take this task?", expectsReply: true })).delivered, true);
|
|
3098
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3099
|
+
|
|
3100
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3101
|
+
const replyReceived = waitForReply(planner, askId);
|
|
3102
|
+
// Unrelated to the ask's content — proves the accepted trade-off: any public
|
|
3103
|
+
// send to the sole pending asker is reclassified as its answer.
|
|
3104
|
+
const result = await intercomTool.execute("notify-during-ask", {
|
|
3105
|
+
action: "send",
|
|
3106
|
+
to: "planner",
|
|
3107
|
+
message: "Deploying build 42 now.",
|
|
3108
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3109
|
+
|
|
3110
|
+
assert.equal(result.details?.replyTo, askId);
|
|
3111
|
+
assert.match(result.content[0]?.text ?? "", /inferred from pending ask/);
|
|
3112
|
+
const reply = await replyReceived;
|
|
3113
|
+
assert.equal(reply.message.content.text, "Deploying build 42 now.");
|
|
3114
|
+
assert.equal(reply.message.replyTo, askId);
|
|
3115
|
+
} finally {
|
|
3116
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3117
|
+
await cleanup();
|
|
3118
|
+
}
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
test("confirmSend still gates an inferred reply; cancellation preserves the pending ask", { concurrency: false }, async () => {
|
|
3122
|
+
await withConfirmSendEnabled(async () => {
|
|
3123
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3124
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3125
|
+
const confirmCalls: Array<[string, string]> = [];
|
|
3126
|
+
const harness = createExtensionHarness("confirm-infer-worker", {
|
|
3127
|
+
hasUI: true,
|
|
3128
|
+
ui: {
|
|
3129
|
+
confirm: async (title: string, text: string) => {
|
|
3130
|
+
confirmCalls.push([title, text]);
|
|
3131
|
+
return false;
|
|
3132
|
+
},
|
|
3133
|
+
},
|
|
3134
|
+
});
|
|
3135
|
+
|
|
3136
|
+
try {
|
|
3137
|
+
piIntercomExtension(harness.pi as never);
|
|
3138
|
+
await harness.emitLifecycle("session_start");
|
|
3139
|
+
const worker = await waitForSessionByName(orchestrator, "confirm-infer-worker");
|
|
3140
|
+
|
|
3141
|
+
const askId = "confirm-infer-ask-1";
|
|
3142
|
+
assert.equal((await planner.send(worker.id, { messageId: askId, text: "Ready?", expectsReply: true })).delivered, true);
|
|
3143
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3144
|
+
|
|
3145
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3146
|
+
const result = await intercomTool.execute("confirm-infer", {
|
|
3147
|
+
action: "send",
|
|
3148
|
+
to: "planner",
|
|
3149
|
+
message: "Yes, ready.",
|
|
3150
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3151
|
+
|
|
3152
|
+
assert.equal(confirmCalls.length, 1);
|
|
3153
|
+
assert.equal(result.content[0]?.text, "Message cancelled by user");
|
|
3154
|
+
assert.equal(result.details?.delivered, undefined);
|
|
3155
|
+
|
|
3156
|
+
const pending = await intercomTool.execute("pending-after-cancel", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3157
|
+
assert.match(pending.content[0]?.text ?? "", /confirm-infer-ask-1/);
|
|
3158
|
+
} finally {
|
|
3159
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3160
|
+
await cleanup();
|
|
3161
|
+
}
|
|
3162
|
+
});
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3165
|
+
test("contact_supervisor progress_update stays unthreaded despite a pending ask; a later inferred send resolves it", { concurrency: false }, async () => {
|
|
3166
|
+
const { orchestrator, cleanup } = await setupClients();
|
|
3167
|
+
|
|
3168
|
+
try {
|
|
3169
|
+
await withChildOrchestratorEnv({
|
|
3170
|
+
orchestratorTarget: "orchestrator",
|
|
3171
|
+
runId: "aa11bb22",
|
|
3172
|
+
agent: "worker",
|
|
3173
|
+
index: "0",
|
|
3174
|
+
sessionName: "subagent-worker-aa11bb22-1",
|
|
3175
|
+
}, async () => {
|
|
3176
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3177
|
+
const harness = createExtensionHarness("subagent-worker-aa11bb22-1");
|
|
3178
|
+
piIntercomExtension(harness.pi as never);
|
|
3179
|
+
await harness.emitLifecycle("session_start");
|
|
3180
|
+
const worker = await waitForSessionByName(orchestrator, "subagent-worker-aa11bb22-1");
|
|
3181
|
+
|
|
3182
|
+
const askId = "boundary-ask-1";
|
|
3183
|
+
assert.equal((await orchestrator.send(worker.id, { messageId: askId, text: "Any blockers?", expectsReply: true })).delivered, true);
|
|
3184
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3185
|
+
|
|
3186
|
+
const supervisorTool = harness.tools.find((tool) => tool.name === "contact_supervisor")!;
|
|
3187
|
+
const updateReceived = once(orchestrator, "message") as Promise<[SessionInfo, Message]>;
|
|
3188
|
+
const updateResult = await supervisorTool.execute("update-1", { reason: "progress_update", message: "Still working." }, new AbortController().signal, undefined, harness.ctx);
|
|
3189
|
+
const [, updateMessage] = await updateReceived;
|
|
3190
|
+
assert.notEqual(updateResult.details?.error, true);
|
|
3191
|
+
assert.equal(updateMessage.replyTo, undefined);
|
|
3192
|
+
|
|
3193
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3194
|
+
const pendingAfterUpdate = await intercomTool.execute("pending-after-update", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3195
|
+
assert.match(pendingAfterUpdate.content[0]?.text ?? "", /boundary-ask-1/);
|
|
3196
|
+
|
|
3197
|
+
const replyReceived = waitForReply(orchestrator, askId);
|
|
3198
|
+
const sendResult = await intercomTool.execute("infer-after-update", { action: "send", to: "orchestrator", message: "No blockers." }, new AbortController().signal, undefined, harness.ctx);
|
|
3199
|
+
assert.equal(sendResult.details?.replyTo, askId);
|
|
3200
|
+
const reply = await replyReceived;
|
|
3201
|
+
assert.equal(reply.message.replyTo, askId);
|
|
3202
|
+
|
|
3203
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3204
|
+
});
|
|
3205
|
+
} finally {
|
|
3206
|
+
await cleanup();
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
|
|
3210
|
+
test("caller-supplied replyTo takes precedence over inference and skips the inferred result text", { concurrency: false }, async () => {
|
|
3211
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3212
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3213
|
+
const harness = createExtensionHarness("precedence-worker");
|
|
3214
|
+
|
|
3215
|
+
try {
|
|
3216
|
+
piIntercomExtension(harness.pi as never);
|
|
3217
|
+
await harness.emitLifecycle("session_start");
|
|
3218
|
+
const worker = await waitForSessionByName(orchestrator, "precedence-worker");
|
|
3219
|
+
|
|
3220
|
+
const inferableAskId = "precedence-inferable-ask";
|
|
3221
|
+
assert.equal((await planner.send(worker.id, { messageId: inferableAskId, text: "Sole pending ask", expectsReply: true })).delivered, true);
|
|
3222
|
+
// A second, distinct pending ask that the caller explicitly targets below —
|
|
3223
|
+
// it must be a real ask (broker rejects a replyTo that isn't a pending ask;
|
|
3224
|
+
// see "broker rejects unknown replyTo values..."), and it proves the explicit
|
|
3225
|
+
// choice isn't just whatever inference would have picked.
|
|
3226
|
+
const explicitReplyTo = "precedence-explicit-target";
|
|
3227
|
+
assert.equal((await planner.send(worker.id, { messageId: explicitReplyTo, text: "Explicit target ask", expectsReply: true })).delivered, true);
|
|
3228
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3229
|
+
|
|
3230
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3231
|
+
const replyReceived = waitForReply(planner, explicitReplyTo);
|
|
3232
|
+
const result = await intercomTool.execute("precedence-send", {
|
|
3233
|
+
action: "send",
|
|
3234
|
+
to: "planner",
|
|
3235
|
+
message: "Explicit wins.",
|
|
3236
|
+
replyTo: explicitReplyTo,
|
|
3237
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3238
|
+
|
|
3239
|
+
assert.equal(result.details?.replyTo, explicitReplyTo);
|
|
3240
|
+
assert.equal(result.content[0]?.text, "Message sent to planner");
|
|
3241
|
+
const reply = await replyReceived;
|
|
3242
|
+
assert.equal(reply.message.replyTo, explicitReplyTo);
|
|
3243
|
+
|
|
3244
|
+
const pending = await intercomTool.execute("pending-after-precedence", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3245
|
+
assert.match(pending.content[0]?.text ?? "", /precedence-inferable-ask/);
|
|
3246
|
+
} finally {
|
|
3247
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3248
|
+
await cleanup();
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
|
|
3252
|
+
test("multiple pending asks from the same sender leave a send unthreaded and preserve every ask", { concurrency: false }, async () => {
|
|
3253
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3254
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3255
|
+
const harness = createExtensionHarness("ambiguous-worker");
|
|
3256
|
+
|
|
3257
|
+
try {
|
|
3258
|
+
piIntercomExtension(harness.pi as never);
|
|
3259
|
+
await harness.emitLifecycle("session_start");
|
|
3260
|
+
const worker = await waitForSessionByName(orchestrator, "ambiguous-worker");
|
|
3261
|
+
|
|
3262
|
+
assert.equal((await planner.send(worker.id, { messageId: "ambiguous-ask-1", text: "First?", expectsReply: true })).delivered, true);
|
|
3263
|
+
assert.equal((await planner.send(worker.id, { messageId: "ambiguous-ask-2", text: "Second?", expectsReply: true })).delivered, true);
|
|
3264
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3265
|
+
|
|
3266
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3267
|
+
const result = await intercomTool.execute("ambiguous-send", {
|
|
3268
|
+
action: "send",
|
|
3269
|
+
to: "planner",
|
|
3270
|
+
message: "Unthreaded update.",
|
|
3271
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3272
|
+
|
|
3273
|
+
assert.equal(result.details?.replyTo, undefined);
|
|
3274
|
+
assert.equal(result.content[0]?.text, "Message sent to planner");
|
|
3275
|
+
|
|
3276
|
+
const pending = await intercomTool.execute("pending-after-ambiguous", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3277
|
+
assert.match(pending.content[0]?.text ?? "", /ambiguous-ask-1/);
|
|
3278
|
+
assert.match(pending.content[0]?.text ?? "", /ambiguous-ask-2/);
|
|
3279
|
+
} finally {
|
|
3280
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3281
|
+
await cleanup();
|
|
3282
|
+
}
|
|
3283
|
+
});
|
|
3284
|
+
|
|
3285
|
+
test("an expired pending ask is not inferred; the send remains ordinary", { concurrency: false }, async () => {
|
|
3286
|
+
await withAskTimeoutMs(50, async () => {
|
|
3287
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3288
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3289
|
+
const harness = createExtensionHarness("expiry-worker");
|
|
3290
|
+
|
|
3291
|
+
try {
|
|
3292
|
+
piIntercomExtension(harness.pi as never);
|
|
3293
|
+
await harness.emitLifecycle("session_start");
|
|
3294
|
+
const worker = await waitForSessionByName(orchestrator, "expiry-worker");
|
|
3295
|
+
|
|
3296
|
+
assert.equal((await planner.send(worker.id, { messageId: "expiry-ask-1", text: "Answer soon?", expectsReply: true })).delivered, true);
|
|
3297
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
3298
|
+
|
|
3299
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3300
|
+
const result = await intercomTool.execute("expiry-send", {
|
|
3301
|
+
action: "send",
|
|
3302
|
+
to: "planner",
|
|
3303
|
+
message: "Unrelated now.",
|
|
3304
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3305
|
+
|
|
3306
|
+
assert.equal(result.details?.replyTo, undefined);
|
|
3307
|
+
assert.equal(result.content[0]?.text, "Message sent to planner");
|
|
3308
|
+
} finally {
|
|
3309
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3310
|
+
await cleanup();
|
|
3311
|
+
}
|
|
3312
|
+
});
|
|
3313
|
+
});
|
|
3314
|
+
|
|
3315
|
+
test("intercom ask fails fast when the target is not currently connected", { concurrency: false }, async () => {
|
|
3316
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3317
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3318
|
+
const harness = createExtensionHarness("offline-ask-worker");
|
|
3319
|
+
|
|
3320
|
+
try {
|
|
3321
|
+
piIntercomExtension(harness.pi as never);
|
|
3322
|
+
await harness.emitLifecycle("session_start");
|
|
3323
|
+
await waitForSessionByName(orchestrator, "offline-ask-worker");
|
|
3324
|
+
const disconnectedId = planner.sessionId!;
|
|
3325
|
+
await planner.disconnect();
|
|
3326
|
+
|
|
3327
|
+
const controller = new AbortController();
|
|
3328
|
+
const timeout = setTimeout(() => controller.abort(), 250);
|
|
3329
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3330
|
+
const result = await intercomTool.execute("ask-offline", {
|
|
3331
|
+
action: "ask",
|
|
3332
|
+
to: disconnectedId,
|
|
3333
|
+
message: "This must not wait in the disconnected mailbox.",
|
|
3334
|
+
}, controller.signal, undefined, harness.ctx);
|
|
3335
|
+
clearTimeout(timeout);
|
|
3336
|
+
|
|
3337
|
+
assert.match(result.content[0]?.text ?? "", /not currently connected/i);
|
|
3338
|
+
assert.equal(result.details?.error, true);
|
|
3339
|
+
} finally {
|
|
3340
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3341
|
+
await cleanup();
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
|
|
3345
|
+
test("send falls back to an exact stored ID or name for a disconnected asker but never guesses from an ID prefix", { concurrency: false }, async () => {
|
|
3346
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3347
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3348
|
+
const harness = createExtensionHarness("disconnected-asker-worker");
|
|
3349
|
+
|
|
3350
|
+
try {
|
|
3351
|
+
piIntercomExtension(harness.pi as never);
|
|
3352
|
+
await harness.emitLifecycle("session_start");
|
|
3353
|
+
const worker = await waitForSessionByName(orchestrator, "disconnected-asker-worker");
|
|
3354
|
+
|
|
3355
|
+
const askId = "disconnected-ask-1";
|
|
3356
|
+
const originalPlannerId = planner.sessionId!;
|
|
3357
|
+
assert.equal((await planner.send(worker.id, { messageId: askId, text: "Any concerns before I disconnect?", expectsReply: true })).delivered, true);
|
|
3358
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3359
|
+
await planner.disconnect();
|
|
3360
|
+
|
|
3361
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3362
|
+
|
|
3363
|
+
const prefixResult = await intercomTool.execute("send-prefix", {
|
|
3364
|
+
action: "send",
|
|
3365
|
+
to: originalPlannerId.slice(0, 8),
|
|
3366
|
+
message: "Should not infer from a bare prefix.",
|
|
3367
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3368
|
+
assert.doesNotMatch(prefixResult.content[0]?.text ?? "", /inferred from pending ask/);
|
|
3369
|
+
assert.equal(prefixResult.details?.replyTo, undefined);
|
|
3370
|
+
|
|
3371
|
+
const pendingAfterPrefix = await intercomTool.execute("pending-after-prefix", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3372
|
+
assert.match(pendingAfterPrefix.content[0]?.text ?? "", /disconnected-ask-1/);
|
|
3373
|
+
|
|
3374
|
+
const replacement = new IntercomClient();
|
|
3375
|
+
const queuedReply = waitForReply(replacement, askId);
|
|
3376
|
+
|
|
3377
|
+
const exactResult = await intercomTool.execute("send-exact-id", {
|
|
3378
|
+
action: "send",
|
|
3379
|
+
to: originalPlannerId,
|
|
3380
|
+
message: "Reconnect and see this.",
|
|
3381
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3382
|
+
assert.match(exactResult.content[0]?.text ?? "", /inferred from pending ask/);
|
|
3383
|
+
assert.equal(exactResult.details?.replyTo, askId);
|
|
3384
|
+
|
|
3385
|
+
await replacement.connect({ name: "planner", cwd: repoDir, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() });
|
|
3386
|
+
const queuedMessage = await queuedReply;
|
|
3387
|
+
assert.equal(queuedMessage.message.replyTo, askId);
|
|
3388
|
+
|
|
3389
|
+
const pendingAfterExact = await intercomTool.execute("pending-after-exact-id", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3390
|
+
assert.equal(pendingAfterExact.content[0]?.text, "No unresolved inbound asks.");
|
|
3391
|
+
|
|
3392
|
+
await replacement.disconnect().catch(() => undefined);
|
|
3393
|
+
} finally {
|
|
3394
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3395
|
+
await cleanup();
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
|
|
3399
|
+
test("failed delivery from an inferred reply preserves the pending ask", { concurrency: false }, async () => {
|
|
3400
|
+
const { planner, orchestrator, cleanup } = await setupClients();
|
|
3401
|
+
const impostor = new IntercomClient();
|
|
3402
|
+
const { default: piIntercomExtension } = await import("./index.ts");
|
|
3403
|
+
const harness = createExtensionHarness("delivery-failure-worker");
|
|
3404
|
+
|
|
3405
|
+
try {
|
|
3406
|
+
piIntercomExtension(harness.pi as never);
|
|
3407
|
+
await harness.emitLifecycle("session_start");
|
|
3408
|
+
const worker = await waitForSessionByName(orchestrator, "delivery-failure-worker");
|
|
3409
|
+
|
|
3410
|
+
const askId = "delivery-failure-ask-1";
|
|
3411
|
+
assert.equal((await planner.send(worker.id, { messageId: askId, text: "Ping before disconnect", expectsReply: true })).delivered, true);
|
|
3412
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3413
|
+
await planner.disconnect();
|
|
3414
|
+
|
|
3415
|
+
await impostor.connect({ name: "planner", cwd: repoDir, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() });
|
|
3416
|
+
await impostor.disconnect();
|
|
3417
|
+
|
|
3418
|
+
const intercomTool = harness.tools.find((tool) => tool.name === "intercom")!;
|
|
3419
|
+
const result = await intercomTool.execute("send-ambiguous-disconnected", {
|
|
3420
|
+
action: "send",
|
|
3421
|
+
to: "planner",
|
|
3422
|
+
message: "Should not deliver.",
|
|
3423
|
+
}, new AbortController().signal, undefined, harness.ctx);
|
|
3424
|
+
|
|
3425
|
+
assert.equal(result.details?.delivered, false);
|
|
3426
|
+
assert.match(result.content[0]?.text ?? "", /Multiple disconnected sessions named/);
|
|
3427
|
+
|
|
3428
|
+
const pending = await intercomTool.execute("pending-after-failure", { action: "pending" }, new AbortController().signal, undefined, harness.ctx);
|
|
3429
|
+
assert.match(pending.content[0]?.text ?? "", /delivery-failure-ask-1/);
|
|
3430
|
+
} finally {
|
|
3431
|
+
await harness.emitLifecycle("session_shutdown");
|
|
3432
|
+
await cleanup();
|
|
3433
|
+
}
|
|
3434
|
+
});
|