@selesai/code 0.8.2 → 0.8.4

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.
Files changed (528) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +4 -2
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.js +18 -0
  5. package/dist/cli/config-selector.js +2 -2
  6. package/dist/cli/startup-ui.d.ts +1 -1
  7. package/dist/cli/startup-ui.js +2 -2
  8. package/dist/core/keybindings.d.ts +44 -4
  9. package/dist/core/llama/provider.js +22 -7
  10. package/dist/core/model-resolver.js +2 -0
  11. package/dist/core/model-runtime.d.ts +4 -2
  12. package/dist/core/model-runtime.js +23 -2
  13. package/dist/core/remote-catalog-provider.js +72 -67
  14. package/dist/core/settings-manager.d.ts +8 -0
  15. package/dist/core/settings-manager.js +18 -0
  16. package/dist/extensions/handoff-new.ts +1 -1
  17. package/dist/extensions/inline-skills.test.ts +1 -0
  18. package/dist/extensions/inline-skills.ts +1 -1
  19. package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
  20. package/dist/extensions/pi-intercom/README.md +34 -15
  21. package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
  22. package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
  23. package/dist/extensions/pi-intercom/broker/client.ts +78 -150
  24. package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
  25. package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
  26. package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
  27. package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
  28. package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
  29. package/dist/extensions/pi-intercom/config.test.ts +8 -10
  30. package/dist/extensions/pi-intercom/config.ts +3 -3
  31. package/dist/extensions/pi-intercom/index.ts +218 -147
  32. package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
  33. package/dist/extensions/pi-intercom/package.json +3 -2
  34. package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
  35. package/dist/extensions/pi-intercom/project-agent.ts +324 -0
  36. package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
  37. package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
  38. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
  39. package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
  40. package/dist/extensions/pi-intercom/types.ts +3 -1
  41. package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
  42. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
  43. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +15 -1
  44. package/dist/extensions/pi-powerline-footer/README.md +20 -37
  45. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
  46. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
  47. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
  48. package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
  49. package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
  50. package/dist/extensions/pi-powerline-footer/index.ts +394 -586
  51. package/dist/extensions/pi-powerline-footer/package.json +5 -6
  52. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -18
  53. package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
  54. package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
  55. package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
  56. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
  57. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
  58. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
  59. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
  60. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
  61. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +1 -1
  62. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +86 -19
  63. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
  64. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
  65. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
  66. package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
  67. package/dist/extensions/pi-powerline-footer/types.ts +1 -2
  68. package/dist/extensions/pi-subagents/CHANGELOG.md +202 -14
  69. package/dist/extensions/pi-subagents/README.md +65 -1614
  70. package/dist/extensions/pi-subagents/agents/builder.md +0 -1
  71. package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
  72. package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
  73. package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
  74. package/dist/extensions/pi-subagents/agents/scout.md +50 -0
  75. package/dist/extensions/pi-subagents/agents/worker.md +58 -0
  76. package/dist/extensions/pi-subagents/docs/configuration.md +328 -0
  77. package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
  78. package/dist/extensions/pi-subagents/docs/missions.md +119 -0
  79. package/dist/extensions/pi-subagents/docs/models.md +192 -0
  80. package/dist/extensions/pi-subagents/docs/observability.md +174 -0
  81. package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
  82. package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
  83. package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
  84. package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
  85. package/dist/extensions/pi-subagents/package-lock.json +152 -194
  86. package/dist/extensions/pi-subagents/package.json +9 -10
  87. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  88. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  89. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  90. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  91. package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
  92. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
  93. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
  94. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
  95. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
  96. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
  97. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
  98. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
  99. package/dist/extensions/pi-subagents/src/agents/agents.ts +29 -16
  100. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
  101. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  102. package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
  103. package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
  104. package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
  105. package/dist/extensions/pi-subagents/src/extension/config.ts +56 -8
  106. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
  107. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
  108. package/dist/extensions/pi-subagents/src/extension/index.ts +119 -50
  109. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +72 -0
  110. package/dist/extensions/pi-subagents/src/extension/rpc.ts +15 -15
  111. package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
  112. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
  113. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
  114. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
  115. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
  116. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
  117. package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
  118. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
  119. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -14
  120. package/dist/extensions/pi-subagents/src/missions/store.ts +147 -5
  121. package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
  122. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +256 -0
  123. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
  124. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +42 -0
  125. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +41 -7
  126. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
  127. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +19 -3
  128. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +39 -6
  129. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
  130. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
  131. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
  132. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
  133. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
  134. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
  135. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +21 -2
  136. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
  137. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
  138. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
  139. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +152 -68
  140. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
  141. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
  142. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
  143. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
  144. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
  145. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
  146. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +3 -0
  147. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +55 -26
  148. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
  149. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +798 -112
  150. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  151. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +165 -22
  152. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  153. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
  154. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +9 -5
  155. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
  156. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +1 -0
  157. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +465 -116
  158. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
  159. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
  160. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +2 -2
  161. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +3 -3
  162. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
  163. package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
  164. package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
  165. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
  166. package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
  167. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
  168. package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
  169. package/dist/extensions/pi-subagents/src/shared/types.ts +100 -3
  170. package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
  171. package/dist/extensions/pi-subagents/src/shared/utils.ts +43 -33
  172. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
  173. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
  174. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
  175. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
  176. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
  177. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
  178. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
  179. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +30 -16
  180. package/dist/extensions/pi-subagents/src/tui/fleet.ts +151 -31
  181. package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
  182. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
  183. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
  184. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
  185. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +313 -25
  186. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  362. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
  363. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
  364. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +15 -15
  365. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +411 -300
  366. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
  367. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +99 -56
  368. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
  369. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +159 -108
  370. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
  371. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
  372. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +56 -56
  373. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +114 -45
  374. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +57 -19
  375. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
  376. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
  377. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
  378. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +809 -87
  379. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
  380. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  381. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
  382. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  383. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  384. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
  385. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
  386. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +163 -58
  387. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  388. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  389. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
  390. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
  391. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  392. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +99 -99
  393. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
  394. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  395. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
  396. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
  397. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
  398. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
  399. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +2 -2
  400. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +94 -40
  401. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
  402. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
  403. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
  404. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +19 -19
  405. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  406. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  407. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  408. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
  409. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  410. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +52 -52
  411. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
  412. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
  413. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
  414. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
  415. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
  416. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  417. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  418. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  419. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
  420. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +62 -44
  421. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +329 -37
  422. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +4 -4
  423. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
  424. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
  425. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
  426. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
  427. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
  428. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +5 -5
  429. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
  430. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +55 -7
  431. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
  432. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +28 -1
  433. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
  434. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
  435. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
  436. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
  437. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
  438. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
  439. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
  440. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
  441. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  442. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  443. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
  444. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
  445. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +357 -92
  446. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +22 -6
  447. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
  448. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
  449. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  450. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +4 -4
  451. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
  452. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
  453. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
  454. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
  455. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +40 -0
  456. package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
  457. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
  458. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
  459. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
  460. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +107 -27
  461. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  462. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +56 -50
  463. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
  464. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  465. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +59 -44
  466. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
  467. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
  468. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
  469. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  470. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +120 -91
  471. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  472. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
  473. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  474. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
  475. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
  476. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
  477. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -26
  478. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +73 -0
  479. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
  480. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
  481. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
  482. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  483. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  484. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  485. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  486. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
  487. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
  488. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  489. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
  490. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +7 -7
  491. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
  492. package/dist/extensions/workflow/extension.ts +21 -19
  493. package/dist/extensions/workflow/modes.ts +65 -0
  494. package/dist/index.d.ts +1 -1
  495. package/dist/main.js +1 -0
  496. package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
  497. package/dist/modes/interactive/components/settings-selector.js +20 -0
  498. package/dist/modes/interactive/interactive-mode.d.ts +28 -0
  499. package/dist/modes/interactive/interactive-mode.js +214 -28
  500. package/dist/modes/interactive/theme/dark.json +1 -0
  501. package/dist/modes/interactive/theme/light.json +1 -0
  502. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  503. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  504. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  505. package/dist/modes/interactive/theme/theme.js +24 -6
  506. package/dist/skills/pi-subagents/SKILL.md +20 -15
  507. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
  508. package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
  509. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
  510. package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
  511. package/docs/keybindings.md +35 -7
  512. package/docs/plans/workflow-autoloop-reference.md +192 -0
  513. package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
  514. package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
  515. package/docs/settings.md +16 -14
  516. package/docs/usage.md +5 -0
  517. package/package.json +5 -5
  518. package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
  519. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
  520. package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
  521. package/dist/extensions/workflow/adapter.ts +0 -949
  522. package/dist/extensions/workflow/modes/loop.ts +0 -74
  523. package/dist/extensions/workflow/modes/prototype.ts +0 -170
  524. package/dist/extensions/workflow/modes/quicktype.ts +0 -150
  525. package/dist/extensions/workflow/modes/task.ts +0 -105
  526. package/dist/extensions/workflow/run-state.ts +0 -124
  527. package/dist/extensions/workflow/state-machine.ts +0 -484
  528. 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(), "pi-intercom-home-"));
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
- if (previousHome === undefined) delete process.env.HOME;
35
- else process.env.HOME = previousHome;
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 points at the short id when names collide", { concurrency: false }, async () => {
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() }, "aaaa1111-session");
897
- await twinB.connect({ name: "twin", cwd: repoDir, model: "test-model", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now() }, "bbbb2222-session");
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, /aaaa1111/);
908
- assert.match(text, /bbbb2222/);
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 Need a decision/);
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 Please answer/);
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("session_info_changed publishes Selesai name changes immediately", { concurrency: false }, async () => {
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 idle-gate top-level asks without aborting", { concurrency: false }, async () => {
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: "interactive-busy-ask",
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, 250));
1262
+ await new Promise((resolve) => setTimeout(resolve, 20));
1222
1263
  assert.equal(abortCount, 0);
