@selesai/code 0.6.2 → 0.7.0

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 (997) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +1 -1
  3. package/dist/bun/cli.d.ts +0 -1
  4. package/dist/bun/cli.js +0 -1
  5. package/dist/bun/register-bedrock.d.ts +0 -1
  6. package/dist/bun/register-bedrock.js +0 -1
  7. package/dist/bun/restore-sandbox-env.d.ts +0 -1
  8. package/dist/bun/restore-sandbox-env.js +0 -1
  9. package/dist/cli/args.d.ts +0 -1
  10. package/dist/cli/args.js +0 -1
  11. package/dist/cli/config-selector.d.ts +0 -1
  12. package/dist/cli/config-selector.js +0 -1
  13. package/dist/cli/credential-print.d.ts +0 -1
  14. package/dist/cli/credential-print.js +0 -1
  15. package/dist/cli/file-processor.d.ts +0 -1
  16. package/dist/cli/file-processor.js +0 -1
  17. package/dist/cli/initial-message.d.ts +0 -1
  18. package/dist/cli/initial-message.js +0 -1
  19. package/dist/cli/list-models.d.ts +0 -1
  20. package/dist/cli/list-models.js +0 -1
  21. package/dist/cli/project-trust.d.ts +0 -1
  22. package/dist/cli/project-trust.js +0 -1
  23. package/dist/cli/session-picker.d.ts +0 -1
  24. package/dist/cli/session-picker.js +0 -1
  25. package/dist/cli/startup-ui.d.ts +0 -1
  26. package/dist/cli/startup-ui.js +0 -1
  27. package/dist/cli.d.ts +0 -1
  28. package/dist/cli.js +0 -1
  29. package/dist/config.d.ts +0 -1
  30. package/dist/config.js +0 -1
  31. package/dist/core/agent-session-auto-handoff.test.d.ts +0 -1
  32. package/dist/core/agent-session-auto-handoff.test.js +0 -1
  33. package/dist/core/agent-session-runtime.d.ts +0 -1
  34. package/dist/core/agent-session-runtime.js +0 -1
  35. package/dist/core/agent-session-services.d.ts +0 -1
  36. package/dist/core/agent-session-services.js +0 -1
  37. package/dist/core/agent-session-skill-block.test.d.ts +0 -1
  38. package/dist/core/agent-session-skill-block.test.js +0 -1
  39. package/dist/core/agent-session.d.ts +0 -1
  40. package/dist/core/agent-session.js +0 -1
  41. package/dist/core/agents.d.ts +0 -1
  42. package/dist/core/agents.js +0 -1
  43. package/dist/core/auth-guidance.d.ts +0 -1
  44. package/dist/core/auth-guidance.js +0 -1
  45. package/dist/core/auth-storage.d.ts +0 -1
  46. package/dist/core/auth-storage.js +0 -1
  47. package/dist/core/bash-executor.d.ts +0 -1
  48. package/dist/core/bash-executor.js +0 -1
  49. package/dist/core/built-in-extensions.d.ts +0 -1
  50. package/dist/core/built-in-extensions.js +0 -1
  51. package/dist/core/cache-stats.d.ts +0 -1
  52. package/dist/core/cache-stats.js +0 -1
  53. package/dist/core/compaction/branch-summarization.d.ts +0 -1
  54. package/dist/core/compaction/branch-summarization.js +0 -1
  55. package/dist/core/compaction/compaction.d.ts +0 -1
  56. package/dist/core/compaction/compaction.js +0 -1
  57. package/dist/core/compaction/index.d.ts +0 -1
  58. package/dist/core/compaction/index.js +0 -1
  59. package/dist/core/compaction/utils.d.ts +0 -1
  60. package/dist/core/compaction/utils.js +0 -1
  61. package/dist/core/defaults.d.ts +0 -1
  62. package/dist/core/defaults.js +0 -1
  63. package/dist/core/diagnostics.d.ts +0 -1
  64. package/dist/core/diagnostics.js +0 -1
  65. package/dist/core/event-bus.d.ts +0 -1
  66. package/dist/core/event-bus.js +0 -1
  67. package/dist/core/exec.d.ts +0 -1
  68. package/dist/core/exec.js +0 -1
  69. package/dist/core/experimental.d.ts +0 -1
  70. package/dist/core/experimental.js +0 -1
  71. package/dist/core/export-html/ansi-to-html.d.ts +0 -1
  72. package/dist/core/export-html/ansi-to-html.js +0 -1
  73. package/dist/core/export-html/index.d.ts +0 -1
  74. package/dist/core/export-html/index.js +0 -1
  75. package/dist/core/export-html/tool-renderer.d.ts +0 -1
  76. package/dist/core/export-html/tool-renderer.js +0 -1
  77. package/dist/core/extensions/index.d.ts +0 -1
  78. package/dist/core/extensions/index.js +0 -1
  79. package/dist/core/extensions/loader.d.ts +0 -1
  80. package/dist/core/extensions/loader.js +0 -1
  81. package/dist/core/extensions/runner.d.ts +0 -1
  82. package/dist/core/extensions/runner.js +0 -1
  83. package/dist/core/extensions/types.d.ts +0 -1
  84. package/dist/core/extensions/types.js +0 -1
  85. package/dist/core/extensions/wrapper.d.ts +0 -1
  86. package/dist/core/extensions/wrapper.js +0 -1
  87. package/dist/core/footer-data-provider.d.ts +0 -1
  88. package/dist/core/footer-data-provider.js +0 -1
  89. package/dist/core/git-command.d.ts +0 -1
  90. package/dist/core/git-command.js +0 -1
  91. package/dist/core/http-dispatcher.d.ts +0 -1
  92. package/dist/core/http-dispatcher.js +0 -1
  93. package/dist/core/index.d.ts +0 -1
  94. package/dist/core/index.js +0 -1
  95. package/dist/core/keybindings.d.ts +0 -1
  96. package/dist/core/keybindings.js +0 -1
  97. package/dist/core/llama/client.d.ts +0 -1
  98. package/dist/core/llama/client.js +0 -1
  99. package/dist/core/llama/huggingface.d.ts +0 -1
  100. package/dist/core/llama/huggingface.js +0 -1
  101. package/dist/core/llama/index.d.ts +0 -1
  102. package/dist/core/llama/index.js +0 -1
  103. package/dist/core/llama/provider.d.ts +0 -1
  104. package/dist/core/llama/provider.js +0 -1
  105. package/dist/core/llama/ui.d.ts +0 -1
  106. package/dist/core/llama/ui.js +0 -1
  107. package/dist/core/messages.d.ts +0 -1
  108. package/dist/core/messages.js +0 -1
  109. package/dist/core/model-config.d.ts +0 -1
  110. package/dist/core/model-config.js +0 -1
  111. package/dist/core/model-registry.d.ts +0 -1
  112. package/dist/core/model-registry.js +0 -1
  113. package/dist/core/model-resolver.d.ts +0 -1
  114. package/dist/core/model-resolver.js +0 -1
  115. package/dist/core/model-runtime.d.ts +0 -1
  116. package/dist/core/model-runtime.js +0 -1
  117. package/dist/core/models-store.d.ts +0 -1
  118. package/dist/core/models-store.js +0 -1
  119. package/dist/core/output-guard.d.ts +0 -1
  120. package/dist/core/output-guard.js +0 -1
  121. package/dist/core/package-manager.d.ts +0 -1
  122. package/dist/core/package-manager.js +0 -1
  123. package/dist/core/package-manager.test.d.ts +0 -1
  124. package/dist/core/package-manager.test.js +0 -1
  125. package/dist/core/project-trust.d.ts +0 -1
  126. package/dist/core/project-trust.js +0 -1
  127. package/dist/core/prompt-templates.d.ts +0 -1
  128. package/dist/core/prompt-templates.js +0 -1
  129. package/dist/core/provider-attribution.d.ts +0 -1
  130. package/dist/core/provider-attribution.js +0 -1
  131. package/dist/core/provider-composer.d.ts +0 -1
  132. package/dist/core/provider-composer.js +0 -1
  133. package/dist/core/radius.d.ts +0 -1
  134. package/dist/core/radius.js +0 -1
  135. package/dist/core/remote-catalog-provider.d.ts +0 -1
  136. package/dist/core/remote-catalog-provider.js +0 -1
  137. package/dist/core/resolve-config-value.d.ts +0 -1
  138. package/dist/core/resolve-config-value.js +0 -1
  139. package/dist/core/resource-loader.d.ts +0 -1
  140. package/dist/core/resource-loader.js +0 -1
  141. package/dist/core/runtime-credentials.d.ts +0 -1
  142. package/dist/core/runtime-credentials.js +0 -1
  143. package/dist/core/sdk.d.ts +0 -1
  144. package/dist/core/sdk.js +0 -1
  145. package/dist/core/session-cwd.d.ts +0 -1
  146. package/dist/core/session-cwd.js +0 -1
  147. package/dist/core/session-manager.d.ts +0 -1
  148. package/dist/core/session-manager.js +0 -1
  149. package/dist/core/settings-manager-auto-handoff.test.d.ts +0 -1
  150. package/dist/core/settings-manager-auto-handoff.test.js +0 -1
  151. package/dist/core/settings-manager.d.ts +0 -4
  152. package/dist/core/settings-manager.js +0 -12
  153. package/dist/core/skills.d.ts +0 -1
  154. package/dist/core/skills.js +0 -1
  155. package/dist/core/slash-commands.d.ts +0 -1
  156. package/dist/core/slash-commands.js +0 -1
  157. package/dist/core/source-info.d.ts +0 -1
  158. package/dist/core/source-info.js +0 -1
  159. package/dist/core/system-prompt.d.ts +0 -1
  160. package/dist/core/system-prompt.js +0 -1
  161. package/dist/core/system-prompt.test.d.ts +0 -1
  162. package/dist/core/system-prompt.test.js +0 -1
  163. package/dist/core/telemetry.d.ts +0 -1
  164. package/dist/core/telemetry.js +0 -1
  165. package/dist/core/timings.d.ts +0 -1
  166. package/dist/core/timings.js +0 -1
  167. package/dist/core/tools/bash.d.ts +0 -1
  168. package/dist/core/tools/bash.js +0 -1
  169. package/dist/core/tools/edit-diff.d.ts +0 -1
  170. package/dist/core/tools/edit-diff.js +0 -1
  171. package/dist/core/tools/edit.d.ts +0 -1
  172. package/dist/core/tools/edit.js +0 -1
  173. package/dist/core/tools/file-mutation-queue.d.ts +0 -1
  174. package/dist/core/tools/file-mutation-queue.js +0 -1
  175. package/dist/core/tools/find.d.ts +0 -1
  176. package/dist/core/tools/find.js +0 -1
  177. package/dist/core/tools/grep.d.ts +0 -1
  178. package/dist/core/tools/grep.js +0 -1
  179. package/dist/core/tools/index.d.ts +0 -1
  180. package/dist/core/tools/index.js +0 -1
  181. package/dist/core/tools/ls.d.ts +0 -1
  182. package/dist/core/tools/ls.js +0 -1
  183. package/dist/core/tools/output-accumulator.d.ts +0 -1
  184. package/dist/core/tools/output-accumulator.js +0 -1
  185. package/dist/core/tools/path-utils.d.ts +0 -1
  186. package/dist/core/tools/path-utils.js +0 -1
  187. package/dist/core/tools/read.d.ts +0 -1
  188. package/dist/core/tools/read.js +0 -1
  189. package/dist/core/tools/render-utils.d.ts +0 -1
  190. package/dist/core/tools/render-utils.js +0 -1
  191. package/dist/core/tools/tool-definition-wrapper.d.ts +0 -1
  192. package/dist/core/tools/tool-definition-wrapper.js +0 -1
  193. package/dist/core/tools/truncate.d.ts +0 -1
  194. package/dist/core/tools/truncate.js +0 -1
  195. package/dist/core/tools/write.d.ts +0 -1
  196. package/dist/core/tools/write.js +0 -1
  197. package/dist/core/trust-manager.d.ts +0 -1
  198. package/dist/core/trust-manager.js +0 -1
  199. package/dist/core/usage-totals.d.ts +0 -1
  200. package/dist/core/usage-totals.js +0 -1
  201. package/dist/defaults/models.json +8 -0
  202. package/dist/defaults/settings.json +6 -7
  203. package/dist/extensions/inline-skills.test.ts +13 -13
  204. package/dist/extensions/inline-skills.ts +6 -6
  205. package/dist/extensions/pi-subagents/CHANGELOG.md +89 -12
  206. package/dist/extensions/pi-subagents/README.md +339 -604
  207. package/dist/extensions/pi-subagents/package-lock.json +190 -2079
  208. package/dist/extensions/pi-subagents/package.json +13 -5
  209. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  210. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  211. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  212. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +10 -10
  213. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +32 -43
  214. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +106 -105
  215. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
  216. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +28 -35
  217. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +167 -124
  218. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  219. package/dist/extensions/pi-subagents/src/agents/agents.ts +163 -75
  220. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +10 -7
  221. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +5 -3
  222. package/dist/extensions/pi-subagents/src/agents/identity.ts +1 -1
  223. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +14 -11
  224. package/dist/extensions/pi-subagents/src/agents/skills.ts +23 -6
  225. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +8 -1
  226. package/dist/extensions/pi-subagents/src/api/control-channel.ts +4 -0
  227. package/dist/extensions/pi-subagents/src/api/delegation.ts +26 -194
  228. package/dist/extensions/pi-subagents/src/api/external-runs.ts +129 -0
  229. package/dist/extensions/pi-subagents/src/api/intercom-bridge.ts +3 -0
  230. package/dist/extensions/pi-subagents/src/api/pi-args.ts +5 -0
  231. package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -4
  232. package/dist/extensions/pi-subagents/src/api/shared-types.ts +19 -0
  233. package/dist/extensions/pi-subagents/src/extension/config.ts +10 -0
  234. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +5 -39
  235. package/dist/extensions/pi-subagents/src/extension/doctor.ts +10 -9
  236. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +7 -4
  237. package/dist/extensions/pi-subagents/src/extension/index.ts +243 -77
  238. package/dist/extensions/pi-subagents/src/extension/rpc.ts +18 -6
  239. package/dist/extensions/pi-subagents/src/extension/schemas.ts +50 -39
  240. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +36 -103
  241. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +229 -0
  242. package/dist/extensions/pi-subagents/src/inspectors/herdr/client.ts +130 -0
  243. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +141 -0
  244. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +154 -0
  245. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +330 -0
  246. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +3 -2
  247. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +5 -1
  248. package/dist/extensions/pi-subagents/src/missions/actions.ts +372 -0
  249. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +314 -0
  250. package/dist/extensions/pi-subagents/src/missions/store.ts +442 -0
  251. package/dist/extensions/pi-subagents/src/missions/types.ts +135 -0
  252. package/dist/extensions/pi-subagents/src/policy/authority.ts +46 -0
  253. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +32 -6
  254. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -72
  255. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +10 -2
  256. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -6
  257. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +29 -1
  258. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +3 -3
  259. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +3 -2
  260. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +9 -7
  261. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +3 -4
  262. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +3 -28
  263. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +5 -5
  264. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +18 -68
  265. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -3
  266. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +35 -8
  267. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +602 -375
  268. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -3
  269. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +612 -464
  270. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +50 -9
  271. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +253 -0
  272. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +12 -4
  273. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +3 -3
  274. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +8 -4
  275. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +74 -102
  276. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -22
  277. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1050 -396
  278. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +44 -18
  279. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +0 -1
  280. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +302 -22
  281. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  282. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +130 -0
  283. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +42 -1
  284. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +1 -1
  285. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +59 -5
  286. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -4
  287. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +86 -2
  288. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +11 -2
  289. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +95 -0
  290. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +11 -1
  291. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +15 -6
  292. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +1 -1
  293. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +9 -63
  294. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +37 -6
  295. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +5 -2
  296. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +2 -23
  297. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +6 -6
  298. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +124 -13
  299. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +29 -7
  300. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +18 -1
  301. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +3 -2
  302. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +1 -0
  303. package/dist/extensions/pi-subagents/src/shared/settings.ts +10 -0
  304. package/dist/extensions/pi-subagents/src/shared/types.ts +158 -55
  305. package/dist/extensions/pi-subagents/src/shared/utils.ts +16 -66
  306. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +33 -199
  307. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +43 -126
  308. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +103 -0
  309. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +158 -205
  310. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +22 -58
  311. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +14 -0
  312. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +44 -642
  313. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +18 -14
  314. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +156 -21
  315. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +110 -5
  316. package/dist/extensions/pi-subagents/src/tui/fleet.ts +60 -24
  317. package/dist/extensions/pi-subagents/src/tui/render.ts +296 -140
  318. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +14 -0
  319. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +1 -1
  320. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +13 -7
  321. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +2 -2
  322. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +145 -0
  323. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +1 -1
  324. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +1 -1
  325. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -1
  326. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +3 -2
  327. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +140 -0
  328. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +415 -0
  329. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +41 -144
  330. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +16 -16
  331. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +415 -298
  332. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +149 -82
  333. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +40 -40
  334. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +15 -15
  335. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +193 -133
  336. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -49
  337. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +62 -0
  338. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +56 -58
  339. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +81 -68
  340. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +20 -25
  341. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +425 -317
  342. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +73 -73
  343. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +123 -98
  344. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +653 -240
  345. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +20 -1661
  346. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  347. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +3 -3
  348. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  349. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +8 -5
  350. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +6 -6
  351. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +4 -20
  352. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +25 -4
  353. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  354. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +280 -295
  355. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +47 -207
  356. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  357. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +89 -89
  358. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  359. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +10 -0
  360. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +33 -9
  361. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +24 -2
  362. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +11 -9
  363. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +2 -2
  364. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +40 -40
  365. package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +22 -0
  366. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +1 -1
  367. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +5 -39
  368. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +19 -19
  369. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  370. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  371. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +99 -0
  372. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  373. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +41 -0
  374. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  375. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +35 -5
  376. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +7 -0
  377. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +16 -82
  378. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +162 -620
  379. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  380. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  381. package/dist/extensions/pi-subagents/test/unit/ensure-accessible-dir.test.ts +125 -0
  382. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +94 -0
  383. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +66 -0
  384. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  385. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +242 -52
  386. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +127 -0
  387. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +197 -36
  388. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +4 -4
  389. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +245 -0
  390. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +494 -0
  391. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +261 -31
  392. package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +52 -0
  393. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +6 -6
  394. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +217 -0
  395. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +182 -0
  396. package/dist/extensions/pi-subagents/test/unit/mock-pi.test.ts +22 -0
  397. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +1 -1
  398. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +10 -10
  399. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -12
  400. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +47 -5
  401. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +49 -55
  402. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +20 -3
  403. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +37 -7
  404. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  405. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  406. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +46 -0
  407. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +31 -10
  408. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +40 -16
  409. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +42 -44
  410. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  411. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +4 -4
  412. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +75 -9
  413. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +20 -84
  414. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +15 -16
  415. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +28 -28
  416. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +19 -12
  417. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +37 -21
  418. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  419. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +50 -50
  420. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +396 -433
  421. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +76 -153
  422. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +411 -0
  423. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +1 -91
  424. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +139 -0
  425. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +29 -0
  426. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  427. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
  428. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  429. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  430. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +53 -18
  431. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +13 -1
  432. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +44 -11
  433. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +2 -26
  434. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +24 -148
  435. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +11 -9
  436. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +351 -0
  437. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  438. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  439. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +79 -0
  440. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  441. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  442. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +45 -9
  443. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +197 -0
  444. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  445. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +142 -12
  446. package/dist/extensions/pi-subagents/tsconfig.json +15 -0
  447. package/dist/extensions/pi-web-agent/package.json +1 -1
  448. package/dist/extensions/rtk.test.ts +126 -0
  449. package/dist/extensions/rtk.ts +56 -20
  450. package/dist/index.d.ts +1 -1
  451. package/dist/index.js +1 -1
  452. package/dist/main.d.ts +0 -1
  453. package/dist/main.js +0 -1
  454. package/dist/migrations.d.ts +0 -1
  455. package/dist/migrations.js +0 -1
  456. package/dist/modes/index.d.ts +0 -1
  457. package/dist/modes/index.js +0 -1
  458. package/dist/modes/interactive/components/armin.d.ts +0 -1
  459. package/dist/modes/interactive/components/armin.js +0 -1
  460. package/dist/modes/interactive/components/assistant-message.d.ts +0 -1
  461. package/dist/modes/interactive/components/assistant-message.js +0 -1
  462. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  463. package/dist/modes/interactive/components/bash-execution.js +0 -1
  464. package/dist/modes/interactive/components/bordered-loader.d.ts +0 -1
  465. package/dist/modes/interactive/components/bordered-loader.js +0 -1
  466. package/dist/modes/interactive/components/branch-summary-message.d.ts +0 -1
  467. package/dist/modes/interactive/components/branch-summary-message.js +0 -1
  468. package/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -1
  469. package/dist/modes/interactive/components/compaction-summary-message.js +0 -1
  470. package/dist/modes/interactive/components/config-selector.d.ts +0 -1
  471. package/dist/modes/interactive/components/config-selector.js +0 -1
  472. package/dist/modes/interactive/components/countdown-timer.d.ts +0 -1
  473. package/dist/modes/interactive/components/countdown-timer.js +0 -1
  474. package/dist/modes/interactive/components/custom-editor.d.ts +0 -1
  475. package/dist/modes/interactive/components/custom-editor.js +0 -1
  476. package/dist/modes/interactive/components/custom-entry.d.ts +0 -1
  477. package/dist/modes/interactive/components/custom-entry.js +0 -1
  478. package/dist/modes/interactive/components/custom-message.d.ts +0 -1
  479. package/dist/modes/interactive/components/custom-message.js +0 -1
  480. package/dist/modes/interactive/components/daxnuts.d.ts +0 -1
  481. package/dist/modes/interactive/components/daxnuts.js +0 -1
  482. package/dist/modes/interactive/components/diff.d.ts +0 -1
  483. package/dist/modes/interactive/components/diff.js +0 -1
  484. package/dist/modes/interactive/components/dynamic-border.d.ts +0 -1
  485. package/dist/modes/interactive/components/dynamic-border.js +0 -1
  486. package/dist/modes/interactive/components/earendil-announcement.d.ts +0 -1
  487. package/dist/modes/interactive/components/earendil-announcement.js +0 -1
  488. package/dist/modes/interactive/components/extension-editor.d.ts +0 -1
  489. package/dist/modes/interactive/components/extension-editor.js +0 -1
  490. package/dist/modes/interactive/components/extension-input.d.ts +0 -1
  491. package/dist/modes/interactive/components/extension-input.js +0 -1
  492. package/dist/modes/interactive/components/extension-selector.d.ts +0 -1
  493. package/dist/modes/interactive/components/extension-selector.js +0 -1
  494. package/dist/modes/interactive/components/first-time-setup.d.ts +0 -1
  495. package/dist/modes/interactive/components/first-time-setup.js +0 -1
  496. package/dist/modes/interactive/components/footer.d.ts +0 -1
  497. package/dist/modes/interactive/components/footer.js +0 -1
  498. package/dist/modes/interactive/components/index.d.ts +0 -1
  499. package/dist/modes/interactive/components/index.js +0 -1
  500. package/dist/modes/interactive/components/keybinding-hints.d.ts +0 -1
  501. package/dist/modes/interactive/components/keybinding-hints.js +0 -1
  502. package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
  503. package/dist/modes/interactive/components/login-dialog.js +0 -1
  504. package/dist/modes/interactive/components/model-selector.d.ts +0 -1
  505. package/dist/modes/interactive/components/model-selector.js +0 -1
  506. package/dist/modes/interactive/components/oauth-selector.d.ts +0 -1
  507. package/dist/modes/interactive/components/oauth-selector.js +0 -1
  508. package/dist/modes/interactive/components/scoped-models-selector.d.ts +0 -1
  509. package/dist/modes/interactive/components/scoped-models-selector.js +0 -1
  510. package/dist/modes/interactive/components/session-selector-search.d.ts +0 -1
  511. package/dist/modes/interactive/components/session-selector-search.js +0 -1
  512. package/dist/modes/interactive/components/session-selector.d.ts +0 -1
  513. package/dist/modes/interactive/components/session-selector.js +0 -1
  514. package/dist/modes/interactive/components/settings-selector.d.ts +0 -3
  515. package/dist/modes/interactive/components/settings-selector.js +1 -14
  516. package/dist/modes/interactive/components/show-images-selector.d.ts +0 -1
  517. package/dist/modes/interactive/components/show-images-selector.js +0 -1
  518. package/dist/modes/interactive/components/skill-invocation-message.d.ts +0 -1
  519. package/dist/modes/interactive/components/skill-invocation-message.js +0 -1
  520. package/dist/modes/interactive/components/status-indicator.d.ts +0 -1
  521. package/dist/modes/interactive/components/status-indicator.js +0 -1
  522. package/dist/modes/interactive/components/theme-selector.d.ts +0 -1
  523. package/dist/modes/interactive/components/theme-selector.js +0 -1
  524. package/dist/modes/interactive/components/thinking-selector.d.ts +0 -1
  525. package/dist/modes/interactive/components/thinking-selector.js +0 -1
  526. package/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  527. package/dist/modes/interactive/components/tool-execution.js +0 -1
  528. package/dist/modes/interactive/components/tree-selector.d.ts +0 -1
  529. package/dist/modes/interactive/components/tree-selector.js +0 -1
  530. package/dist/modes/interactive/components/trust-selector.d.ts +0 -1
  531. package/dist/modes/interactive/components/trust-selector.js +0 -1
  532. package/dist/modes/interactive/components/user-message-selector.d.ts +0 -1
  533. package/dist/modes/interactive/components/user-message-selector.js +0 -1
  534. package/dist/modes/interactive/components/user-message.d.ts +0 -1
  535. package/dist/modes/interactive/components/user-message.js +0 -1
  536. package/dist/modes/interactive/components/visual-truncate.d.ts +0 -1
  537. package/dist/modes/interactive/components/visual-truncate.js +0 -1
  538. package/dist/modes/interactive/external-editor.d.ts +0 -1
  539. package/dist/modes/interactive/external-editor.js +0 -1
  540. package/dist/modes/interactive/interactive-mode.d.ts +0 -2
  541. package/dist/modes/interactive/interactive-mode.js +1 -22
  542. package/dist/modes/interactive/model-search.d.ts +0 -1
  543. package/dist/modes/interactive/model-search.js +0 -1
  544. package/dist/modes/interactive/theme/theme-controller.d.ts +0 -1
  545. package/dist/modes/interactive/theme/theme-controller.js +0 -1
  546. package/dist/modes/interactive/theme/theme.d.ts +0 -1
  547. package/dist/modes/interactive/theme/theme.js +0 -1
  548. package/dist/modes/print-mode.d.ts +0 -1
  549. package/dist/modes/print-mode.js +0 -1
  550. package/dist/modes/rpc/jsonl.d.ts +0 -1
  551. package/dist/modes/rpc/jsonl.js +0 -1
  552. package/dist/modes/rpc/rpc-client.d.ts +1 -2
  553. package/dist/modes/rpc/rpc-client.js +1 -2
  554. package/dist/modes/rpc/rpc-mode.d.ts +0 -1
  555. package/dist/modes/rpc/rpc-mode.js +0 -9
  556. package/dist/modes/rpc/rpc-types.d.ts +0 -1
  557. package/dist/modes/rpc/rpc-types.js +0 -1
  558. package/dist/package-manager-cli.d.ts +0 -1
  559. package/dist/package-manager-cli.js +0 -1
  560. package/dist/rpc-entry.d.ts +0 -1
  561. package/dist/rpc-entry.js +0 -1
  562. package/dist/utils/ansi.d.ts +0 -1
  563. package/dist/utils/ansi.js +0 -1
  564. package/dist/utils/changelog.d.ts +0 -1
  565. package/dist/utils/changelog.js +0 -1
  566. package/dist/utils/child-process.d.ts +0 -1
  567. package/dist/utils/child-process.js +0 -1
  568. package/dist/utils/clipboard-image.d.ts +0 -1
  569. package/dist/utils/clipboard-image.js +0 -1
  570. package/dist/utils/clipboard-native.d.ts +0 -1
  571. package/dist/utils/clipboard-native.js +0 -1
  572. package/dist/utils/clipboard.d.ts +0 -1
  573. package/dist/utils/clipboard.js +0 -1
  574. package/dist/utils/deprecation.d.ts +0 -1
  575. package/dist/utils/deprecation.js +0 -1
  576. package/dist/utils/exif-orientation.d.ts +0 -1
  577. package/dist/utils/exif-orientation.js +0 -1
  578. package/dist/utils/frontmatter.d.ts +0 -1
  579. package/dist/utils/frontmatter.js +0 -1
  580. package/dist/utils/fs-watch.d.ts +0 -1
  581. package/dist/utils/fs-watch.js +0 -1
  582. package/dist/utils/git.d.ts +0 -1
  583. package/dist/utils/git.js +0 -1
  584. package/dist/utils/html.d.ts +0 -1
  585. package/dist/utils/html.js +0 -1
  586. package/dist/utils/image-convert.d.ts +0 -1
  587. package/dist/utils/image-convert.js +0 -1
  588. package/dist/utils/image-process.d.ts +0 -1
  589. package/dist/utils/image-process.js +0 -1
  590. package/dist/utils/image-resize-core.d.ts +0 -1
  591. package/dist/utils/image-resize-core.js +0 -1
  592. package/dist/utils/image-resize-worker.d.ts +0 -1
  593. package/dist/utils/image-resize-worker.js +0 -1
  594. package/dist/utils/image-resize.d.ts +0 -1
  595. package/dist/utils/image-resize.js +0 -1
  596. package/dist/utils/json.d.ts +0 -1
  597. package/dist/utils/json.js +0 -1
  598. package/dist/utils/mime.d.ts +0 -1
  599. package/dist/utils/mime.js +0 -1
  600. package/dist/utils/open-browser.d.ts +0 -1
  601. package/dist/utils/open-browser.js +0 -1
  602. package/dist/utils/paths.d.ts +0 -1
  603. package/dist/utils/paths.js +0 -1
  604. package/dist/utils/photon.d.ts +0 -1
  605. package/dist/utils/photon.js +0 -1
  606. package/dist/utils/pi-user-agent.d.ts +0 -1
  607. package/dist/utils/pi-user-agent.js +0 -1
  608. package/dist/utils/shell.d.ts +0 -1
  609. package/dist/utils/shell.js +0 -1
  610. package/dist/utils/sleep.d.ts +0 -1
  611. package/dist/utils/sleep.js +0 -1
  612. package/dist/utils/syntax-highlight.d.ts +0 -1
  613. package/dist/utils/syntax-highlight.js +0 -1
  614. package/dist/utils/thinking-tags.d.ts +0 -1
  615. package/dist/utils/thinking-tags.js +0 -1
  616. package/dist/utils/tools-manager.d.ts +3 -3
  617. package/dist/utils/tools-manager.js +125 -42
  618. package/dist/utils/version-check.d.ts +0 -1
  619. package/dist/utils/version-check.js +0 -1
  620. package/dist/utils/windows-self-update.d.ts +0 -1
  621. package/dist/utils/windows-self-update.js +0 -1
  622. package/docs/rpc.md +1 -2
  623. package/docs/settings.md +0 -1
  624. package/docs/skills.md +10 -18
  625. package/package.json +4 -3
  626. package/dist/bun/cli.d.ts.map +0 -1
  627. package/dist/bun/cli.js.map +0 -1
  628. package/dist/bun/register-bedrock.d.ts.map +0 -1
  629. package/dist/bun/register-bedrock.js.map +0 -1
  630. package/dist/bun/restore-sandbox-env.d.ts.map +0 -1
  631. package/dist/bun/restore-sandbox-env.js.map +0 -1
  632. package/dist/cli/args.d.ts.map +0 -1
  633. package/dist/cli/args.js.map +0 -1
  634. package/dist/cli/config-selector.d.ts.map +0 -1
  635. package/dist/cli/config-selector.js.map +0 -1
  636. package/dist/cli/credential-print.d.ts.map +0 -1
  637. package/dist/cli/credential-print.js.map +0 -1
  638. package/dist/cli/file-processor.d.ts.map +0 -1
  639. package/dist/cli/file-processor.js.map +0 -1
  640. package/dist/cli/initial-message.d.ts.map +0 -1
  641. package/dist/cli/initial-message.js.map +0 -1
  642. package/dist/cli/list-models.d.ts.map +0 -1
  643. package/dist/cli/list-models.js.map +0 -1
  644. package/dist/cli/project-trust.d.ts.map +0 -1
  645. package/dist/cli/project-trust.js.map +0 -1
  646. package/dist/cli/session-picker.d.ts.map +0 -1
  647. package/dist/cli/session-picker.js.map +0 -1
  648. package/dist/cli/startup-ui.d.ts.map +0 -1
  649. package/dist/cli/startup-ui.js.map +0 -1
  650. package/dist/cli.d.ts.map +0 -1
  651. package/dist/cli.js.map +0 -1
  652. package/dist/config.d.ts.map +0 -1
  653. package/dist/config.js.map +0 -1
  654. package/dist/core/agent-session-auto-handoff.test.d.ts.map +0 -1
  655. package/dist/core/agent-session-auto-handoff.test.js.map +0 -1
  656. package/dist/core/agent-session-runtime.d.ts.map +0 -1
  657. package/dist/core/agent-session-runtime.js.map +0 -1
  658. package/dist/core/agent-session-services.d.ts.map +0 -1
  659. package/dist/core/agent-session-services.js.map +0 -1
  660. package/dist/core/agent-session-skill-block.test.d.ts.map +0 -1
  661. package/dist/core/agent-session-skill-block.test.js.map +0 -1
  662. package/dist/core/agent-session.d.ts.map +0 -1
  663. package/dist/core/agent-session.js.map +0 -1
  664. package/dist/core/agents.d.ts.map +0 -1
  665. package/dist/core/agents.js.map +0 -1
  666. package/dist/core/auth-guidance.d.ts.map +0 -1
  667. package/dist/core/auth-guidance.js.map +0 -1
  668. package/dist/core/auth-storage.d.ts.map +0 -1
  669. package/dist/core/auth-storage.js.map +0 -1
  670. package/dist/core/bash-executor.d.ts.map +0 -1
  671. package/dist/core/bash-executor.js.map +0 -1
  672. package/dist/core/built-in-extensions.d.ts.map +0 -1
  673. package/dist/core/built-in-extensions.js.map +0 -1
  674. package/dist/core/cache-stats.d.ts.map +0 -1
  675. package/dist/core/cache-stats.js.map +0 -1
  676. package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
  677. package/dist/core/compaction/branch-summarization.js.map +0 -1
  678. package/dist/core/compaction/compaction.d.ts.map +0 -1
  679. package/dist/core/compaction/compaction.js.map +0 -1
  680. package/dist/core/compaction/index.d.ts.map +0 -1
  681. package/dist/core/compaction/index.js.map +0 -1
  682. package/dist/core/compaction/utils.d.ts.map +0 -1
  683. package/dist/core/compaction/utils.js.map +0 -1
  684. package/dist/core/defaults.d.ts.map +0 -1
  685. package/dist/core/defaults.js.map +0 -1
  686. package/dist/core/diagnostics.d.ts.map +0 -1
  687. package/dist/core/diagnostics.js.map +0 -1
  688. package/dist/core/event-bus.d.ts.map +0 -1
  689. package/dist/core/event-bus.js.map +0 -1
  690. package/dist/core/exec.d.ts.map +0 -1
  691. package/dist/core/exec.js.map +0 -1
  692. package/dist/core/experimental.d.ts.map +0 -1
  693. package/dist/core/experimental.js.map +0 -1
  694. package/dist/core/export-html/ansi-to-html.d.ts.map +0 -1
  695. package/dist/core/export-html/ansi-to-html.js.map +0 -1
  696. package/dist/core/export-html/index.d.ts.map +0 -1
  697. package/dist/core/export-html/index.js.map +0 -1
  698. package/dist/core/export-html/tool-renderer.d.ts.map +0 -1
  699. package/dist/core/export-html/tool-renderer.js.map +0 -1
  700. package/dist/core/extensions/index.d.ts.map +0 -1
  701. package/dist/core/extensions/index.js.map +0 -1
  702. package/dist/core/extensions/loader.d.ts.map +0 -1
  703. package/dist/core/extensions/loader.js.map +0 -1
  704. package/dist/core/extensions/runner.d.ts.map +0 -1
  705. package/dist/core/extensions/runner.js.map +0 -1
  706. package/dist/core/extensions/types.d.ts.map +0 -1
  707. package/dist/core/extensions/types.js.map +0 -1
  708. package/dist/core/extensions/wrapper.d.ts.map +0 -1
  709. package/dist/core/extensions/wrapper.js.map +0 -1
  710. package/dist/core/footer-data-provider.d.ts.map +0 -1
  711. package/dist/core/footer-data-provider.js.map +0 -1
  712. package/dist/core/git-command.d.ts.map +0 -1
  713. package/dist/core/git-command.js.map +0 -1
  714. package/dist/core/http-dispatcher.d.ts.map +0 -1
  715. package/dist/core/http-dispatcher.js.map +0 -1
  716. package/dist/core/index.d.ts.map +0 -1
  717. package/dist/core/index.js.map +0 -1
  718. package/dist/core/keybindings.d.ts.map +0 -1
  719. package/dist/core/keybindings.js.map +0 -1
  720. package/dist/core/llama/client.d.ts.map +0 -1
  721. package/dist/core/llama/client.js.map +0 -1
  722. package/dist/core/llama/huggingface.d.ts.map +0 -1
  723. package/dist/core/llama/huggingface.js.map +0 -1
  724. package/dist/core/llama/index.d.ts.map +0 -1
  725. package/dist/core/llama/index.js.map +0 -1
  726. package/dist/core/llama/provider.d.ts.map +0 -1
  727. package/dist/core/llama/provider.js.map +0 -1
  728. package/dist/core/llama/ui.d.ts.map +0 -1
  729. package/dist/core/llama/ui.js.map +0 -1
  730. package/dist/core/messages.d.ts.map +0 -1
  731. package/dist/core/messages.js.map +0 -1
  732. package/dist/core/model-config.d.ts.map +0 -1
  733. package/dist/core/model-config.js.map +0 -1
  734. package/dist/core/model-registry.d.ts.map +0 -1
  735. package/dist/core/model-registry.js.map +0 -1
  736. package/dist/core/model-resolver.d.ts.map +0 -1
  737. package/dist/core/model-resolver.js.map +0 -1
  738. package/dist/core/model-runtime.d.ts.map +0 -1
  739. package/dist/core/model-runtime.js.map +0 -1
  740. package/dist/core/models-store.d.ts.map +0 -1
  741. package/dist/core/models-store.js.map +0 -1
  742. package/dist/core/output-guard.d.ts.map +0 -1
  743. package/dist/core/output-guard.js.map +0 -1
  744. package/dist/core/package-manager.d.ts.map +0 -1
  745. package/dist/core/package-manager.js.map +0 -1
  746. package/dist/core/package-manager.test.d.ts.map +0 -1
  747. package/dist/core/package-manager.test.js.map +0 -1
  748. package/dist/core/project-trust.d.ts.map +0 -1
  749. package/dist/core/project-trust.js.map +0 -1
  750. package/dist/core/prompt-templates.d.ts.map +0 -1
  751. package/dist/core/prompt-templates.js.map +0 -1
  752. package/dist/core/provider-attribution.d.ts.map +0 -1
  753. package/dist/core/provider-attribution.js.map +0 -1
  754. package/dist/core/provider-composer.d.ts.map +0 -1
  755. package/dist/core/provider-composer.js.map +0 -1
  756. package/dist/core/radius.d.ts.map +0 -1
  757. package/dist/core/radius.js.map +0 -1
  758. package/dist/core/remote-catalog-provider.d.ts.map +0 -1
  759. package/dist/core/remote-catalog-provider.js.map +0 -1
  760. package/dist/core/resolve-config-value.d.ts.map +0 -1
  761. package/dist/core/resolve-config-value.js.map +0 -1
  762. package/dist/core/resource-loader.d.ts.map +0 -1
  763. package/dist/core/resource-loader.js.map +0 -1
  764. package/dist/core/runtime-credentials.d.ts.map +0 -1
  765. package/dist/core/runtime-credentials.js.map +0 -1
  766. package/dist/core/sdk.d.ts.map +0 -1
  767. package/dist/core/sdk.js.map +0 -1
  768. package/dist/core/session-cwd.d.ts.map +0 -1
  769. package/dist/core/session-cwd.js.map +0 -1
  770. package/dist/core/session-manager.d.ts.map +0 -1
  771. package/dist/core/session-manager.js.map +0 -1
  772. package/dist/core/settings-manager-auto-handoff.test.d.ts.map +0 -1
  773. package/dist/core/settings-manager-auto-handoff.test.js.map +0 -1
  774. package/dist/core/settings-manager.d.ts.map +0 -1
  775. package/dist/core/settings-manager.js.map +0 -1
  776. package/dist/core/skills.d.ts.map +0 -1
  777. package/dist/core/skills.js.map +0 -1
  778. package/dist/core/slash-commands.d.ts.map +0 -1
  779. package/dist/core/slash-commands.js.map +0 -1
  780. package/dist/core/source-info.d.ts.map +0 -1
  781. package/dist/core/source-info.js.map +0 -1
  782. package/dist/core/system-prompt.d.ts.map +0 -1
  783. package/dist/core/system-prompt.js.map +0 -1
  784. package/dist/core/system-prompt.test.d.ts.map +0 -1
  785. package/dist/core/system-prompt.test.js.map +0 -1
  786. package/dist/core/telemetry.d.ts.map +0 -1
  787. package/dist/core/telemetry.js.map +0 -1
  788. package/dist/core/timings.d.ts.map +0 -1
  789. package/dist/core/timings.js.map +0 -1
  790. package/dist/core/tools/bash.d.ts.map +0 -1
  791. package/dist/core/tools/bash.js.map +0 -1
  792. package/dist/core/tools/edit-diff.d.ts.map +0 -1
  793. package/dist/core/tools/edit-diff.js.map +0 -1
  794. package/dist/core/tools/edit.d.ts.map +0 -1
  795. package/dist/core/tools/edit.js.map +0 -1
  796. package/dist/core/tools/file-mutation-queue.d.ts.map +0 -1
  797. package/dist/core/tools/file-mutation-queue.js.map +0 -1
  798. package/dist/core/tools/find.d.ts.map +0 -1
  799. package/dist/core/tools/find.js.map +0 -1
  800. package/dist/core/tools/grep.d.ts.map +0 -1
  801. package/dist/core/tools/grep.js.map +0 -1
  802. package/dist/core/tools/index.d.ts.map +0 -1
  803. package/dist/core/tools/index.js.map +0 -1
  804. package/dist/core/tools/ls.d.ts.map +0 -1
  805. package/dist/core/tools/ls.js.map +0 -1
  806. package/dist/core/tools/output-accumulator.d.ts.map +0 -1
  807. package/dist/core/tools/output-accumulator.js.map +0 -1
  808. package/dist/core/tools/path-utils.d.ts.map +0 -1
  809. package/dist/core/tools/path-utils.js.map +0 -1
  810. package/dist/core/tools/read.d.ts.map +0 -1
  811. package/dist/core/tools/read.js.map +0 -1
  812. package/dist/core/tools/render-utils.d.ts.map +0 -1
  813. package/dist/core/tools/render-utils.js.map +0 -1
  814. package/dist/core/tools/tool-definition-wrapper.d.ts.map +0 -1
  815. package/dist/core/tools/tool-definition-wrapper.js.map +0 -1
  816. package/dist/core/tools/truncate.d.ts.map +0 -1
  817. package/dist/core/tools/truncate.js.map +0 -1
  818. package/dist/core/tools/write.d.ts.map +0 -1
  819. package/dist/core/tools/write.js.map +0 -1
  820. package/dist/core/trust-manager.d.ts.map +0 -1
  821. package/dist/core/trust-manager.js.map +0 -1
  822. package/dist/core/usage-totals.d.ts.map +0 -1
  823. package/dist/core/usage-totals.js.map +0 -1
  824. package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  825. package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +0 -464
  826. package/dist/index.d.ts.map +0 -1
  827. package/dist/index.js.map +0 -1
  828. package/dist/main.d.ts.map +0 -1
  829. package/dist/main.js.map +0 -1
  830. package/dist/migrations.d.ts.map +0 -1
  831. package/dist/migrations.js.map +0 -1
  832. package/dist/modes/index.d.ts.map +0 -1
  833. package/dist/modes/index.js.map +0 -1
  834. package/dist/modes/interactive/components/armin.d.ts.map +0 -1
  835. package/dist/modes/interactive/components/armin.js.map +0 -1
  836. package/dist/modes/interactive/components/assistant-message.d.ts.map +0 -1
  837. package/dist/modes/interactive/components/assistant-message.js.map +0 -1
  838. package/dist/modes/interactive/components/bash-execution.d.ts.map +0 -1
  839. package/dist/modes/interactive/components/bash-execution.js.map +0 -1
  840. package/dist/modes/interactive/components/bordered-loader.d.ts.map +0 -1
  841. package/dist/modes/interactive/components/bordered-loader.js.map +0 -1
  842. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +0 -1
  843. package/dist/modes/interactive/components/branch-summary-message.js.map +0 -1
  844. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +0 -1
  845. package/dist/modes/interactive/components/compaction-summary-message.js.map +0 -1
  846. package/dist/modes/interactive/components/config-selector.d.ts.map +0 -1
  847. package/dist/modes/interactive/components/config-selector.js.map +0 -1
  848. package/dist/modes/interactive/components/countdown-timer.d.ts.map +0 -1
  849. package/dist/modes/interactive/components/countdown-timer.js.map +0 -1
  850. package/dist/modes/interactive/components/custom-editor.d.ts.map +0 -1
  851. package/dist/modes/interactive/components/custom-editor.js.map +0 -1
  852. package/dist/modes/interactive/components/custom-entry.d.ts.map +0 -1
  853. package/dist/modes/interactive/components/custom-entry.js.map +0 -1
  854. package/dist/modes/interactive/components/custom-message.d.ts.map +0 -1
  855. package/dist/modes/interactive/components/custom-message.js.map +0 -1
  856. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  857. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
  858. package/dist/modes/interactive/components/diff.d.ts.map +0 -1
  859. package/dist/modes/interactive/components/diff.js.map +0 -1
  860. package/dist/modes/interactive/components/dynamic-border.d.ts.map +0 -1
  861. package/dist/modes/interactive/components/dynamic-border.js.map +0 -1
  862. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +0 -1
  863. package/dist/modes/interactive/components/earendil-announcement.js.map +0 -1
  864. package/dist/modes/interactive/components/extension-editor.d.ts.map +0 -1
  865. package/dist/modes/interactive/components/extension-editor.js.map +0 -1
  866. package/dist/modes/interactive/components/extension-input.d.ts.map +0 -1
  867. package/dist/modes/interactive/components/extension-input.js.map +0 -1
  868. package/dist/modes/interactive/components/extension-selector.d.ts.map +0 -1
  869. package/dist/modes/interactive/components/extension-selector.js.map +0 -1
  870. package/dist/modes/interactive/components/first-time-setup.d.ts.map +0 -1
  871. package/dist/modes/interactive/components/first-time-setup.js.map +0 -1
  872. package/dist/modes/interactive/components/footer.d.ts.map +0 -1
  873. package/dist/modes/interactive/components/footer.js.map +0 -1
  874. package/dist/modes/interactive/components/index.d.ts.map +0 -1
  875. package/dist/modes/interactive/components/index.js.map +0 -1
  876. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +0 -1
  877. package/dist/modes/interactive/components/keybinding-hints.js.map +0 -1
  878. package/dist/modes/interactive/components/login-dialog.d.ts.map +0 -1
  879. package/dist/modes/interactive/components/login-dialog.js.map +0 -1
  880. package/dist/modes/interactive/components/model-selector.d.ts.map +0 -1
  881. package/dist/modes/interactive/components/model-selector.js.map +0 -1
  882. package/dist/modes/interactive/components/oauth-selector.d.ts.map +0 -1
  883. package/dist/modes/interactive/components/oauth-selector.js.map +0 -1
  884. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +0 -1
  885. package/dist/modes/interactive/components/scoped-models-selector.js.map +0 -1
  886. package/dist/modes/interactive/components/session-selector-search.d.ts.map +0 -1
  887. package/dist/modes/interactive/components/session-selector-search.js.map +0 -1
  888. package/dist/modes/interactive/components/session-selector.d.ts.map +0 -1
  889. package/dist/modes/interactive/components/session-selector.js.map +0 -1
  890. package/dist/modes/interactive/components/settings-selector.d.ts.map +0 -1
  891. package/dist/modes/interactive/components/settings-selector.js.map +0 -1
  892. package/dist/modes/interactive/components/show-images-selector.d.ts.map +0 -1
  893. package/dist/modes/interactive/components/show-images-selector.js.map +0 -1
  894. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +0 -1
  895. package/dist/modes/interactive/components/skill-invocation-message.js.map +0 -1
  896. package/dist/modes/interactive/components/status-indicator.d.ts.map +0 -1
  897. package/dist/modes/interactive/components/status-indicator.js.map +0 -1
  898. package/dist/modes/interactive/components/theme-selector.d.ts.map +0 -1
  899. package/dist/modes/interactive/components/theme-selector.js.map +0 -1
  900. package/dist/modes/interactive/components/thinking-selector.d.ts.map +0 -1
  901. package/dist/modes/interactive/components/thinking-selector.js.map +0 -1
  902. package/dist/modes/interactive/components/tool-execution.d.ts.map +0 -1
  903. package/dist/modes/interactive/components/tool-execution.js.map +0 -1
  904. package/dist/modes/interactive/components/tree-selector.d.ts.map +0 -1
  905. package/dist/modes/interactive/components/tree-selector.js.map +0 -1
  906. package/dist/modes/interactive/components/trust-selector.d.ts.map +0 -1
  907. package/dist/modes/interactive/components/trust-selector.js.map +0 -1
  908. package/dist/modes/interactive/components/user-message-selector.d.ts.map +0 -1
  909. package/dist/modes/interactive/components/user-message-selector.js.map +0 -1
  910. package/dist/modes/interactive/components/user-message.d.ts.map +0 -1
  911. package/dist/modes/interactive/components/user-message.js.map +0 -1
  912. package/dist/modes/interactive/components/visual-truncate.d.ts.map +0 -1
  913. package/dist/modes/interactive/components/visual-truncate.js.map +0 -1
  914. package/dist/modes/interactive/external-editor.d.ts.map +0 -1
  915. package/dist/modes/interactive/external-editor.js.map +0 -1
  916. package/dist/modes/interactive/interactive-mode.d.ts.map +0 -1
  917. package/dist/modes/interactive/interactive-mode.js.map +0 -1
  918. package/dist/modes/interactive/model-search.d.ts.map +0 -1
  919. package/dist/modes/interactive/model-search.js.map +0 -1
  920. package/dist/modes/interactive/theme/theme-controller.d.ts.map +0 -1
  921. package/dist/modes/interactive/theme/theme-controller.js.map +0 -1
  922. package/dist/modes/interactive/theme/theme.d.ts.map +0 -1
  923. package/dist/modes/interactive/theme/theme.js.map +0 -1
  924. package/dist/modes/print-mode.d.ts.map +0 -1
  925. package/dist/modes/print-mode.js.map +0 -1
  926. package/dist/modes/rpc/jsonl.d.ts.map +0 -1
  927. package/dist/modes/rpc/jsonl.js.map +0 -1
  928. package/dist/modes/rpc/rpc-client.d.ts.map +0 -1
  929. package/dist/modes/rpc/rpc-client.js.map +0 -1
  930. package/dist/modes/rpc/rpc-mode.d.ts.map +0 -1
  931. package/dist/modes/rpc/rpc-mode.js.map +0 -1
  932. package/dist/modes/rpc/rpc-types.d.ts.map +0 -1
  933. package/dist/modes/rpc/rpc-types.js.map +0 -1
  934. package/dist/package-manager-cli.d.ts.map +0 -1
  935. package/dist/package-manager-cli.js.map +0 -1
  936. package/dist/rpc-entry.d.ts.map +0 -1
  937. package/dist/rpc-entry.js.map +0 -1
  938. package/dist/utils/ansi.d.ts.map +0 -1
  939. package/dist/utils/ansi.js.map +0 -1
  940. package/dist/utils/changelog.d.ts.map +0 -1
  941. package/dist/utils/changelog.js.map +0 -1
  942. package/dist/utils/child-process.d.ts.map +0 -1
  943. package/dist/utils/child-process.js.map +0 -1
  944. package/dist/utils/clipboard-image.d.ts.map +0 -1
  945. package/dist/utils/clipboard-image.js.map +0 -1
  946. package/dist/utils/clipboard-native.d.ts.map +0 -1
  947. package/dist/utils/clipboard-native.js.map +0 -1
  948. package/dist/utils/clipboard.d.ts.map +0 -1
  949. package/dist/utils/clipboard.js.map +0 -1
  950. package/dist/utils/deprecation.d.ts.map +0 -1
  951. package/dist/utils/deprecation.js.map +0 -1
  952. package/dist/utils/exif-orientation.d.ts.map +0 -1
  953. package/dist/utils/exif-orientation.js.map +0 -1
  954. package/dist/utils/frontmatter.d.ts.map +0 -1
  955. package/dist/utils/frontmatter.js.map +0 -1
  956. package/dist/utils/fs-watch.d.ts.map +0 -1
  957. package/dist/utils/fs-watch.js.map +0 -1
  958. package/dist/utils/git.d.ts.map +0 -1
  959. package/dist/utils/git.js.map +0 -1
  960. package/dist/utils/html.d.ts.map +0 -1
  961. package/dist/utils/html.js.map +0 -1
  962. package/dist/utils/image-convert.d.ts.map +0 -1
  963. package/dist/utils/image-convert.js.map +0 -1
  964. package/dist/utils/image-process.d.ts.map +0 -1
  965. package/dist/utils/image-process.js.map +0 -1
  966. package/dist/utils/image-resize-core.d.ts.map +0 -1
  967. package/dist/utils/image-resize-core.js.map +0 -1
  968. package/dist/utils/image-resize-worker.d.ts.map +0 -1
  969. package/dist/utils/image-resize-worker.js.map +0 -1
  970. package/dist/utils/image-resize.d.ts.map +0 -1
  971. package/dist/utils/image-resize.js.map +0 -1
  972. package/dist/utils/json.d.ts.map +0 -1
  973. package/dist/utils/json.js.map +0 -1
  974. package/dist/utils/mime.d.ts.map +0 -1
  975. package/dist/utils/mime.js.map +0 -1
  976. package/dist/utils/open-browser.d.ts.map +0 -1
  977. package/dist/utils/open-browser.js.map +0 -1
  978. package/dist/utils/paths.d.ts.map +0 -1
  979. package/dist/utils/paths.js.map +0 -1
  980. package/dist/utils/photon.d.ts.map +0 -1
  981. package/dist/utils/photon.js.map +0 -1
  982. package/dist/utils/pi-user-agent.d.ts.map +0 -1
  983. package/dist/utils/pi-user-agent.js.map +0 -1
  984. package/dist/utils/shell.d.ts.map +0 -1
  985. package/dist/utils/shell.js.map +0 -1
  986. package/dist/utils/sleep.d.ts.map +0 -1
  987. package/dist/utils/sleep.js.map +0 -1
  988. package/dist/utils/syntax-highlight.d.ts.map +0 -1
  989. package/dist/utils/syntax-highlight.js.map +0 -1
  990. package/dist/utils/thinking-tags.d.ts.map +0 -1
  991. package/dist/utils/thinking-tags.js.map +0 -1
  992. package/dist/utils/tools-manager.d.ts.map +0 -1
  993. package/dist/utils/tools-manager.js.map +0 -1
  994. package/dist/utils/version-check.d.ts.map +0 -1
  995. package/dist/utils/version-check.js.map +0 -1
  996. package/dist/utils/windows-self-update.d.ts.map +0 -1
  997. package/dist/utils/windows-self-update.js.map +0 -1