1223
- assert.equal(harness.sentMessages.length, 0);
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 keep same-sender queued messages in sequence order", { concurrency: false }, async () => {
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 queued message" })).delivered, true);
1307
- assert.equal((await planner.send(worker.id, { messageId: "sequence-2", text: "Second queued message" })).delivered, true);
1308
- await new Promise((resolve) => setTimeout(resolve, 100));
1309
- assert.equal(harness.sentMessages.length, 0);
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, 100));
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", "queued", "injected"]);
1325
- assert.deepEqual(receipts.get("sequence-2"), ["receiver_received", "acknowledged", "queued", "injected"]);
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 removes a queued inbound message before injection", { concurrency: false }, async () => {
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-queued") receipts.push(receipt.status);
1417
+ if (receipt.messageId === "cancel-steered") receipts.push(receipt.status);
1349
1418
  });
1350
1419
 
1351
- assert.equal((await planner.send(worker.id, { messageId: "cancel-queued", text: "Cancel this before delivery", expectsReply: true })).delivered, true);
1352
- await new Promise((resolve) => setTimeout(resolve, 50));
1353
- assert.equal(harness.sentMessages.length, 0);
1354
- assert.equal((await planner.cancelMessage("cancel-queued")).delivered, true);
1355
- await new Promise((resolve) => setTimeout(resolve, 50));
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, 100));
1429
+ await new Promise((resolve) => setTimeout(resolve, 20));
1360
1430
 
1361
- assert.equal(harness.sentMessages.length, 0);
1362
- assert.deepEqual(receipts, ["receiver_received", "acknowledged", "queued", "cancelled"]);
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, 50));
1394
- assert.equal(receiverHarness.sentMessages.length, 0);
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, 100));
1403
- assert.equal(receiverHarness.sentMessages.length, 0);
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 replaces a queued inbound message", { concurrency: false }, async () => {
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 queued message", expectsReply: true })).delivered, true);
1432
- await new Promise((resolve) => setTimeout(resolve, 50));
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, 100));
1439
-
1440
- assert.equal(harness.sentMessages.length, 1);
1441
- assert.doesNotMatch(harness.sentMessages[0]?.message.content ?? "", /Old queued message/);
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
- let idle = false;
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 idle-gated asks are discarded before delivery", { concurrency: false }, async () => {
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, 50));
1513
- assert.equal(harness.sentMessages.length, 0);
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, 250));
1527
- assert.equal(harness.sentMessages.length, 0);
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("queued inbound messages are discarded after shutdown", { concurrency: false }, async () => {
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 not deliver after shutdown.",
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, 50));
1621
- assert.equal(harness.sentMessages.length, 0);
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, 250));
1703
+ await new Promise((resolve) => setTimeout(resolve, 20));
1627
1704
 
1628
- assert.equal(harness.sentMessages.length, 0);
1629
- assert.deepEqual(receipts, ["receiver_received", "acknowledged", "queued", "expired"]);
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, / - Stable API/);
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 tool preserves delivery failure reasons", { concurrency: false }, async () => {
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 ?? "", /Session not found/);
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 ?? "", /Session not found/);
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
+ });