@@ -1,525 +1,488 @@
1
1
  import assert from "node:assert/strict";
2
- import { randomUUID } from "node:crypto";
3
2
  import * as fs from "node:fs";
4
3
  import * as os from "node:os";
5
4
  import * as path from "node:path";
6
- import { after, before, describe, it } from "node:test";
5
+ import { afterEach, describe, it } from "node:test";
6
+ import type { ExtensionContext } from "@selesai/code";
7
7
  import {
8
- SCHEDULED_RUNS_DIR,
9
- ScheduledRunManager,
8
+ SCHEDULED_RUN_ACTIONS,
10
9
  createScheduledRunManager,
11
10
  isScheduledRunAction,
11
+ listScheduledRunSummaries,
12
+ parseScheduleInterval,
12
13
  parseScheduledRunTime,
13
14
  scheduledRunStorePath,
14
15
  scheduledRunsEnabled,
16
+ type ScheduledRunManager,
15
17
  } from "../../src/runs/background/scheduled-runs.ts";
16
18
  import type { ExtensionConfig } from "../../src/shared/types.ts";
17
- import { encodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../../src/api/capability-ceiling.ts";
18
- import type { ExtensionContext } from "@selesai/code";
19
-
20
- type TimerHandle = number;
21
19
 
20
+ type Timer = { callback: () => void; delay: number };
22
21
  class FakeTimers {
23
- readonly scheduled = new Map<TimerHandle, { cb: () => void; delay: number }>();
24
- private nextHandle = 1;
25
-
26
- setTimeout = (cb: () => void, delay: number): TimerHandle => {
27
- const handle = this.nextHandle++;
28
- this.scheduled.set(handle, { cb, delay });
29
- return handle;
22
+ readonly values = new Map<number, Timer>();
23
+ private id = 0;
24
+ setTimeout = (callback: () => void, delay: number) => {
25
+ const id = ++this.id;
26
+ this.values.set(id, { callback, delay });
27
+ return id as unknown as ReturnType<typeof setTimeout>;
30
28
  };
31
-
32
- clearTimeout = (handle: TimerHandle): void => {
33
- this.scheduled.delete(handle);
34
- };
35
-
29
+ clearTimeout = (id: ReturnType<typeof setTimeout>) => void this.values.delete(id as unknown as number);
36
30
  fireAll(): void {
37
- // Snapshot first: fire() re-arms by clearing the fired handle and adding a new one.
38
- const entries = [...this.scheduled.values()];
39
- for (const entry of entries) entry.cb();
40
- }
41
-
42
- pendingCount(): number {
43
- return this.scheduled.size;
31
+ const pending = [...this.values.entries()];
32
+ for (const [id, timer] of pending) {
33
+ this.values.delete(id);
34
+ timer.callback();
35
+ }
44
36
  }
45
37
  }
46
38
 
47
- type LaunchRecord = {
39
+ type Launch = {
48
40
  params: Record<string, unknown>;
49
41
  ctx: ExtensionContext;
50
- signal: AbortSignal;
51
- resolve: (result: { content: Array<{ type: "text"; text: string }>; details: Record<string, unknown>; isError?: boolean }) => void;
52
- promise: Promise<{ content: Array<{ type: "text"; text: string }>; details: Record<string, unknown>; isError?: boolean }>;
42
+ resolve(result: { content: Array<{ type: "text"; text: string }>; details: Record<string, unknown>; isError?: boolean }): void;
53
43
  };
54
44
 
55
- type TestHarness = {
45
+ type Harness = {
56
46
  manager: ScheduledRunManager;
57
- timers: FakeTimers;
58
- launches: LaunchRecord[];
59
- clock: { now: number };
60
- storeRoot: string;
61
47
  ctx: ExtensionContext;
48
+ clock: { now: number };
49
+ timers: FakeTimers;
50
+ launches: Launch[];
51
+ root: string;
62
52
  };
63
53
 
64
- function makeCtx(cwd = "/project", sessionFile = "/sessions/abc.jsonl"): ExtensionContext {
54
+ const roots: string[] = [];
55
+ afterEach(() => {
56
+ for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
57
+ });
58
+
59
+ function context(cwd: string, sessionId = "session-a"): ExtensionContext {
65
60
  return {
66
61
  cwd,
67
62
  sessionManager: {
68
- getSessionFile: () => sessionFile,
69
- getSessionId: () => path.basename(sessionFile, ".jsonl"),
63
+ getSessionId: () => sessionId,
64
+ getSessionFile: () => path.join(cwd, `${sessionId}.jsonl`),
70
65
  },
71
66
  } as unknown as ExtensionContext;
72
67
  }
73
68
 
74
- function makeConfig(overrides: Partial<ExtensionConfig["scheduledRuns"]> = {}): ExtensionConfig {
75
- return { scheduledRuns: { enabled: true, ...overrides } };
76
- }
77
-
78
- async function flushMicrotasks(times = 10): Promise<void> {
79
- for (let i = 0; i < times; i++) await Promise.resolve();
80
- }
81
-
82
- function createHarness(options: { config?: ExtensionConfig; storeRoot?: string; now?: number; resolveCapabilityCeiling?: (sessionId: string) => { version: 1; allowedTools?: string[]; denyExtensions: boolean; sources: string[] } | undefined } = {}): TestHarness {
69
+ function harness(options: { cwd?: string; sessionId?: string; now?: number; config?: ExtensionConfig } = {}): Harness {
70
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-schedule-test-"));
71
+ roots.push(root);
72
+ const project = options.cwd ?? path.join(root, "project");
73
+ fs.mkdirSync(project, { recursive: true });
74
+ const ctx = context(project, options.sessionId);
75
+ const clock = { now: options.now ?? Date.parse("2030-01-01T00:00:00Z") };
83
76
  const timers = new FakeTimers();
84
- const launches: LaunchRecord[] = [];
85
- const clock = { now: options.now ?? 1_000_000 };
86
- const storeRoot = options.storeRoot ?? fs.mkdtempSync(path.join(os.tmpdir(), "sched-test-"));
77
+ const launches: Launch[] = [];
78
+ let id = 0;
87
79
  const manager = createScheduledRunManager({
88
- config: options.config ?? makeConfig(),
89
- storeRoot,
80
+ config: options.config ?? { scheduledRuns: { enabled: true } },
81
+ storeRoot: path.join(root, "stores"),
90
82
  now: () => clock.now,
91
- randomId: () => "id-" + randomUUID().slice(0, 4),
83
+ randomId: () => `id-${++id}`,
92
84
  timers,
93
- resolveCapabilityCeiling: options.resolveCapabilityCeiling,
94
- launch: (params, ctx, signal) => {
95
- let resolve!: LaunchRecord["resolve"];
96
- const promise = new Promise<{ content: Array<{ type: "text"; text: string }>; details: Record<string, unknown>; isError?: boolean }>((res) => {
97
- resolve = res;
98
- });
99
- launches.push({ params: params as unknown as Record<string, unknown>, ctx, signal, resolve, promise });
100
- return promise as ReturnType<TestHarness["manager"]["handleToolCall"]>;
101
- },
85
+ launch: (params, launchCtx) => new Promise((resolve) => launches.push({ params: params as Record<string, unknown>, ctx: launchCtx, resolve: resolve as Launch["resolve"] })) as never,
102
86
  });
103
- const ctx = makeCtx();
104
- return { manager, timers, launches, clock, storeRoot, ctx };
87
+ manager.bindSession(ctx);
88
+ return { manager, ctx, clock, timers, launches, root };
105
89
  }
106
90
 
107
- function extractId(result: { content: Array<{ type: "text"; text: string }> }): string {
108
- const text = result.content[0]!.text;
109
- const match = text.match(/Scheduled subagent run (id-[a-z0-9]+)/);
110
- assert.ok(match, `expected scheduled run id in: ${text}`);
111
- return match[1]!;
91
+ function text(result: Awaited<ReturnType<ScheduledRunManager["handleToolCall"]>>): string {
92
+ return result.content[0]?.type === "text" ? result.content[0].text : "";
112
93
  }
113
94
 
114
- function isError(result: { isError?: boolean }): boolean {
115
- return result.isError === true;
95
+ async function flush(): Promise<void> {
96
+ for (let i = 0; i < 8; i++) await Promise.resolve();
116
97
  }
117
98
 
118
- describe("scheduled-runs helpers", () => {
119
- it("isScheduledRunAction narrows the four actions", () => {
120
- assert.equal(isScheduledRunAction("schedule"), true);
121
- assert.equal(isScheduledRunAction("schedule-cancel"), true);
122
- assert.equal(isScheduledRunAction("status"), false);
123
- assert.equal(isScheduledRunAction(undefined), false);
124
- });
125
-
126
- it("scheduledRunsEnabled is true only when explicitly enabled", () => {
127
- assert.equal(scheduledRunsEnabled({}), false);
128
- assert.equal(scheduledRunsEnabled({ scheduledRuns: {} }), false);
129
- assert.equal(scheduledRunsEnabled({ scheduledRuns: { enabled: false } }), false);
130
- assert.equal(scheduledRunsEnabled({ scheduledRuns: { enabled: true } }), true);
99
+ describe("schedule helpers", () => {
100
+ it("recognizes only the dot-action schedule API", () => {
101
+ assert.deepEqual(SCHEDULED_RUN_ACTIONS, ["schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
102
+ assert.equal(isScheduledRunAction("schedule.create"), true);
103
+ assert.equal(isScheduledRunAction("schedule"), false);
131
104
  });
132
105
 
133
- it("scheduledRunStorePath is stable per cwd+session and distinct otherwise", () => {
134
- const root = path.join("tmp", "sched-root");
135
- const project = path.join("project");
136
- const other = path.join("other");
137
- const a = scheduledRunStorePath(project, "sess1", root);
138
- const aAgain = scheduledRunStorePath(project, "sess1", root);
139
- const b = scheduledRunStorePath(project, "sess2", root);
140
- const c = scheduledRunStorePath(other, "sess1", root);
141
- assert.equal(a, aAgain);
142
- assert.notEqual(a, b);
143
- assert.notEqual(a, c);
144
- assert.ok(a.startsWith(root));
145
- assert.ok(a.endsWith(".json"));
106
+ it("uses a stable project store independent of session id", () => {
107
+ const root = path.join("tmp", "schedules");
108
+ assert.equal(scheduledRunStorePath("project", "a", root), scheduledRunStorePath("project", "b", root));
109
+ assert.notEqual(scheduledRunStorePath("project", "a", root), scheduledRunStorePath("other", "a", root));
110
+ assert.equal(scheduledRunStorePath("/project"), path.join(path.resolve("/project"), ".pi-subagents", "schedules"));
146
111
  });
147
112
 
148
- it("parseScheduledRunTime handles relative delays and ISO timestamps", () => {
149
- const base = 1_000_000;
150
- assert.equal(parseScheduledRunTime("+10m", base), base + 10 * 60_000);
151
- assert.equal(parseScheduledRunTime("+1h", base), base + 3_600_000);
152
- assert.equal(parseScheduledRunTime("+2d", base), base + 2 * 86_400_000);
153
- assert.equal(parseScheduledRunTime("+30s", base), base + 30_000);
154
- assert.equal(parseScheduledRunTime("2030-01-01T00:00:00Z", base), new Date("2030-01-01T00:00:00Z").getTime());
155
- assert.equal(parseScheduledRunTime("2030-01-01T09:00:00+05:30", base), new Date("2030-01-01T09:00:00+05:30").getTime());
113
+ it("parses one-shot and fixed interval forms strictly", () => {
114
+ const now = Date.parse("2030-01-01T00:00:00Z");
115
+ assert.equal(parseScheduledRunTime("+10m", now), now + 600_000);
116
+ assert.equal(parseScheduledRunTime("2030-01-02T00:00:00Z", now), now + 86_400_000);
117
+ assert.equal(parseScheduledRunTime("2030-01-02T09:00:00+05:30", now), Date.parse("2030-01-02T09:00:00+05:30"));
118
+ assert.equal(parseScheduleInterval("30m"), 1_800_000);
119
+ assert.equal(parseScheduleInterval("2w"), 1_209_600_000);
120
+ assert.throws(() => parseScheduleInterval("day"), /fixed intervals/);
121
+ assert.throws(() => parseScheduledRunTime("2030-01-01T00:00:00", now), /timezone/);
156
122
  });
157
123
 
158
- it("parseScheduledRunTime rejects past, zero, malformed, and ambiguous schedules", () => {
159
- assert.throws(() => parseScheduledRunTime("+0m", 1_000_000), /positive/);
160
- assert.throws(() => parseScheduledRunTime("+9000000000000d", 1_000_000), /too large/);
161
- assert.throws(() => parseScheduledRunTime("2020-01-01T00:00:00Z", Date.now() + 60_000), /in the past/);
162
- assert.throws(() => parseScheduledRunTime("2030-01-01T00:00:00", 1_000_000), /must include a timezone/);
163
- assert.throws(() => parseScheduledRunTime("2030-02-30T00:00:00Z", 1_000_000), /valid future ISO/);
164
- assert.throws(() => parseScheduledRunTime("next tuesday", 1_000_000), /Invalid schedule/);
165
- assert.throws(() => parseScheduledRunTime("+5w", 1_000_000), /Invalid schedule/);
124
+ it("honors the explicit feature opt-out", () => {
125
+ assert.equal(scheduledRunsEnabled({}), true);
126
+ assert.equal(scheduledRunsEnabled({ scheduledRuns: { enabled: false } }), false);
166
127
  });
167
128
  });
168
129
 
169
- describe("ScheduledRunManager create/list/status/cancel", () => {
170
- let storeRoots: string[] = [];
171
- after(() => {
172
- for (const root of storeRoots) {
173
- try {
174
- fs.rmSync(root, { recursive: true, force: true });
175
- } catch {
176
- // best-effort cleanup of temp test dirs
177
- }
178
- }
179
- });
180
-
181
- function freshHarness(options: { config?: ExtensionConfig; now?: number } = {}): TestHarness {
182
- const harness = createHarness(options);
183
- storeRoots.push(harness.storeRoot);
184
- return harness;
185
- }
186
-
187
- it("rejects schedule actions when the feature is disabled", async () => {
188
- const harness = freshHarness({ config: makeConfig({ enabled: false }) });
189
- const result = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
190
- assert.equal(isError(result), true);
191
- assert.match(result.content[0]!.text, /disabled/);
192
- assert.equal(harness.timers.pendingCount(), 0);
193
- });
194
-
195
- it("rejects capability-ceiling-restricted schedules instead of persisting policy-less jobs", async () => {
196
- const harness = createHarness({ resolveCapabilityCeiling: () => ({ version: 1, allowedTools: ["read"], denyExtensions: true, sources: ["plan"] }) });
197
- const result = await harness.manager.handleToolCall({ action: "schedule", agent: "worker", task: "review", schedule: "+10m" }, harness.ctx);
198
- assert.equal(isError(result), true);
199
- assert.match(result.content[0]!.text, /does not yet persist ceilings/);
200
- assert.equal(harness.launches.length, 0);
201
- });
202
-
203
-
204
- it("rejects schedules restricted only by inherited process policy", async () => {
205
- const previous = process.env.SELESAI_SUBAGENT_CAPABILITY_CEILING_V1;
206
- try {
207
- process.env.SELESAI_SUBAGENT_CAPABILITY_CEILING_V1 = encodeSubagentCapabilityCeiling({ version: 1, allowedTools: ["read"], denyExtensions: true, sources: ["ancestor"] });
208
- const harness = createHarness({ resolveCapabilityCeiling: resolveCurrentSubagentCapabilityCeiling });
209
- const result = await harness.manager.handleToolCall({ action: "schedule", agent: "worker", task: "review", schedule: "+10m" }, harness.ctx);
210
- assert.equal(isError(result), true);
211
- assert.match(result.content[0]!.text, /does not yet persist ceilings/);
212
- assert.equal(harness.launches.length, 0);
213
- } finally {
214
- if (previous === undefined) delete process.env.SELESAI_SUBAGENT_CAPABILITY_CEILING_V1;
215
- else process.env.SELESAI_SUBAGENT_CAPABILITY_CEILING_V1 = previous;
216
- }
217
- });
218
-
219
- it("creates a scheduled job and arms a timer", async () => {
220
- const harness = freshHarness();
221
- const result = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "review later", schedule: "+10m", scheduleName: "nightly review" }, harness.ctx);
222
- assert.equal(isError(result), false);
223
- assert.match(result.content[0]!.text, /Scheduled subagent run id-/);
224
- assert.match(result.content[0]!.text, /nightly review/);
225
- assert.equal(harness.timers.pendingCount(), 1);
226
- const list = await harness.manager.handleToolCall({ action: "schedule-list" }, harness.ctx);
227
- assert.match(list.content[0]!.text, /nightly review/);
130
+ describe("project schedule management", () => {
131
+ it("creates a project one-shot schedule and restores it in another session", async () => {
132
+ const first = harness();
133
+ const result = await first.manager.handleToolCall({ action: "schedule.create", id: "night-review", name: "Night review", at: "+10m", agent: "commentator", task: "Review the diff" }, first.ctx);
134
+ assert.equal(result.isError, undefined);
135
+ assert.match(text(result), /Created schedule night-review/);
136
+ assert.equal(first.timers.values.size, 1);
137
+ const records = listScheduledRunSummaries(first.ctx.cwd, path.join(first.root, "stores"));
138
+ assert.equal(records[0]?.name, "Night review");
139
+ assert.equal(records[0]?.cwd, first.ctx.cwd);
140
+
141
+ first.manager.stop();
142
+ const secondTimers = new FakeTimers();
143
+ const second = createScheduledRunManager({
144
+ config: { scheduledRuns: { enabled: true } },
145
+ storeRoot: path.join(first.root, "stores"),
146
+ now: () => first.clock.now,
147
+ timers: secondTimers,
148
+ launch: async () => ({ content: [{ type: "text", text: "unused" }], details: { mode: "management", results: [] } }),
149
+ });
150
+ second.bindSession(context(first.ctx.cwd, "session-b"));
151
+ assert.equal(secondTimers.values.size, 1, "a different session restores the project schedule");
152
+ const shown = await second.handleToolCall({ action: "schedule.show", id: "night-review" }, context(first.ctx.cwd, "session-b"));
153
+ assert.match(text(shown), /Night review/);
228
154
  });
229
155
 
230
- it("requires exactly one execution mode", async () => {
231
- const harness = freshHarness();
232
- // tasks + chain is genuinely ambiguous (both are execution arrays)
233
- const chainAndTasks = await harness.manager.handleToolCall({ action: "schedule", tasks: [{ agent: "scout", task: "x" }], chain: [{ agent: "scout", task: "y" }], schedule: "+10m" }, harness.ctx);
234
- assert.match(chainAndTasks.content[0]!.text, /exactly one execution mode/);
235
- // no execution mode at all
236
- const none = await harness.manager.handleToolCall({ action: "schedule", schedule: "+10m" }, harness.ctx);
237
- assert.match(none.content[0]!.text, /exactly one execution mode/);
156
+ it("stores explicit cwd schedules in the target project", async () => {
157
+ const h = harness();
158
+ const target = path.join(h.root, "other-project");
159
+ fs.mkdirSync(target);
160
+ h.manager.bindSession(context(target, "target-session"));
161
+ h.manager.bindSession(h.ctx);
162
+ await h.manager.handleToolCall({ action: "schedule.create", id: "other", cwd: target, every: "1h", agent: "builder" }, h.ctx);
163
+ assert.equal(listScheduledRunSummaries(h.ctx.cwd, path.join(h.root, "stores")).length, 0);
164
+ assert.equal(listScheduledRunSummaries(target, path.join(h.root, "stores"))[0]?.cwd, target);
165
+ const listed = await h.manager.handleToolCall({ action: "schedule.list", cwd: target }, h.ctx);
166
+ assert.match(text(listed), /other/);
238
167
  });
239
168
 
240
- it("rejects explicit reviewed acceptance at every scheduled nesting level before persisting a job", async () => {
241
- const harness = freshHarness();
242
- const cases = [
243
- { agent: "scout", task: "review later", acceptance: "reviewed" },
244
- { agent: "scout", task: "review later", acceptance: { level: "reviewed" } },
245
- { tasks: [{ agent: "scout", task: "review later", acceptance: "reviewed" }] },
246
- { chain: [{ agent: "scout", task: "review later", acceptance: { level: "reviewed" } }] },
247
- { chain: [{ parallel: [{ agent: "scout", task: "review later", acceptance: "reviewed" }] }] },
248
- ];
249
- for (const params of cases) {
250
- const result = await harness.manager.handleToolCall({
251
- action: "schedule",
252
- schedule: "+10m",
253
- ...params,
254
- }, harness.ctx);
255
- assert.equal(isError(result), true);
256
- assert.match(result.content[0]!.text, /achieved status.*omit acceptance.*acceptance\.review\.required/i);
169
+ it("supports workflowScript targets and rejects unsafe or deferred shapes", async () => {
170
+ const h = harness();
171
+ const workflow = await h.manager.handleToolCall({ action: "schedule.create", id: "workflow", every: "6h", workflowScript: "return await runs.run('review', {agent:'commentator'})" }, h.ctx);
172
+ assert.equal(workflow.isError, undefined);
173
+ assert.match(text(workflow), /workflowScript/);
174
+ for (const params of [
175
+ { action: "schedule.create", id: "../escape", every: "1h", agent: "builder" },
176
+ { action: "schedule.create", id: "both", at: "+1h", every: "1h", agent: "builder" },
177
+ { action: "schedule.create", id: "calendar", every: "day", at: "09:00", timezone: "UTC", agent: "builder" },
178
+ { action: "schedule.create", id: "two-targets", every: "1h", agent: "builder", workflowScript: "return 1" },
179
+ { action: "schedule.create", id: "fork", every: "1h", agent: "builder", context: "fork" },
180
+ { action: "schedule.create", id: "mission-id", every: "1h", agent: "builder", missionId: "mission-1" },
181
+ { action: "schedule.create", id: "mission-off", every: "1h", agent: "builder", mission: false },
182
+ ] as const) {
183
+ const result = await h.manager.handleToolCall(params, h.ctx);
184
+ assert.equal(result.isError, true, JSON.stringify(params));
257
185
  }
258
- assert.equal(harness.timers.pendingCount(), 0);
259
- });
260
-
261
- it("requires a schedule and rejects fork/async-false/clarify-true", async () => {
262
- const harness = freshHarness();
263
- const noSchedule = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x" }, harness.ctx);
264
- assert.match(noSchedule.content[0]!.text, /requires schedule/);
265
- const fork = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m", context: "fork" }, harness.ctx);
266
- assert.match(fork.content[0]!.text, /fresh context/);
267
- const sync = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m", async: false }, harness.ctx);
268
- assert.match(sync.content[0]!.text, /always async/);
269
- const clarify = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m", clarify: true }, harness.ctx);
270
- assert.match(clarify.content[0]!.text, /clarify/);
271
- });
272
-
273
- it("rejects a past schedule time", async () => {
274
- const harness = freshHarness({ now: Date.now() + 60_000 });
275
- const result = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "2020-01-01T00:00:00Z" }, harness.ctx);
276
- assert.match(result.content[0]!.text, /in the past/);
277
- });
278
-
279
- it("enforces maxPending", async () => {
280
- const harness = freshHarness({ config: makeConfig({ maxPending: 1 }) });
281
- const first = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
282
- assert.equal(isError(first), false);
283
- const second = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "y", schedule: "+20m" }, harness.ctx);
284
- assert.match(second.content[0]!.text, /limit reached/);
285
186
  });
286
187
 
287
- it("status resolves by exact id and prefix, and rejects ambiguous/missing", async () => {
288
- const harness = freshHarness();
289
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
290
- const id = extractId(created);
291
- const status = await harness.manager.handleToolCall({ action: "schedule-status", id }, harness.ctx);
292
- assert.match(status.content[0]!.text, /State: scheduled/);
293
- assert.match(status.content[0]!.text, /Cancel: subagent/);
294
- const byPrefix = await harness.manager.handleToolCall({ action: "schedule-status", id: id.slice(0, 4) }, harness.ctx);
295
- assert.match(byPrefix.content[0]!.text, /State: scheduled/);
296
- const missing = await harness.manager.handleToolCall({ action: "schedule-status", id: "nope" }, harness.ctx);
297
- assert.match(missing.content[0]!.text, /not found/);
298
- const noId = await harness.manager.handleToolCall({ action: "schedule-status" }, harness.ctx);
299
- assert.match(noId.content[0]!.text, /requires id/);
188
+ it("pauses, resumes, lists, and deletes an inactive schedule", async () => {
189
+ const h = harness();
190
+ await h.manager.handleToolCall({ action: "schedule.create", id: "maintenance", every: "1h", agent: "builder" }, h.ctx);
191
+ assert.match(text(await h.manager.handleToolCall({ action: "schedule.list" }, h.ctx)), /maintenance/);
192
+ assert.match(text(await h.manager.handleToolCall({ action: "schedule.pause", id: "maintenance" }, h.ctx)), /Paused/);
193
+ assert.equal(h.timers.values.size, 0);
194
+ assert.match(text(await h.manager.handleToolCall({ action: "schedule.resume", id: "maintenance" }, h.ctx)), /Resumed/);
195
+ assert.equal(h.timers.values.size, 1);
196
+ assert.match(text(await h.manager.handleToolCall({ action: "schedule.delete", id: "maintenance" }, h.ctx)), /Deleted/);
197
+ assert.match(text(await h.manager.handleToolCall({ action: "schedule.list" }, h.ctx)), /No project schedules/);
300
198
  });
301
199
 
302
- it("cancel clears the timer and marks the job canceled", async () => {
303
- const harness = freshHarness();
304
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
305
- const id = extractId(created);
306
- assert.equal(harness.timers.pendingCount(), 1);
307
- const canceled = await harness.manager.handleToolCall({ action: "schedule-cancel", id }, harness.ctx);
308
- assert.match(canceled.content[0]!.text, /Canceled scheduled subagent run/);
309
- assert.equal(harness.timers.pendingCount(), 0);
310
- const status = await harness.manager.handleToolCall({ action: "schedule-status", id }, harness.ctx);
311
- assert.match(status.content[0]!.text, /State: canceled/);
200
+ it("reports corrupt project schedule records instead of dropping them", async () => {
201
+ const h = harness();
202
+ const root = scheduledRunStorePath(h.ctx.cwd, undefined, path.join(h.root, "stores"));
203
+ const dir = path.join(root, "broken");
204
+ fs.mkdirSync(dir, { recursive: true });
205
+ fs.writeFileSync(path.join(dir, "schedule.json"), "{ bad", "utf-8");
206
+ const result = await h.manager.handleToolCall({ action: "schedule.list" }, h.ctx);
207
+ assert.equal(result.isError, true);
208
+ assert.match(text(result), /Failed to read schedule record/);
312
209
  });
313
210
 
314
- it("cancel refuses a terminal job", async () => {
315
- const harness = freshHarness();
316
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
317
- const id = extractId(created);
318
- await harness.manager.handleToolCall({ action: "schedule-cancel", id }, harness.ctx);
319
- const again = await harness.manager.handleToolCall({ action: "schedule-cancel", id }, harness.ctx);
320
- assert.match(again.content[0]!.text, /already canceled/);
211
+ it("rejects schedule directories that escape through a symlink", async () => {
212
+ const h = harness();
213
+ const root = scheduledRunStorePath(h.ctx.cwd, undefined, path.join(h.root, "stores"));
214
+ const outside = path.join(h.root, "outside");
215
+ fs.mkdirSync(root, { recursive: true });
216
+ fs.mkdirSync(outside);
217
+ fs.symlinkSync(outside, path.join(root, "escaped"), process.platform === "win32" ? "junction" : "dir");
218
+ const result = await h.manager.handleToolCall({ action: "schedule.create", id: "escaped", every: "1h", agent: "builder" }, h.ctx);
219
+ assert.equal(result.isError, true);
220
+ assert.match(text(result), /must be a real directory/);
221
+ assert.equal(fs.existsSync(path.join(outside, "schedule.json")), false);
321
222
  });
322
223
 
323
- it("reports malformed persisted job records instead of dropping them", async () => {
324
- const harness = freshHarness();
325
- const sessionId = harness.ctx.sessionManager.getSessionFile()!;
326
- const storePath = scheduledRunStorePath(harness.ctx.cwd, sessionId, harness.storeRoot);
327
- fs.mkdirSync(path.dirname(storePath), { recursive: true });
328
- fs.writeFileSync(storePath, JSON.stringify({ version: 1, cwd: harness.ctx.cwd, sessionId, jobs: [{ id: "bad" }] }), "utf-8");
329
- const result = await harness.manager.handleToolCall({ action: "schedule-list" }, harness.ctx);
330
- assert.equal(isError(result), true);
331
- assert.match(result.content[0]!.text, /job 0 has invalid string fields/);
332
- });
224
+ it("rejects a default project schedule root that escapes through .pi-subagents", async () => {
225
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-schedule-root-link-"));
226
+ roots.push(root);
227
+ const project = path.join(root, "project");
228
+ const outside = path.join(root, "outside");
229
+ fs.mkdirSync(project);
230
+ fs.mkdirSync(outside);
231
+ const ctx = context(project);
232
+ const manager = createScheduledRunManager({
233
+ config: { scheduledRuns: { enabled: true } },
234
+ launch: async () => ({ content: [{ type: "text", text: "unused" }], details: { mode: "management", results: [] } }),
235
+ });
236
+ manager.bindSession(ctx);
237
+ fs.symlinkSync(outside, path.join(project, ".pi-subagents"), process.platform === "win32" ? "junction" : "dir");
333
238
 
334
- it("reports JSON parse errors from a corrupted persisted store", async () => {
335
- const harness = freshHarness();
336
- const sessionId = harness.ctx.sessionManager.getSessionFile()!;
337
- const storePath = scheduledRunStorePath(harness.ctx.cwd, sessionId, harness.storeRoot);
338
- fs.mkdirSync(path.dirname(storePath), { recursive: true });
339
- fs.writeFileSync(storePath, "{ not-json", "utf-8");
340
- const result = await harness.manager.handleToolCall({ action: "schedule-list" }, harness.ctx);
341
- assert.equal(isError(result), true);
342
- assert.match(result.content[0]!.text, /Failed to parse scheduled subagent store/);
343
- assert.match(result.content[0]!.text, /JSON/);
239
+ const result = await manager.handleToolCall({ action: "schedule.create", id: "escaped-root", every: "1h", agent: "builder" }, ctx);
240
+ assert.equal(result.isError, true);
241
+ assert.match(text(result), /resolves outside the real project/);
242
+ assert.equal(fs.existsSync(path.join(outside, "schedules")), false);
344
243
  });
345
244
  });
346
245
 
347
- describe("ScheduledRunManager firing", () => {
348
- let storeRoots: string[] = [];
349
- after(() => {
350
- for (const root of storeRoots) {
351
- try {
352
- fs.rmSync(root, { recursive: true, force: true });
353
- } catch {
354
- // best-effort cleanup of temp test dirs
355
- }
356
- }
246
+ describe("recurring schedule execution", () => {
247
+ it("launches a fixed interval from its planned time and records durable history/events", async () => {
248
+ const h = harness();
249
+ await h.manager.handleToolCall({ action: "schedule.create", id: "hourly", every: "1h", agent: "builder", task: "Maintain backlog" }, h.ctx);
250
+ h.clock.now += 3_600_000;
251
+ h.timers.fireAll();
252
+ assert.equal(h.launches.length, 1);
253
+ assert.deepEqual(h.launches[0]?.params, { agent: "builder", task: "Maintain backlog", async: true, clarify: false, context: "fresh", cwd: h.ctx.cwd, mission: false });
254
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "Async builder" }], details: { mode: "single", results: [], asyncId: "async-1", asyncDir: "/tmp/async-1" } });
255
+ await flush();
256
+
257
+ let history = await h.manager.handleToolCall({ action: "schedule.history", id: "hourly" }, h.ctx);
258
+ assert.match(text(history), /running.*async async-1/);
259
+ const scheduleRoot = scheduledRunStorePath(h.ctx.cwd, undefined, path.join(h.root, "stores"));
260
+ const dir = path.join(scheduleRoot, "hourly");
261
+ assert.equal(fs.existsSync(path.join(dir, "history.json")), true);
262
+ assert.equal(fs.existsSync(path.join(dir, "events.jsonl")), true);
263
+ assert.equal(fs.readdirSync(path.join(dir, "runs")).length, 1);
264
+
265
+ h.manager.handleAsyncCompletion({ runId: "async-1", success: true, summary: "Done" });
266
+ history = await h.manager.handleToolCall({ action: "schedule.history", id: "hourly" }, h.ctx);
267
+ assert.match(text(history), /completed.*async async-1/);
268
+ assert.equal(fs.existsSync(path.join(dir, "active.lock")), false);
269
+ const shown = await h.manager.handleToolCall({ action: "schedule.show", id: "hourly" }, h.ctx);
270
+ assert.match(text(shown), /2030-01-01T02:00:00.000Z/, "next occurrence advances from the planned time without completion drift");
357
271
  });
358
272
 
359
- function freshHarness(options: { config?: ExtensionConfig; now?: number } = {}): TestHarness {
360
- const harness = createHarness(options);
361
- storeRoots.push(harness.storeRoot);
362
- return harness;
363
- }
364
-
365
- it("launches the sanitized async run when the timer fires", async () => {
366
- const harness = freshHarness();
367
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "review", schedule: "+10m", scheduleName: "rev" }, harness.ctx);
368
- const id = extractId(created);
369
- harness.clock.now += 10 * 60_000; // advance to runAt so fire() launches instead of re-arming
370
- harness.timers.fireAll();
371
- assert.equal(harness.launches.length, 1, "launch should be invoked once when the timer fires");
372
- const launch = harness.launches[0]!;
373
- assert.equal(launch.params.async, true);
374
- assert.equal(launch.params.clarify, false);
375
- assert.equal(launch.params.context, "fresh");
376
- assert.equal(launch.params.agent, "scout");
377
- assert.equal(launch.params.task, "review");
378
- assert.equal((launch.params as { action?: string }).action, undefined, "action must be stripped before launch");
379
- assert.equal((launch.params as { schedule?: string }).schedule, undefined, "schedule must be stripped before launch");
380
-
381
- launch.resolve({
382
- content: [{ type: "text", text: "Async: scout [run-xyz]" }],
383
- details: { mode: "single", runId: "run-xyz", asyncId: "run-xyz", asyncDir: "/tmp/async-xyz", results: [] },
384
- });
385
- await flushMicrotasks();
273
+ it("run-due launches the latest missed occurrence while catchUp none records a miss", async () => {
274
+ const latest = harness();
275
+ await latest.manager.handleToolCall({ action: "schedule.create", id: "latest", every: "1h", catchUp: "latest", agent: "builder" }, latest.ctx);
276
+ latest.manager.stop();
277
+ latest.clock.now += 3 * 3_600_000;
278
+ latest.manager.bindSession(latest.ctx);
279
+ const duePromise = latest.manager.handleToolCall({ action: "schedule.run-due" }, latest.ctx);
280
+ await flush();
281
+ assert.equal(latest.launches.length, 1);
282
+ latest.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "late-1" } });
283
+ assert.match(text(await duePromise), /Processed 1 due schedule/);
284
+ assert.match(text(await latest.manager.handleToolCall({ action: "schedule.history", id: "latest" }, latest.ctx)), /2030-01-01T03:00:00.000Z/, "latest catch-up selects the latest missed slot");
285
+
286
+ const none = harness();
287
+ await none.manager.handleToolCall({ action: "schedule.create", id: "none", every: "1h", catchUp: "none", agent: "builder" }, none.ctx);
288
+ none.manager.stop();
289
+ none.clock.now += 3 * 3_600_000;
290
+ none.manager.bindSession(none.ctx);
291
+ assert.match(text(await none.manager.handleToolCall({ action: "schedule.history", id: "none" }, none.ctx)), /missed/);
292
+ assert.equal(none.launches.length, 0);
293
+ });
386
294
 
387
- const status = await harness.manager.handleToolCall({ action: "schedule-status", id }, harness.ctx);
388
- assert.match(status.content[0]!.text, /State: fired/);
389
- assert.match(status.content[0]!.text, /Launched async run: run-xyz/);
390
- assert.match(status.content[0]!.text, /Async dir: \/tmp\/async-xyz/);
295
+ it("keeps project timers, contexts, and completion ownership across session_start bindings", async () => {
296
+ const h = harness();
297
+ await h.manager.handleToolCall({ action: "schedule.create", id: "project-a", every: "1h", agent: "builder" }, h.ctx);
298
+
299
+ const projectB = path.join(h.root, "project-b");
300
+ fs.mkdirSync(projectB);
301
+ const projectBCtx = context(projectB, "session-b");
302
+ const sourceSessionManager = h.ctx.sessionManager as { getSessionId(): string | null; getSessionFile(): string | null };
303
+ const getSourceSessionId = sourceSessionManager.getSessionId;
304
+ const getSourceSessionFile = sourceSessionManager.getSessionFile;
305
+ sourceSessionManager.getSessionId = () => "session-b";
306
+ sourceSessionManager.getSessionFile = () => path.join(projectB, "session-b.jsonl");
307
+ h.manager.bindSession(projectBCtx);
308
+ await h.manager.handleToolCall({ action: "schedule.create", id: "project-b", every: "1h", agent: "builder" }, projectBCtx);
309
+ assert.equal(h.timers.values.size, 2, "both project timers remain armed after session_start binds project B");
310
+
311
+ h.clock.now += 3_600_000;
312
+ h.timers.fireAll();
313
+ await flush();
314
+ assert.equal(h.launches.length, 2);
315
+ assert.equal(h.launches[0]!.ctx.cwd, h.ctx.cwd);
316
+ assert.equal(h.launches[0]!.ctx.sessionManager.getSessionId(), "session-a");
317
+ assert.equal(h.launches[0]!.ctx.sessionManager.getSessionFile(), path.join(h.ctx.cwd, "session-a.jsonl"));
318
+ assert.equal(h.launches[1]!.ctx.cwd, projectB);
319
+ assert.equal(h.launches[1]!.ctx.sessionManager.getSessionId(), "session-b");
320
+ assert.equal(h.launches[1]!.ctx.sessionManager.getSessionFile(), path.join(projectB, "session-b.jsonl"));
321
+ sourceSessionManager.getSessionId = getSourceSessionId;
322
+ sourceSessionManager.getSessionFile = getSourceSessionFile;
323
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "async-a" } });
324
+ h.launches[1]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "async-b" } });
325
+ await flush();
326
+
327
+ h.manager.handleAsyncCompletion({ runId: "async-a", success: true });
328
+ const history = await h.manager.handleToolCall({ action: "schedule.history", id: "project-a" }, h.ctx);
329
+ assert.match(text(history), /completed.*async async-a/);
330
+ const projectARoot = scheduledRunStorePath(h.ctx.cwd, undefined, path.join(h.root, "stores"));
331
+ assert.equal(fs.existsSync(path.join(projectARoot, "project-a", "active.lock")), false);
391
332
  });
392
333
 
393
- it("marks the job failed when launch returns an error result", async () => {
394
- const harness = freshHarness();
395
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+1m" }, harness.ctx);
396
- const id = extractId(created);
397
- harness.clock.now += 60_000; // advance to runAt
398
- harness.timers.fireAll();
399
- assert.equal(harness.launches.length, 1);
400
- harness.launches[0]!.resolve({
401
- content: [{ type: "text", text: "Async mode requires jiti" }],
402
- details: { mode: "single", results: [] },
403
- isError: true,
404
- });
405
- await flushMicrotasks();
406
- const status = await harness.manager.handleToolCall({ action: "schedule-status", id }, harness.ctx);
407
- assert.match(status.content[0]!.text, /State: failed/);
408
- assert.match(status.content[0]!.text, /Async mode requires jiti/);
334
+ it("uses only a bound target session for an explicit cross-project cwd", async () => {
335
+ const h = harness();
336
+ const target = path.join(h.root, "explicit-target");
337
+ fs.mkdirSync(target);
338
+ const unbound = await h.manager.handleToolCall({ action: "schedule.create", id: "targeted", cwd: target, every: "1h", agent: "builder" }, h.ctx);
339
+ assert.equal(unbound.isError, true);
340
+ assert.match(text(unbound), /until that project has been opened/);
341
+
342
+ const targetCtx = context(target, "target-session");
343
+ h.manager.bindSession(targetCtx);
344
+ h.manager.bindSession(h.ctx);
345
+ await h.manager.handleToolCall({ action: "schedule.create", id: "targeted", cwd: target, every: "1h", agent: "builder" }, h.ctx);
346
+ h.clock.now += 3_600_000;
347
+ h.timers.fireAll();
348
+ await flush();
349
+ assert.equal(h.launches.length, 1);
350
+ assert.equal(h.launches[0]!.ctx.cwd, target);
351
+ assert.equal(h.launches[0]!.ctx.sessionManager.getSessionId(), "target-session");
352
+ assert.equal(h.launches[0]!.ctx.sessionManager.getSessionFile(), path.join(target, "target-session.jsonl"));
353
+ assert.equal(h.launches[0]!.params.cwd, target);
354
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "target-async" } });
355
+ await flush();
409
356
  });
410
357
 
411
- it("marks the job failed when launch throws", async () => {
412
- const storeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sched-throw-"));
413
- storeRoots.push(storeRoot);
414
- const ctx = makeCtx();
358
+ it("reconciles owner completion after an unrelated store becomes malformed", async () => {
359
+ const h = harness();
360
+ const badProject = path.join(h.root, "bad-project");
361
+ fs.mkdirSync(badProject);
362
+ const badCtx = context(badProject, "bad-session");
363
+ const ownerCtx = context(h.ctx.cwd, "owner-session");
415
364
  const timers = new FakeTimers();
416
- const clock = { now: 1_000_000 };
417
- const throwingManager = createScheduledRunManager({
418
- config: makeConfig(),
419
- storeRoot,
420
- now: () => clock.now,
421
- randomId: () => "id-" + randomUUID().slice(0, 4),
365
+ const launches: Launch[] = [];
366
+ let id = 0;
367
+ const manager = createScheduledRunManager({
368
+ config: { scheduledRuns: { enabled: true } },
369
+ storeRoot: path.join(h.root, "isolated-stores"),
370
+ now: () => h.clock.now,
371
+ randomId: () => `isolated-${++id}`,
422
372
  timers,
423
- launch: () => Promise.reject(new Error("spawn ENOENT")),
373
+ launch: (params, launchCtx) => new Promise((resolve) => launches.push({ params: params as Record<string, unknown>, ctx: launchCtx, resolve: resolve as Launch["resolve"] })) as never,
424
374
  });
425
- throwingManager.bindSession(ctx);
426
- const created = await throwingManager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+1m" }, ctx);
427
- const id = extractId(created);
428
- clock.now += 60_000; // advance to runAt
429
- timers.fireAll();
430
- await flushMicrotasks();
431
- const status = await throwingManager.handleToolCall({ action: "schedule-status", id }, ctx);
432
- assert.match(status.content[0]!.text, /State: failed/);
433
- assert.match(status.content[0]!.text, /spawn ENOENT/);
375
+ manager.bindSession(badCtx);
376
+ manager.bindSession(ownerCtx);
377
+ await manager.handleToolCall({ action: "schedule.create", id: "owner", every: "1h", agent: "builder" }, ownerCtx);
378
+ const manual = manager.handleToolCall({ action: "schedule.run", id: "owner" }, ownerCtx);
379
+ await flush();
380
+ launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "owner-async" } });
381
+ await manual;
382
+
383
+ const badDir = path.join(scheduledRunStorePath(badProject, undefined, path.join(h.root, "isolated-stores")), "broken");
384
+ fs.mkdirSync(badDir, { recursive: true });
385
+ fs.writeFileSync(path.join(badDir, "schedule.json"), "{ bad", "utf-8");
386
+ const ownerRoot = scheduledRunStorePath(ownerCtx.cwd, undefined, path.join(h.root, "isolated-stores"));
387
+ const badSiblingDir = path.join(ownerRoot, "broken-sibling");
388
+ fs.mkdirSync(badSiblingDir, { recursive: true });
389
+ fs.writeFileSync(path.join(badSiblingDir, "schedule.json"), "{ bad", "utf-8");
390
+ const originalError = console.error;
391
+ console.error = () => {};
392
+ try {
393
+ manager.handleAsyncCompletion({ runId: "owner-async", success: true });
394
+ } finally {
395
+ console.error = originalError;
396
+ }
397
+ const history = await manager.handleToolCall({ action: "schedule.history", id: "owner" }, ownerCtx);
398
+ assert.match(text(history), /completed.*async owner-async/);
399
+ assert.equal(fs.existsSync(path.join(ownerRoot, "owner", "active.lock")), false);
434
400
  });
435
401
 
436
- it("re-arms instead of launching when a capped timer fires before runAt", async () => {
437
- const harness = freshHarness({ now: 1_000_000 });
438
- // +25d exceeds MAX_TIMER_DELAY_MS so the armed timer is capped and would fire "early" in fake time.
439
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+25d" }, harness.ctx);
440
- const id = extractId(created);
441
- harness.timers.fireAll();
442
- assert.equal(harness.launches.length, 0, "must not launch before runAt");
443
- assert.equal(harness.timers.pendingCount(), 1, "must re-arm a new timer");
444
- // Advance time to runAt and fire again -> now it launches.
445
- harness.clock.now = 1_000_000 + 25 * 86_400_000;
446
- harness.timers.fireAll();
447
- assert.equal(harness.launches.length, 1, "must launch once runAt is reached");
448
- harness.launches[0]!.resolve({
449
- content: [{ type: "text", text: "Async: scout [run-1]" }],
450
- details: { mode: "single", runId: "run-1", asyncId: "run-1", results: [] },
402
+ it("reconciles a terminal async status after a new session binds", async () => {
403
+ const h = harness();
404
+ await h.manager.handleToolCall({ action: "schedule.create", id: "restart", every: "1h", agent: "builder" }, h.ctx);
405
+ h.clock.now += 3_600_000;
406
+ h.timers.fireAll();
407
+ const asyncDir = path.join(h.root, "async-restart");
408
+ fs.mkdirSync(asyncDir);
409
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "async-restart", asyncDir } });
410
+ await flush();
411
+ h.manager.stop();
412
+ fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({ runId: "async-restart", mode: "single", state: "complete", startedAt: h.clock.now, endedAt: h.clock.now }), "utf-8");
413
+
414
+ const next = createScheduledRunManager({
415
+ config: { scheduledRuns: { enabled: true } },
416
+ storeRoot: path.join(h.root, "stores"),
417
+ now: () => h.clock.now,
418
+ randomId: () => "reconciled-id",
419
+ timers: new FakeTimers(),
420
+ launch: async () => ({ content: [{ type: "text", text: "unused" }], details: { mode: "management", results: [] } }),
451
421
  });
452
- await flushMicrotasks();
453
- const status = await harness.manager.handleToolCall({ action: "schedule-status", id }, harness.ctx);
454
- assert.match(status.content[0]!.text, /State: fired/);
455
- });
456
-
457
- it("cancel before fire prevents the launch", async () => {
458
- const harness = freshHarness();
459
- const created = await harness.manager.handleToolCall({ action: "schedule", agent: "scout", task: "x", schedule: "+10m" }, harness.ctx);
460
- const id = extractId(created);
461
- await harness.manager.handleToolCall({ action: "schedule-cancel", id }, harness.ctx);
462
- harness.timers.fireAll();
463
- assert.equal(harness.launches.length, 0, "a canceled job must not launch");
422
+ const nextCtx = context(h.ctx.cwd, "session-b");
423
+ next.bindSession(nextCtx);
424
+ const history = await next.handleToolCall({ action: "schedule.history", id: "restart" }, nextCtx);
425
+ assert.match(text(history), /completed.*async async-restart/);
464
426
  });
465
- });
466
427
 
467
- describe("ScheduledRunManager restart restore", () => {
468
- let storeRoots: string[] = [];
469
- after(() => {
470
- for (const root of storeRoots) {
471
- try {
472
- fs.rmSync(root, { recursive: true, force: true });
473
- } catch {
474
- // best-effort cleanup of temp test dirs
475
- }
476
- }
428
+ it("records elapsed overlap and catch-up-none slots without an immediate rerun", async () => {
429
+ const latest = harness();
430
+ await latest.manager.handleToolCall({ action: "schedule.create", id: "overlap", every: "1h", catchUp: "latest", agent: "builder" }, latest.ctx);
431
+ latest.clock.now += 3_600_000;
432
+ latest.timers.fireAll();
433
+ latest.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "long-run" } });
434
+ await flush();
435
+ latest.clock.now += 3 * 3_600_000;
436
+ latest.timers.fireAll();
437
+ assert.equal(latest.launches.length, 1, "an overlapping occurrence is skipped instead of queued");
438
+ latest.manager.handleAsyncCompletion({ runId: "long-run", success: true });
439
+ const latestHistory = await latest.manager.handleToolCall({ action: "schedule.history", id: "overlap" }, latest.ctx);
440
+ assert.match(text(latestHistory), /skipped/);
441
+ assert.match(text(latestHistory), /completed.*async long-run/);
442
+ assert.match(text(await latest.manager.handleToolCall({ action: "schedule.show", id: "overlap" }, latest.ctx)), /2030-01-01T05:00:00.000Z/);
443
+
444
+ const none = harness();
445
+ await none.manager.handleToolCall({ action: "schedule.create", id: "none-overlap", every: "1h", catchUp: "none", agent: "builder" }, none.ctx);
446
+ none.clock.now += 3_600_000;
447
+ none.timers.fireAll();
448
+ none.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "none-long-run" } });
449
+ await flush();
450
+ none.clock.now += 3 * 3_600_000;
451
+ none.timers.fireAll();
452
+ assert.equal(none.launches.length, 1);
453
+ none.manager.handleAsyncCompletion({ runId: "none-long-run", success: true });
454
+ assert.match(text(await none.manager.handleToolCall({ action: "schedule.history", id: "none-overlap" }, none.ctx)), /skipped/);
455
+ assert.match(text(await none.manager.handleToolCall({ action: "schedule.show", id: "none-overlap" }, none.ctx)), /2030-01-01T05:00:00.000Z/);
477
456
  });
478
457
 
479
- it("re-arms pending jobs and marks stale jobs missed after a restart", async () => {
480
- const storeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sched-restore-"));
481
- storeRoots.push(storeRoot);
482
- const ctx = makeCtx();
483
- const clock = { now: 1_000_000 };
484
- const timersA = new FakeTimers();
485
- const managerA = createScheduledRunManager({
486
- config: makeConfig({ maxLatenessMs: 60_000 }),
487
- storeRoot,
488
- now: () => clock.now,
489
- randomId: () => "id-" + randomUUID().slice(0, 4),
490
- timers: timersA,
491
- launch: () => Promise.resolve({ content: [{ type: "text", text: "ok" }], details: { mode: "single", results: [] } }),
492
- });
493
- managerA.bindSession(ctx);
494
- const soon = await managerA.handleToolCall({ action: "schedule", agent: "scout", task: "soon", schedule: "+10m" }, ctx);
495
- const later = await managerA.handleToolCall({ action: "schedule", agent: "scout", task: "later", schedule: "+1h" }, ctx);
496
- const soonId = extractId(soon);
497
- const laterId = extractId(later);
498
- assert.equal(timersA.pendingCount(), 2);
499
- managerA.stop();
500
-
501
- // Simulate Pi being offline past the first job's window but before the second fires.
502
- clock.now = 1_000_000 + 20 * 60_000; // +20m: soon is 10m overdue (> maxLateness 1m), later is still 40m out.
503
- const timersB = new FakeTimers();
504
- const managerB = createScheduledRunManager({
505
- config: makeConfig({ maxLatenessMs: 60_000 }),
506
- storeRoot,
507
- now: () => clock.now,
508
- randomId: () => "id-" + randomUUID().slice(0, 4),
509
- timers: timersB,
510
- launch: () => Promise.resolve({ content: [{ type: "text", text: "ok" }], details: { mode: "single", results: [] } }),
511
- });
512
- managerB.bindSession(ctx);
513
- const soonStatus = await managerB.handleToolCall({ action: "schedule-status", id: soonId }, ctx);
514
- assert.match(soonStatus.content[0]!.text, /State: missed/);
515
- const laterStatus = await managerB.handleToolCall({ action: "schedule-status", id: laterId }, ctx);
516
- assert.match(laterStatus.content[0]!.text, /State: scheduled/);
517
- assert.equal(timersB.pendingCount(), 1, "only the still-future job should be re-armed");
518
- managerB.stop();
458
+ it("manual run uses the normal async target and overlap skip prevents a second launch", async () => {
459
+ const h = harness();
460
+ await h.manager.handleToolCall({ action: "schedule.create", id: "manual", every: "1h", workflowScript: "return 1" }, h.ctx);
461
+ const firstPromise = h.manager.handleToolCall({ action: "schedule.run", id: "manual" }, h.ctx);
462
+ await flush();
463
+ assert.equal(h.launches.length, 1);
464
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "workflow", results: [], asyncId: "workflow-1" } });
465
+ assert.match(text(await firstPromise), /async workflow-1/);
466
+ const second = await h.manager.handleToolCall({ action: "schedule.run", id: "manual" }, h.ctx);
467
+ assert.match(text(second), /skipped/);
468
+ assert.equal(h.launches.length, 1);
519
469
  });
520
470
 
521
- it("store path lives under the scheduled runs dir by default", () => {
522
- const p = scheduledRunStorePath("/project", "sess1");
523
- assert.ok(p.startsWith(SCHEDULED_RUNS_DIR), `${p} should be under ${SCHEDULED_RUNS_DIR}`);
471
+ it("distinguishes failed launch from failed async completion", async () => {
472
+ const h = harness();
473
+ await h.manager.handleToolCall({ action: "schedule.create", id: "failures", every: "1h", agent: "builder" }, h.ctx);
474
+ const first = h.manager.handleToolCall({ action: "schedule.run", id: "failures" }, h.ctx);
475
+ await flush();
476
+ h.launches[0]!.resolve({ content: [{ type: "text", text: "spawn failed" }], details: { mode: "management", results: [] }, isError: true });
477
+ assert.match(text(await first), /failed_launch/);
478
+
479
+ const second = h.manager.handleToolCall({ action: "schedule.run", id: "failures" }, h.ctx);
480
+ await flush();
481
+ h.launches[1]!.resolve({ content: [{ type: "text", text: "Async" }], details: { mode: "single", results: [], asyncId: "async-fail" } });
482
+ await second;
483
+ h.manager.handleAsyncCompletion({ id: "async-fail", success: false, summary: "child failed" });
484
+ const history = await h.manager.handleToolCall({ action: "schedule.history", id: "failures" }, h.ctx);
485
+ assert.match(text(history), /failed_run.*async async-fail/);
486
+ assert.match(text(history), /failed_launch/);
524
487
  });
525
488
  });