pi-crew 0.9.53 → 0.9.55

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 (493) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -3
  3. package/scripts/clean-strip-types.mjs +60 -0
  4. package/src/adapters/claude-adapter.ts +23 -0
  5. package/src/adapters/codex-adapter.ts +21 -0
  6. package/src/adapters/cursor-adapter.ts +17 -0
  7. package/src/adapters/export-util.ts +143 -0
  8. package/src/adapters/index.ts +15 -0
  9. package/src/adapters/registry.ts +18 -0
  10. package/src/adapters/types.ts +23 -0
  11. package/src/agents/agent-config.ts +194 -0
  12. package/src/agents/agent-search.ts +98 -0
  13. package/src/agents/agent-serializer.ts +38 -0
  14. package/src/agents/discover-agents.ts +623 -0
  15. package/src/benchmark/benchmark-runner.ts +313 -0
  16. package/src/benchmark/feedback-loop.ts +73 -0
  17. package/src/config/config.ts +1270 -0
  18. package/src/config/defaults.ts +193 -0
  19. package/src/config/drift-detector.ts +290 -0
  20. package/src/config/markers.ts +330 -0
  21. package/src/config/resilient-parser.ts +117 -0
  22. package/src/config/role-tools.ts +118 -0
  23. package/src/config/suggestions.ts +75 -0
  24. package/src/config/types.ts +280 -0
  25. package/src/errors.ts +194 -0
  26. package/src/extension/action-suggestions.ts +117 -0
  27. package/src/extension/async-notifier.ts +179 -0
  28. package/src/extension/autonomous-policy.ts +209 -0
  29. package/src/extension/command-completions.ts +120 -0
  30. package/src/extension/context-status-injection.ts +185 -0
  31. package/src/extension/crew-autocomplete.ts +131 -0
  32. package/src/extension/crew-cleanup.ts +168 -0
  33. package/src/extension/crew-input-router.ts +93 -0
  34. package/src/extension/crew-shortcuts.ts +87 -0
  35. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  36. package/src/extension/crew-vibes/config.ts +195 -0
  37. package/src/extension/crew-vibes/figures.ts +101 -0
  38. package/src/extension/crew-vibes/font-detect.ts +71 -0
  39. package/src/extension/crew-vibes/footer.ts +292 -0
  40. package/src/extension/crew-vibes/index.ts +432 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +396 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/cross-extension-rpc.ts +350 -0
  45. package/src/extension/help.ts +62 -0
  46. package/src/extension/import-index.ts +74 -0
  47. package/src/extension/knowledge-injection.ts +402 -0
  48. package/src/extension/management.ts +571 -0
  49. package/src/extension/message-renderers.ts +108 -0
  50. package/src/extension/notification-router.ts +152 -0
  51. package/src/extension/notification-sink.ts +54 -0
  52. package/src/extension/pi-api.ts +56 -0
  53. package/src/extension/plan-orchestrate.ts +302 -0
  54. package/src/extension/project-init.ts +170 -0
  55. package/src/extension/register.ts +113 -0
  56. package/src/extension/registration/artifact-cleanup.ts +20 -0
  57. package/src/extension/registration/command-registration.ts +58 -0
  58. package/src/extension/registration/command-utils.ts +58 -0
  59. package/src/extension/registration/commands.ts +1228 -0
  60. package/src/extension/registration/compaction-guard.ts +333 -0
  61. package/src/extension/registration/context-builder.ts +135 -0
  62. package/src/extension/registration/crash-recovery-cache.ts +54 -0
  63. package/src/extension/registration/foreground-run-controller.ts +238 -0
  64. package/src/extension/registration/hook-registration.ts +119 -0
  65. package/src/extension/registration/lazy-configurers.ts +124 -0
  66. package/src/extension/registration/lifecycle-handlers.ts +923 -0
  67. package/src/extension/registration/lifecycle.ts +259 -0
  68. package/src/extension/registration/observability.ts +322 -0
  69. package/src/extension/registration/registration-types.ts +158 -0
  70. package/src/extension/registration/runtime-cleanup.ts +230 -0
  71. package/src/extension/registration/subagent-helpers.ts +125 -0
  72. package/src/extension/registration/subagent-manager-setup.ts +330 -0
  73. package/src/extension/registration/subagent-tools.ts +456 -0
  74. package/src/extension/registration/team-tool.ts +225 -0
  75. package/src/extension/registration/tool-registration.ts +50 -0
  76. package/src/extension/registration/ui.ts +172 -0
  77. package/src/extension/registration/viewers.ts +121 -0
  78. package/src/extension/registration/wire-cross-extension.ts +37 -0
  79. package/src/extension/result-watcher.ts +139 -0
  80. package/src/extension/rpc-hmac.ts +256 -0
  81. package/src/extension/run-bundle-schema.ts +104 -0
  82. package/src/extension/run-export.ts +97 -0
  83. package/src/extension/run-import.ts +168 -0
  84. package/src/extension/run-index.ts +131 -0
  85. package/src/extension/run-maintenance.ts +190 -0
  86. package/src/extension/session-summary.ts +18 -0
  87. package/src/extension/team-manager-command.ts +153 -0
  88. package/src/extension/team-onboard.ts +180 -0
  89. package/src/extension/team-recommendation.ts +288 -0
  90. package/src/extension/team-tool/anchor.ts +172 -0
  91. package/src/extension/team-tool/api.ts +1244 -0
  92. package/src/extension/team-tool/auto-summarize.ts +142 -0
  93. package/src/extension/team-tool/cache-control.ts +19 -0
  94. package/src/extension/team-tool/cancel.ts +337 -0
  95. package/src/extension/team-tool/chain-dispatch.ts +95 -0
  96. package/src/extension/team-tool/chain-executor.ts +379 -0
  97. package/src/extension/team-tool/config-patch.ts +50 -0
  98. package/src/extension/team-tool/context.ts +149 -0
  99. package/src/extension/team-tool/destructive-gate.ts +52 -0
  100. package/src/extension/team-tool/dispatch/automate.ts +80 -0
  101. package/src/extension/team-tool/dispatch/control.ts +42 -0
  102. package/src/extension/team-tool/dispatch/index.ts +95 -0
  103. package/src/extension/team-tool/dispatch/manage.ts +161 -0
  104. package/src/extension/team-tool/dispatch/run.ts +53 -0
  105. package/src/extension/team-tool/dispatch/status.ts +187 -0
  106. package/src/extension/team-tool/doctor.ts +431 -0
  107. package/src/extension/team-tool/explain.ts +282 -0
  108. package/src/extension/team-tool/failure-patterns.ts +118 -0
  109. package/src/extension/team-tool/goal-wrap.ts +330 -0
  110. package/src/extension/team-tool/goal.ts +556 -0
  111. package/src/extension/team-tool/handle-schedule.ts +332 -0
  112. package/src/extension/team-tool/handle-settings.ts +520 -0
  113. package/src/extension/team-tool/health-monitor.ts +481 -0
  114. package/src/extension/team-tool/inspect.ts +88 -0
  115. package/src/extension/team-tool/intent-policy.ts +45 -0
  116. package/src/extension/team-tool/lifecycle-actions.ts +615 -0
  117. package/src/extension/team-tool/orchestrate.ts +98 -0
  118. package/src/extension/team-tool/parallel-dispatch.ts +183 -0
  119. package/src/extension/team-tool/plan.ts +50 -0
  120. package/src/extension/team-tool/respond.ts +150 -0
  121. package/src/extension/team-tool/run-deadline.ts +66 -0
  122. package/src/extension/team-tool/run-not-found.ts +49 -0
  123. package/src/extension/team-tool/run.ts +1051 -0
  124. package/src/extension/team-tool/status.ts +260 -0
  125. package/src/extension/team-tool/workflow-manage.ts +261 -0
  126. package/src/extension/team-tool-types.ts +26 -0
  127. package/src/extension/team-tool.ts +750 -0
  128. package/src/extension/tool-result.ts +16 -0
  129. package/src/extension/validate-resources.ts +108 -0
  130. package/src/hooks/registry.ts +200 -0
  131. package/src/hooks/types.ts +69 -0
  132. package/src/i18n.ts +212 -0
  133. package/src/observability/correlation.ts +50 -0
  134. package/src/observability/event-bus.ts +86 -0
  135. package/src/observability/event-to-metric.ts +170 -0
  136. package/src/observability/exporters/adapter.ts +27 -0
  137. package/src/observability/exporters/otlp-exporter.ts +356 -0
  138. package/src/observability/exporters/prometheus-exporter.ts +54 -0
  139. package/src/observability/metric-registry.ts +100 -0
  140. package/src/observability/metric-retention.ts +64 -0
  141. package/src/observability/metric-sink.ts +99 -0
  142. package/src/observability/metrics-primitives.ts +219 -0
  143. package/src/plugins/plugin-define.ts +6 -0
  144. package/src/plugins/plugin-registry.ts +32 -0
  145. package/src/plugins/plugins/index.ts +3 -0
  146. package/src/plugins/plugins/nextjs.ts +19 -0
  147. package/src/plugins/plugins/vite.ts +10 -0
  148. package/src/plugins/plugins/vitest.ts +9 -0
  149. package/src/prompt/prompt-runtime.ts +380 -0
  150. package/src/runtime/adaptive-plan.ts +563 -0
  151. package/src/runtime/agent-control.ts +215 -0
  152. package/src/runtime/agent-memory.ts +81 -0
  153. package/src/runtime/agent-observability.ts +122 -0
  154. package/src/runtime/anchor-manager.ts +475 -0
  155. package/src/runtime/async-marker.ts +32 -0
  156. package/src/runtime/async-runner.ts +381 -0
  157. package/src/runtime/attention-events.ts +28 -0
  158. package/src/runtime/auto-summarize.ts +344 -0
  159. package/src/runtime/background-runner.ts +840 -0
  160. package/src/runtime/batch-barrier.ts +147 -0
  161. package/src/runtime/broker-issuer.ts +39 -0
  162. package/src/runtime/cancellation-token.ts +99 -0
  163. package/src/runtime/cancellation.ts +99 -0
  164. package/src/runtime/capability-inventory.ts +137 -0
  165. package/src/runtime/chain-parser.ts +237 -0
  166. package/src/runtime/chain-runner.ts +606 -0
  167. package/src/runtime/checkpoint.ts +291 -0
  168. package/src/runtime/child-pi-constants.ts +42 -0
  169. package/src/runtime/child-pi-kill.ts +180 -0
  170. package/src/runtime/child-pi-pool.ts +68 -0
  171. package/src/runtime/child-pi-spawn.ts +287 -0
  172. package/src/runtime/child-pi-steering.ts +128 -0
  173. package/src/runtime/child-pi-streams.ts +296 -0
  174. package/src/runtime/child-pi-transcript.ts +169 -0
  175. package/src/runtime/child-pi.ts +1105 -0
  176. package/src/runtime/coalesce-tasks.ts +268 -0
  177. package/src/runtime/code-summary.ts +292 -0
  178. package/src/runtime/command-trace.ts +105 -0
  179. package/src/runtime/compact-pipeline.ts +56 -0
  180. package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
  181. package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
  182. package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
  183. package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
  184. package/src/runtime/compact-stages/index.ts +23 -0
  185. package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
  186. package/src/runtime/compact-stages/truncation-stage.ts +74 -0
  187. package/src/runtime/compaction-summary.ts +278 -0
  188. package/src/runtime/completion-guard.ts +207 -0
  189. package/src/runtime/concurrency.ts +58 -0
  190. package/src/runtime/crash-classification.ts +229 -0
  191. package/src/runtime/crash-recovery.ts +594 -0
  192. package/src/runtime/crew-agent-records.ts +595 -0
  193. package/src/runtime/crew-agent-runtime.ts +62 -0
  194. package/src/runtime/crew-broker-child.ts +88 -0
  195. package/src/runtime/crew-broker-client.ts +673 -0
  196. package/src/runtime/crew-broker-tokens.ts +159 -0
  197. package/src/runtime/crew-broker.ts +1304 -0
  198. package/src/runtime/crew-hooks.ts +227 -0
  199. package/src/runtime/cross-extension-rpc.ts +143 -0
  200. package/src/runtime/custom-tools/irc-tool.ts +282 -0
  201. package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
  202. package/src/runtime/deadletter.ts +47 -0
  203. package/src/runtime/delivery-coordinator.ts +211 -0
  204. package/src/runtime/delta-conflict.ts +348 -0
  205. package/src/runtime/deterministic-ast.ts +161 -0
  206. package/src/runtime/diagnostic-export.ts +171 -0
  207. package/src/runtime/direct-run.ts +45 -0
  208. package/src/runtime/dwf-state-store.ts +102 -0
  209. package/src/runtime/dynamic-workflow-context.ts +1013 -0
  210. package/src/runtime/dynamic-workflow-runner.ts +325 -0
  211. package/src/runtime/effectiveness.ts +90 -0
  212. package/src/runtime/errors/crew-errors.ts +162 -0
  213. package/src/runtime/event-stream-bridge.ts +98 -0
  214. package/src/runtime/foreground-control.ts +193 -0
  215. package/src/runtime/foreground-watchdog.ts +132 -0
  216. package/src/runtime/global-worker-cap.ts +96 -0
  217. package/src/runtime/goal-achievement.ts +148 -0
  218. package/src/runtime/goal-evaluator.ts +365 -0
  219. package/src/runtime/goal-loop-runner.ts +825 -0
  220. package/src/runtime/goal-state-store.ts +219 -0
  221. package/src/runtime/green-contract.ts +55 -0
  222. package/src/runtime/group-join.ts +267 -0
  223. package/src/runtime/handoff-manager.ts +598 -0
  224. package/src/runtime/heartbeat-gradient.ts +36 -0
  225. package/src/runtime/heartbeat-watcher.ts +239 -0
  226. package/src/runtime/hidden-handoff.ts +407 -0
  227. package/src/runtime/important-line-classifier.ts +140 -0
  228. package/src/runtime/intercom-bridge.ts +187 -0
  229. package/src/runtime/iteration-hooks.ts +305 -0
  230. package/src/runtime/live-agent-control.ts +136 -0
  231. package/src/runtime/live-agent-manager.ts +704 -0
  232. package/src/runtime/live-control-realtime.ts +56 -0
  233. package/src/runtime/live-extension-bridge.ts +148 -0
  234. package/src/runtime/live-irc.ts +97 -0
  235. package/src/runtime/live-session-health.ts +108 -0
  236. package/src/runtime/live-session-runtime.ts +1146 -0
  237. package/src/runtime/loop-gates.ts +128 -0
  238. package/src/runtime/manifest-cache.ts +375 -0
  239. package/src/runtime/mcp-proxy.ts +105 -0
  240. package/src/runtime/metric-parser.ts +36 -0
  241. package/src/runtime/model-fallback.ts +409 -0
  242. package/src/runtime/model-resolver.ts +126 -0
  243. package/src/runtime/model-scope.ts +158 -0
  244. package/src/runtime/orphan-worker-registry.ts +465 -0
  245. package/src/runtime/output-validator.ts +202 -0
  246. package/src/runtime/overflow-recovery.ts +206 -0
  247. package/src/runtime/parallel-research.ts +68 -0
  248. package/src/runtime/parallel-utils.ts +160 -0
  249. package/src/runtime/parent-guard.ts +134 -0
  250. package/src/runtime/path-overlap.ts +150 -0
  251. package/src/runtime/peer-dep.ts +292 -0
  252. package/src/runtime/per-write-validator.ts +181 -0
  253. package/src/runtime/phase-progress.ts +217 -0
  254. package/src/runtime/phase-tracker.ts +385 -0
  255. package/src/runtime/pi-args.ts +655 -0
  256. package/src/runtime/pi-json-output.ts +170 -0
  257. package/src/runtime/pi-spawn.ts +273 -0
  258. package/src/runtime/pipeline-runner.ts +523 -0
  259. package/src/runtime/plan-templates.ts +201 -0
  260. package/src/runtime/policy-engine.ts +115 -0
  261. package/src/runtime/post-checks.ts +142 -0
  262. package/src/runtime/post-exit-stdio-guard.ts +109 -0
  263. package/src/runtime/process-lifecycle.ts +491 -0
  264. package/src/runtime/process-status.ts +154 -0
  265. package/src/runtime/progress-event-coalescer.ts +44 -0
  266. package/src/runtime/progress-tracker.ts +124 -0
  267. package/src/runtime/prose-compressor.ts +162 -0
  268. package/src/runtime/recovery-recipes.ts +203 -0
  269. package/src/runtime/replace.ts +570 -0
  270. package/src/runtime/resilient-edit.ts +153 -0
  271. package/src/runtime/result-extractor.ts +217 -0
  272. package/src/runtime/retry-executor.ts +109 -0
  273. package/src/runtime/retry-runner.ts +336 -0
  274. package/src/runtime/role-permission.ts +59 -0
  275. package/src/runtime/run-coalesced-task-group.ts +308 -0
  276. package/src/runtime/run-drift.ts +219 -0
  277. package/src/runtime/run-tracker.ts +116 -0
  278. package/src/runtime/run-worker.ts +78 -0
  279. package/src/runtime/runtime-policy.ts +29 -0
  280. package/src/runtime/runtime-resolver.ts +172 -0
  281. package/src/runtime/runtime-warmup.ts +160 -0
  282. package/src/runtime/scheduler.ts +353 -0
  283. package/src/runtime/semaphore.ts +141 -0
  284. package/src/runtime/sensitive-paths.ts +93 -0
  285. package/src/runtime/session-resources.ts +25 -0
  286. package/src/runtime/session-snapshot.ts +59 -0
  287. package/src/runtime/session-usage.ts +79 -0
  288. package/src/runtime/settings-store.ts +155 -0
  289. package/src/runtime/sidechain-output.ts +35 -0
  290. package/src/runtime/single-agent-compose.ts +84 -0
  291. package/src/runtime/skill-effectiveness.ts +524 -0
  292. package/src/runtime/skill-instructions.ts +394 -0
  293. package/src/runtime/stale-reconciler.ts +680 -0
  294. package/src/runtime/stream-preview.ts +184 -0
  295. package/src/runtime/streaming-output.ts +50 -0
  296. package/src/runtime/subagent-manager.ts +561 -0
  297. package/src/runtime/subprocess-tool-registry.ts +70 -0
  298. package/src/runtime/supervisor-contact.ts +64 -0
  299. package/src/runtime/task-display.ts +52 -0
  300. package/src/runtime/task-graph-scheduler.ts +227 -0
  301. package/src/runtime/task-graph.ts +290 -0
  302. package/src/runtime/task-health.ts +83 -0
  303. package/src/runtime/task-id.ts +161 -0
  304. package/src/runtime/task-output-context.ts +602 -0
  305. package/src/runtime/task-packet.ts +268 -0
  306. package/src/runtime/task-quality.ts +199 -0
  307. package/src/runtime/task-runner/capabilities.ts +78 -0
  308. package/src/runtime/task-runner/child-executor.ts +790 -0
  309. package/src/runtime/task-runner/context-retrieval.ts +163 -0
  310. package/src/runtime/task-runner/live-executor.ts +227 -0
  311. package/src/runtime/task-runner/output-splitter.ts +152 -0
  312. package/src/runtime/task-runner/post-execution.ts +480 -0
  313. package/src/runtime/task-runner/pre-execution.ts +365 -0
  314. package/src/runtime/task-runner/progress.ts +157 -0
  315. package/src/runtime/task-runner/prompt-builder.ts +278 -0
  316. package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
  317. package/src/runtime/task-runner/result-utils.ts +16 -0
  318. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  319. package/src/runtime/task-runner/run-projection.ts +125 -0
  320. package/src/runtime/task-runner/scaffold-executor.ts +25 -0
  321. package/src/runtime/task-runner/state-helpers.ts +176 -0
  322. package/src/runtime/task-runner/tail-read.ts +34 -0
  323. package/src/runtime/task-runner.ts +216 -0
  324. package/src/runtime/team-runner-artifacts.ts +13 -0
  325. package/src/runtime/team-runner.ts +2396 -0
  326. package/src/runtime/tool-output-pruner.ts +333 -0
  327. package/src/runtime/tool-progress.ts +278 -0
  328. package/src/runtime/usage-tracker.ts +73 -0
  329. package/src/runtime/verification-gates.ts +579 -0
  330. package/src/runtime/verification-integrity.ts +108 -0
  331. package/src/runtime/verification-worktree.ts +186 -0
  332. package/src/runtime/worker-heartbeat.ts +25 -0
  333. package/src/runtime/worker-startup.ts +83 -0
  334. package/src/runtime/workflow-state.ts +195 -0
  335. package/src/runtime/workspace-lock.ts +443 -0
  336. package/src/runtime/workspace-tree.ts +312 -0
  337. package/src/runtime/yield-handler.ts +224 -0
  338. package/src/runtime/zombie-scanner.ts +298 -0
  339. package/src/schema/config-schema.ts +327 -0
  340. package/src/schema/team-tool-schema.ts +492 -0
  341. package/src/schema/validation-types.ts +159 -0
  342. package/src/skills/discover-skills.ts +203 -0
  343. package/src/skills/skill-templates.ts +456 -0
  344. package/src/skills/validate.ts +285 -0
  345. package/src/state/active-run-registry.ts +439 -0
  346. package/src/state/artifact-store.ts +176 -0
  347. package/src/state/atomic-write.ts +970 -0
  348. package/src/state/blob-store.ts +308 -0
  349. package/src/state/contracts.ts +160 -0
  350. package/src/state/crew-init.ts +269 -0
  351. package/src/state/decision-ledger.ts +372 -0
  352. package/src/state/event-log-rotation.ts +399 -0
  353. package/src/state/event-log.ts +1465 -0
  354. package/src/state/event-reconstructor.ts +229 -0
  355. package/src/state/gitignore-manager.ts +46 -0
  356. package/src/state/health-store.ts +79 -0
  357. package/src/state/hook-instinct-bridge.ts +94 -0
  358. package/src/state/hook-integrations.ts +51 -0
  359. package/src/state/instinct-store.ts +275 -0
  360. package/src/state/jsonl-writer.ts +107 -0
  361. package/src/state/locks.ts +562 -0
  362. package/src/state/mailbox.ts +916 -0
  363. package/src/state/observation-store.ts +176 -0
  364. package/src/state/run-cache.ts +193 -0
  365. package/src/state/run-graph.ts +183 -0
  366. package/src/state/run-metrics.ts +165 -0
  367. package/src/state/schedule.ts +166 -0
  368. package/src/state/session-state-map.ts +51 -0
  369. package/src/state/state-store.ts +973 -0
  370. package/src/state/task-claims.ts +61 -0
  371. package/src/state/tiered-eval.ts +480 -0
  372. package/src/state/types-eval.ts +58 -0
  373. package/src/state/types.ts +447 -0
  374. package/src/state/usage.ts +138 -0
  375. package/src/state/worker-atomic-writer.ts +198 -0
  376. package/src/subagents/async-entry.ts +1 -0
  377. package/src/subagents/index.ts +3 -0
  378. package/src/subagents/live/control.ts +1 -0
  379. package/src/subagents/live/manager.ts +1 -0
  380. package/src/subagents/live/realtime.ts +1 -0
  381. package/src/subagents/live/session-runtime.ts +1 -0
  382. package/src/subagents/manager.ts +1 -0
  383. package/src/subagents/spawn.ts +1 -0
  384. package/src/teams/discover-teams.ts +187 -0
  385. package/src/teams/team-config.ts +27 -0
  386. package/src/teams/team-serializer.ts +38 -0
  387. package/src/tools/safe-bash-extension.ts +54 -0
  388. package/src/tools/safe-bash.ts +505 -0
  389. package/src/types/diff.d.ts +18 -0
  390. package/src/types/new-api-types.ts +35 -0
  391. package/src/ui/agent-management-overlay.ts +160 -0
  392. package/src/ui/card-colors.ts +139 -0
  393. package/src/ui/crew-footer.ts +102 -0
  394. package/src/ui/crew-select-list.ts +112 -0
  395. package/src/ui/dashboard-panes/agents-pane.ts +164 -0
  396. package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
  397. package/src/ui/dashboard-panes/capability-pane.ts +77 -0
  398. package/src/ui/dashboard-panes/health-pane.ts +31 -0
  399. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
  400. package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
  401. package/src/ui/dashboard-panes/progress-pane.ts +38 -0
  402. package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
  403. package/src/ui/deploy-bundled-themes.ts +71 -0
  404. package/src/ui/dwf-phase-display.ts +151 -0
  405. package/src/ui/dynamic-border.ts +35 -0
  406. package/src/ui/format-helpers.ts +31 -0
  407. package/src/ui/heartbeat-aggregator.ts +74 -0
  408. package/src/ui/key-utils.ts +42 -0
  409. package/src/ui/keybinding-map.ts +248 -0
  410. package/src/ui/layout-primitives.ts +106 -0
  411. package/src/ui/live-conversation-overlay.ts +183 -0
  412. package/src/ui/live-duration.ts +55 -0
  413. package/src/ui/live-run-sidebar.ts +293 -0
  414. package/src/ui/loaders.ts +6 -0
  415. package/src/ui/mascot.ts +449 -0
  416. package/src/ui/overlays/agent-picker-overlay.ts +66 -0
  417. package/src/ui/overlays/confirm-overlay.ts +60 -0
  418. package/src/ui/overlays/help-overlay.ts +177 -0
  419. package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
  420. package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
  421. package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
  422. package/src/ui/pi-ui-compat.ts +68 -0
  423. package/src/ui/powerbar-publisher.ts +459 -0
  424. package/src/ui/render-coalescer.ts +101 -0
  425. package/src/ui/render-diff.ts +160 -0
  426. package/src/ui/render-scheduler.ts +278 -0
  427. package/src/ui/run-action-dispatcher.ts +182 -0
  428. package/src/ui/run-dashboard.ts +910 -0
  429. package/src/ui/run-event-bus.ts +381 -0
  430. package/src/ui/run-snapshot-cache.ts +1057 -0
  431. package/src/ui/settings-overlay.ts +1067 -0
  432. package/src/ui/shared-overlay-scheduler.ts +96 -0
  433. package/src/ui/snapshot-types.ts +90 -0
  434. package/src/ui/spinner.ts +17 -0
  435. package/src/ui/status-colors.ts +143 -0
  436. package/src/ui/syntax-highlight.ts +136 -0
  437. package/src/ui/terminal-status.ts +269 -0
  438. package/src/ui/theme-adapter.ts +271 -0
  439. package/src/ui/theme-discovery.ts +199 -0
  440. package/src/ui/tool-progress-formatter.ts +93 -0
  441. package/src/ui/tool-renderers/brief-mode.ts +225 -0
  442. package/src/ui/tool-renderers/index.ts +726 -0
  443. package/src/ui/transcript-cache.ts +128 -0
  444. package/src/ui/transcript-entries.ts +256 -0
  445. package/src/ui/transcript-viewer.ts +424 -0
  446. package/src/ui/widget/index.ts +418 -0
  447. package/src/ui/widget/widget-formatters.ts +182 -0
  448. package/src/ui/widget/widget-model.ts +110 -0
  449. package/src/ui/widget/widget-renderer.ts +182 -0
  450. package/src/ui/widget/widget-types.ts +36 -0
  451. package/src/utils/bm25-search.ts +235 -0
  452. package/src/utils/completion-dedupe.ts +63 -0
  453. package/src/utils/conflict-detect.ts +668 -0
  454. package/src/utils/env-allowlist.ts +30 -0
  455. package/src/utils/env-filter.ts +155 -0
  456. package/src/utils/file-coalescer.ts +98 -0
  457. package/src/utils/fingerprint.ts +180 -0
  458. package/src/utils/frontmatter.ts +70 -0
  459. package/src/utils/fs-watch.ts +37 -0
  460. package/src/utils/gh-protocol.ts +556 -0
  461. package/src/utils/git.ts +260 -0
  462. package/src/utils/guards.ts +107 -0
  463. package/src/utils/ids.ts +24 -0
  464. package/src/utils/incremental-reader.ts +220 -0
  465. package/src/utils/internal-error.ts +10 -0
  466. package/src/utils/names.ts +36 -0
  467. package/src/utils/ndjson.ts +115 -0
  468. package/src/utils/paths.ts +227 -0
  469. package/src/utils/project-detector.ts +160 -0
  470. package/src/utils/redaction.ts +370 -0
  471. package/src/utils/resolve-shell.ts +36 -0
  472. package/src/utils/run-watcher-registry.ts +152 -0
  473. package/src/utils/safe-paths.ts +393 -0
  474. package/src/utils/scan-cache.ts +144 -0
  475. package/src/utils/session-utils.ts +110 -0
  476. package/src/utils/sleep.ts +54 -0
  477. package/src/utils/socket-path.ts +164 -0
  478. package/src/utils/sse-parser.ts +131 -0
  479. package/src/utils/task-name-generator.ts +337 -0
  480. package/src/utils/timings.ts +33 -0
  481. package/src/utils/token-counter.ts +200 -0
  482. package/src/utils/visual.ts +207 -0
  483. package/src/workflows/cost-estimator.ts +34 -0
  484. package/src/workflows/discover-workflows.ts +308 -0
  485. package/src/workflows/intermediate-store.ts +166 -0
  486. package/src/workflows/preflight-validator.ts +168 -0
  487. package/src/workflows/topology-analyzer.ts +203 -0
  488. package/src/workflows/validate-workflow.ts +50 -0
  489. package/src/workflows/workflow-config.ts +75 -0
  490. package/src/workflows/workflow-serializer.ts +32 -0
  491. package/src/worktree/branch-freshness.ts +112 -0
  492. package/src/worktree/cleanup.ts +341 -0
  493. package/src/worktree/worktree-manager.ts +1204 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Runtime module-graph warmup — general fix for the cold-start race.
3
+ *
4
+ * Problem: when N in-process live-session subagents spawn CONCURRENTLY, the
5
+ * tsx loader can race module-record instantiation, yielding
6
+ * `Cannot read properties of undefined (reading '<binding>')` for ANY named
7
+ * import in the hot graph (observed: `existsSync`, `validateWorkflowForTeam`).
8
+ * v0.8.1's per-import latch covered only the peer-dep namespace; this is the
9
+ * GENERAL fix.
10
+ *
11
+ * Root cause: under tsx, a named import `import { X } from "mod"` compiles to
12
+ * `mod_1.X` — a namespace-property access at runtime. If `mod_1` (the module
13
+ * namespace object) is observed mid-instantiation as `undefined`, the access
14
+ * throws. ANY module in the graph is vulnerable, not just the peer dep. So
15
+ * per-import latching doesn't scale.
16
+ *
17
+ * Fix: pre-warm the hot module graph during SINGLE-THREADED extension
18
+ * registration (before any subagent can spawn), then `await` the warmup at
19
+ * every spawn entry point. By the time concurrent subagents touch the graph,
20
+ * every module record is fully instantiated → no race window.
21
+ *
22
+ * Why this is general (not per-import):
23
+ * - `startRuntimeWarmup()` fires `import()` for the ROOT modules of each
24
+ * hot execution path. ESM transitively instantiates their entire import
25
+ * graph, so one import per path warms the whole reachable subgraph.
26
+ * - `awaitRuntimeWarmup()` is the gate: spawn paths await it before
27
+ * touching any module, guaranteeing the graph is warm regardless of
28
+ * which specific binding races.
29
+ *
30
+ * The warmup runs during `registerPiTeams()` (single-threaded, before pi
31
+ * accepts user input). It resolves in milliseconds (module loading only, no
32
+ * I/O for local modules). The `await` at spawn boundaries is belt-and-
33
+ * suspenders for pathological cases where a spawn races the warmup promise.
34
+ *
35
+ * @module runtime-warmup
36
+ */
37
+
38
+ /**
39
+ * The modules whose transitive import graphs cover every hot execution path
40
+ * a concurrent subagent can reach. Importing the ROOT of each path warms the
41
+ * full subgraph (ESM instantiates imports eagerly during `import()`).
42
+ *
43
+ * - `./live-session-runtime.ts` — the in-process subagent spawn path
44
+ * (pulls in the peer dep + the entire runtime layer).
45
+ * - `./task-runner.ts` — the child-process task dispatch path.
46
+ * - `../extension/team-tool.ts` — the team tool (pulls in
47
+ * validate-resources → validate-workflow, the `validateWorkflowForTeam` site).
48
+ * - `../extension/validate-resources.ts` — direct warm of the aggregator.
49
+ * - `@earendil-works/pi-coding-agent` — the peer dep (the `existsSync` site;
50
+ * also latched in v0.8.1 — defense in depth).
51
+ */
52
+ const HOT_MODULE_SPECIFIERS = [
53
+ "./live-session-runtime.ts",
54
+ "./task-runner.ts",
55
+ "../extension/team-tool.ts",
56
+ "../extension/team-tool/run.ts", // handleRun path — latched in team-tool.ts but its static graph (config-patch, validate-workflow) still cold-start-races under concurrent fanout
57
+ "../extension/team-tool/config-patch.ts", // effectiveRunConfig (crash variant observed in repro)
58
+ "../extension/validate-resources.ts",
59
+ "../workflows/validate-workflow.ts", // validateWorkflowForTeam (crash variant observed across sessions)
60
+ "../state/crew-init.ts", // TDZ-prone top-level consts (CREW_README); dynamically imported by team-tool/run.ts
61
+ ] as const;
62
+
63
+ /** Additional bare-specifier peer deps to warm. */
64
+ const HOT_PEER_DEPS = ["@earendil-works/pi-coding-agent"] as const;
65
+
66
+ let warmupPromise: Promise<void> | undefined;
67
+ let warmupStarted = false;
68
+ let warmupCompleted = false;
69
+ let warmupDurationMs: number | undefined;
70
+ let warmupError: string | undefined;
71
+
72
+ /**
73
+ * Start the runtime warmup (idempotent). Fires eager `import()` of the hot
74
+ * module graph. Safe to call during single-threaded registration — the
75
+ * promises resolve on the event loop before any subagent can spawn.
76
+ *
77
+ * Errors are swallowed: a failed warmup (e.g. peer dep absent) must never
78
+ * block the extension from loading. The worst case is the old race returns
79
+ * (which is no worse than before this fix).
80
+ */
81
+ export function startRuntimeWarmup(): void {
82
+ if (warmupStarted) return;
83
+ warmupStarted = true;
84
+ const startedAt = Date.now();
85
+ warmupPromise = (async (): Promise<void> => {
86
+ const imports: Array<Promise<unknown>> = [];
87
+ for (const spec of HOT_MODULE_SPECIFIERS) {
88
+ imports.push(
89
+ import(new URL(spec, import.meta.url).href).catch(() => {
90
+ // swallow — never block registration on a warmup failure
91
+ }),
92
+ );
93
+ }
94
+ for (const dep of HOT_PEER_DEPS) {
95
+ imports.push(
96
+ import(dep).catch(() => {
97
+ // peer dep may be absent (optional dep) — swallow
98
+ }),
99
+ );
100
+ }
101
+ await Promise.all(imports);
102
+ })()
103
+ .then(() => {
104
+ warmupCompleted = true;
105
+ warmupDurationMs = Date.now() - startedAt;
106
+ })
107
+ .catch((err: unknown) => {
108
+ // final safety net — warmup must never reject. Record for diagnostics.
109
+ warmupError = err instanceof Error ? err.message : String(err ?? "unknown");
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Await the runtime warmup at a spawn entry point. No-op if warmup hasn't
115
+ * started (back-compat for callers that don't call startRuntimeWarmup) or has
116
+ * already resolved. Guarantees the hot module graph is instantiated before
117
+ * the caller touches any module — eliminating the concurrent cold-start race.
118
+ */
119
+ export async function awaitRuntimeWarmup(): Promise<void> {
120
+ if (warmupPromise) await warmupPromise;
121
+ }
122
+
123
+ /**
124
+ * Test seam: reset the warmup state so tests can re-trigger it. Also lets
125
+ * tests verify idempotency by calling startRuntimeWarmup() multiple times.
126
+ */
127
+ export function resetRuntimeWarmupForTest(): void {
128
+ warmupPromise = undefined;
129
+ warmupStarted = false;
130
+ warmupCompleted = false;
131
+ warmupDurationMs = undefined;
132
+ warmupError = undefined;
133
+ }
134
+
135
+ /** Test seam: has startRuntimeWarmup() been called? */
136
+ export function isRuntimeWarmupStarted(): boolean {
137
+ return warmupStarted;
138
+ }
139
+
140
+ /**
141
+ * Diagnostic snapshot of warmup state for `team doctor`. Surfaces whether the
142
+ * v0.8.6 cold-start fix is active and how long the graph warmup took, so a
143
+ * session can confirm the fix loaded (post-restart) and isn't pathologically
144
+ * slow.
145
+ */
146
+ export interface RuntimeWarmupStatus {
147
+ started: boolean;
148
+ completed: boolean;
149
+ durationMs: number | undefined;
150
+ error: string | undefined;
151
+ }
152
+
153
+ export function getRuntimeWarmupStatus(): RuntimeWarmupStatus {
154
+ return {
155
+ started: warmupStarted,
156
+ completed: warmupCompleted,
157
+ durationMs: warmupDurationMs,
158
+ error: warmupError,
159
+ };
160
+ }
@@ -0,0 +1,353 @@
1
+ export type ScheduleType = "cron" | "once" | "interval";
2
+
3
+ export interface ScheduledJob {
4
+ id: string;
5
+ name: string;
6
+ description: string;
7
+ schedule: string;
8
+ scheduleType: ScheduleType;
9
+ intervalMs?: number;
10
+ subagentType: string;
11
+ prompt: string;
12
+ enabled: boolean;
13
+ createdAt: string;
14
+ lastRun?: string;
15
+ lastStatus?: "success" | "error" | "running";
16
+ nextRun?: string;
17
+ runCount: number;
18
+ /** Run IDs spawned by this job. Used to cancel runs when job is removed. */
19
+ spawnedRunIds?: string[];
20
+ }
21
+
22
+ export type ScheduleChangeEvent =
23
+ | { type: "added"; job: ScheduledJob }
24
+ | { type: "removed"; jobId: string; spawnedRunIds?: string[] }
25
+ | { type: "updated"; job: ScheduledJob }
26
+ | { type: "fired"; jobId: string; agentId: string; name: string }
27
+ | { type: "error"; jobId: string; error: string };
28
+
29
+ export class CrewScheduler {
30
+ private jobs = new Map<string, ScheduledJob>();
31
+ private timers = new Map<string, ReturnType<typeof setInterval | typeof setTimeout>>();
32
+ private emit?: (event: ScheduleChangeEvent) => void;
33
+ private executor?: (job: ScheduledJob) => string;
34
+ private finalizer?: (jobId: string, agentId: string) => void;
35
+ private runCancelFn?: (runId: string) => void;
36
+
37
+ start(options: {
38
+ emit: (event: ScheduleChangeEvent) => void;
39
+ executor: (job: ScheduledJob) => string;
40
+ finalizer: (jobId: string, agentId: string) => void;
41
+ /** Optional callback to cancel a spawned run by runId. */
42
+ runCancelFn?: (runId: string) => void;
43
+ }): void {
44
+ this.emit = options.emit;
45
+ this.executor = options.executor;
46
+ this.finalizer = options.finalizer;
47
+ this.runCancelFn = options.runCancelFn;
48
+ }
49
+
50
+ stop(): void {
51
+ for (const t of this.timers.values()) {
52
+ clearInterval(t as ReturnType<typeof setInterval>);
53
+ clearTimeout(t as ReturnType<typeof setTimeout>);
54
+ }
55
+ this.timers.clear();
56
+ this.emit = undefined;
57
+ this.executor = undefined;
58
+ this.finalizer = undefined;
59
+ this.runCancelFn = undefined;
60
+ }
61
+
62
+ add(job: ScheduledJob): void {
63
+ this.jobs.set(job.id, job);
64
+ if (job.enabled) this.arm(job);
65
+ this.emit?.({ type: "added", job });
66
+ }
67
+
68
+ remove(id: string): boolean {
69
+ const job = this.jobs.get(id);
70
+ const spawnedRunIds = job?.spawnedRunIds;
71
+ this.disarm(id);
72
+ // Cancel all spawned runs that are still active
73
+ if (spawnedRunIds && this.runCancelFn) {
74
+ for (const runId of spawnedRunIds) {
75
+ try {
76
+ this.runCancelFn(runId);
77
+ } catch {
78
+ /* best-effort */
79
+ }
80
+ }
81
+ }
82
+ const ok = this.jobs.delete(id);
83
+ if (ok) this.emit?.({ type: "removed", jobId: id, spawnedRunIds });
84
+ return ok;
85
+ }
86
+
87
+ update(id: string, patch: Partial<ScheduledJob>): ScheduledJob | undefined {
88
+ const existing = this.jobs.get(id);
89
+ if (!existing) return undefined;
90
+ this.disarm(id);
91
+ const updated = { ...existing, ...patch };
92
+ this.jobs.set(id, updated);
93
+ if (updated.enabled) this.arm(updated);
94
+ this.emit?.({ type: "updated", job: updated });
95
+ return updated;
96
+ }
97
+
98
+ list(): ScheduledJob[] {
99
+ return [...this.jobs.values()];
100
+ }
101
+
102
+ /** Record a runId spawned by a job. Call this after executor fires. */
103
+ recordSpawnedRun(jobId: string, runId: string): void {
104
+ const job = this.jobs.get(jobId);
105
+ if (!job) return;
106
+ const spawnedRunIds = [...(job.spawnedRunIds ?? []), runId];
107
+ this.jobs.set(jobId, { ...job, spawnedRunIds });
108
+ }
109
+
110
+ private arm(job: ScheduledJob): void {
111
+ if (this.timers.has(job.id)) return;
112
+ if (job.scheduleType === "interval" && job.intervalMs) {
113
+ const t = setInterval(() => this.fire(job.id), job.intervalMs);
114
+ t.unref();
115
+ this.timers.set(job.id, t);
116
+ } else if (job.scheduleType === "once") {
117
+ const target = new Date(job.schedule).getTime();
118
+ const delay = target - Date.now();
119
+ if (delay > 0) {
120
+ const t = setTimeout(() => {
121
+ this.fire(job.id);
122
+ this.update(job.id, { enabled: false });
123
+ }, delay);
124
+ t.unref();
125
+ this.timers.set(job.id, t);
126
+ } else {
127
+ this.update(job.id, { enabled: false, lastStatus: "error" });
128
+ this.emit?.({
129
+ type: "error",
130
+ jobId: job.id,
131
+ error: `Scheduled time ${job.schedule} is in the past`,
132
+ });
133
+ }
134
+ }
135
+ }
136
+
137
+ private disarm(id: string): void {
138
+ const t = this.timers.get(id);
139
+ if (t) {
140
+ // Branch on timer type to use correct clear function
141
+ const job = this.jobs.get(id);
142
+ if (job?.scheduleType === "once") {
143
+ clearTimeout(t as ReturnType<typeof setTimeout>);
144
+ } else {
145
+ clearInterval(t as ReturnType<typeof setInterval>);
146
+ }
147
+ this.timers.delete(id);
148
+ }
149
+ }
150
+
151
+ private fire(id: string): void {
152
+ const job = this.jobs.get(id);
153
+ if (!job?.enabled || !this.executor) return;
154
+ this.update(id, { lastStatus: "running" });
155
+ let agentId: string;
156
+ try {
157
+ agentId = this.executor(job);
158
+ } catch (err) {
159
+ const error = err instanceof Error ? err.message : String(err);
160
+ this.update(id, {
161
+ lastRun: new Date().toISOString(),
162
+ lastStatus: "error",
163
+ });
164
+ this.emit?.({ type: "error", jobId: id, error });
165
+ return;
166
+ }
167
+ this.emit?.({ type: "fired", jobId: id, agentId, name: job.name });
168
+ this.finalizer?.(id, agentId);
169
+ }
170
+
171
+ static detectSchedule(s: string): {
172
+ type: ScheduleType;
173
+ intervalMs?: number;
174
+ normalized: string;
175
+ } {
176
+ const trimmed = s.trim();
177
+ // Relative: +10m
178
+ const rel = trimmed.match(/^\+(\d+)(s|m|h|d)$/);
179
+ if (rel) {
180
+ const ms = parseInt(rel[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[rel[2] as "s" | "m" | "h" | "d"];
181
+ return {
182
+ type: "once",
183
+ normalized: new Date(Date.now() + ms).toISOString(),
184
+ };
185
+ }
186
+ // Interval: 5m
187
+ const ivl = trimmed.match(/^(\d+)(s|m|h|d)$/);
188
+ if (ivl) {
189
+ const ms = parseInt(ivl[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[ivl[2] as "s" | "m" | "h" | "d"];
190
+ return { type: "interval", intervalMs: ms, normalized: trimmed };
191
+ }
192
+ // ISO timestamp
193
+ if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
194
+ const d = new Date(trimmed);
195
+ if (!Number.isNaN(d.getTime())) {
196
+ if (d.getTime() <= Date.now()) throw new Error(`Scheduled time ${d.toISOString()} is in the past.`);
197
+ return { type: "once", normalized: d.toISOString() };
198
+ }
199
+ }
200
+ // Simple cron-like (5 fields)
201
+ const cronFields = trimmed.split(/\s+/);
202
+ if (cronFields.length >= 5) {
203
+ return { type: "cron", normalized: trimmed };
204
+ }
205
+ throw new Error(`Invalid schedule "${s}". Use "5m", "+10m", ISO timestamp, or cron expression.`);
206
+ }
207
+ }
208
+
209
+ export interface ScheduleSpec {
210
+ kind: "once" | "interval" | "cron";
211
+ spec: string;
212
+ }
213
+
214
+ function parseIntervalMs(s: string): number | undefined {
215
+ let ms = 0;
216
+ let remaining = s;
217
+ const unitMs: Record<string, number> = {
218
+ s: 1000,
219
+ m: 60_000,
220
+ h: 3_600_000,
221
+ d: 86_400_000,
222
+ };
223
+ while (remaining.length > 0) {
224
+ const m = remaining.match(/^(\d+)(s|m|h|d)/);
225
+ if (!m) return undefined;
226
+ ms += parseInt(m[1], 10) * unitMs[m[2]];
227
+ remaining = remaining.slice(m[0].length);
228
+ }
229
+ return ms;
230
+ }
231
+
232
+ function nextCronDate(spec: string, from: Date): Date | { error: string } | null {
233
+ const parts = spec.split(/\s+/);
234
+ if (parts.length < 5) return { error: "Invalid cron expression" };
235
+ const [minStr, hourStr, domStr, monthStr, dowStr] = parts;
236
+
237
+ function matchField(value: number, str: string, min: number, max: number): boolean {
238
+ if (str === "*") return true;
239
+ const n = parseInt(str, 10);
240
+ if (!Number.isNaN(n) && n >= min && n <= max && n === value) return true;
241
+ if (/^\d+-\d+$/.test(str)) {
242
+ const [a, b] = str.split("-").map(Number);
243
+ return value >= a && value <= b;
244
+ }
245
+ if (str.includes(",")) {
246
+ return str.split(",").some((part) => matchField(value, part.trim(), min, max));
247
+ }
248
+ return false;
249
+ }
250
+
251
+ let cursor = new Date(from.getTime());
252
+ cursor.setSeconds(0, 0);
253
+ cursor = new Date(cursor.getTime() + 60_000);
254
+
255
+ const maxIterations = 366 * 24 * 60;
256
+ for (let i = 0; i < maxIterations; i++) {
257
+ const min = cursor.getUTCMinutes();
258
+ const hour = cursor.getUTCHours();
259
+ const dom = cursor.getUTCDate();
260
+ const month = cursor.getUTCMonth() + 1;
261
+ const dow = cursor.getUTCDay();
262
+
263
+ if (
264
+ matchField(min, minStr, 0, 59) &&
265
+ matchField(hour, hourStr, 0, 23) &&
266
+ matchField(dom, domStr, 1, 31) &&
267
+ matchField(month, monthStr, 1, 12) &&
268
+ matchField(dow, dowStr, 0, 6)
269
+ ) {
270
+ return cursor;
271
+ }
272
+
273
+ cursor = new Date(cursor.getTime() + 60_000);
274
+ }
275
+
276
+ return { error: "No next cron occurrence found within search window" };
277
+ }
278
+
279
+ export function parseSchedule(spec: string): ScheduleSpec | { error: string } {
280
+ const trimmed = spec.trim();
281
+ if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
282
+ const d = new Date(trimmed);
283
+ if (!Number.isNaN(d.getTime())) {
284
+ return { kind: "once", spec: trimmed };
285
+ }
286
+ }
287
+ if (/^\+\d+(s|m|h|d)$/.test(trimmed)) {
288
+ return { kind: "once", spec: trimmed };
289
+ }
290
+ const ivlMs = parseIntervalMs(trimmed);
291
+ if (ivlMs !== undefined && ivlMs > 0) {
292
+ return { kind: "interval", spec: trimmed };
293
+ }
294
+ const fields = trimmed.split(/\s+/);
295
+ if (fields.length >= 5) {
296
+ return { kind: "cron", spec: trimmed };
297
+ }
298
+ return {
299
+ error: `Invalid schedule "${spec}". Use "5m", "+10m", ISO timestamp, or cron expression.`,
300
+ };
301
+ }
302
+
303
+ export function nextRunTime(spec: ScheduleSpec, from: Date = new Date()): Date | { error: string } {
304
+ if (spec.kind === "once") {
305
+ const rel = spec.spec.match(/^\+(\d+)(s|m|h|d)$/);
306
+ if (rel) {
307
+ const ms = parseInt(rel[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[rel[2] as "s" | "m" | "h" | "d"];
308
+ return new Date(from.getTime() + ms);
309
+ }
310
+ const d = new Date(spec.spec);
311
+ if (Number.isNaN(d.getTime())) {
312
+ return { error: `Invalid once schedule: ${spec.spec}` };
313
+ }
314
+ return d;
315
+ }
316
+ if (spec.kind === "interval") {
317
+ const ms = parseIntervalMs(spec.spec);
318
+ if (ms === undefined || ms <= 0) {
319
+ return { error: `Invalid interval: ${spec.spec}` };
320
+ }
321
+ return new Date(from.getTime() + ms);
322
+ }
323
+ if (spec.kind === "cron") {
324
+ const next = nextCronDate(spec.spec, from);
325
+ if (!next || !(next instanceof Date)) {
326
+ return (
327
+ (next as { error: string } | null) ?? {
328
+ error: "Invalid cron expression",
329
+ }
330
+ );
331
+ }
332
+ return next;
333
+ }
334
+ return {
335
+ error: `Unknown schedule kind: ${(spec as unknown as Record<string, unknown>).kind}`,
336
+ };
337
+ }
338
+
339
+ export function humanizeSchedule(spec: ScheduleSpec): string {
340
+ if (spec.kind === "once") {
341
+ if (/^\+\d+(s|m|h|d)$/.test(spec.spec)) {
342
+ return `once in ${spec.spec.slice(1)}`;
343
+ }
344
+ return `once at ${spec.spec}`;
345
+ }
346
+ if (spec.kind === "interval") {
347
+ return `every ${spec.spec}`;
348
+ }
349
+ if (spec.kind === "cron") {
350
+ return `cron ${spec.spec}`;
351
+ }
352
+ return "unknown schedule";
353
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Phase 6: Semaphore and fail-fast parallel execution.
3
+ *
4
+ * Adapted from oh-my-pi's `parallel.ts` Semaphore class and
5
+ * `mapWithConcurrencyLimit` implementation. Provides:
6
+ * - Explicit acquire/release Semaphore for concurrency control
7
+ * - Fail-fast on first error (via Promise.race)
8
+ * - AbortSignal support for graceful cancellation
9
+ * - Partial results on abort
10
+ */
11
+
12
+ /**
13
+ * Simple counting semaphore for limiting concurrency across independently-scheduled async work.
14
+ */
15
+ export class Semaphore {
16
+ #max: number;
17
+ #current = 0;
18
+ #queue: Array<() => void> = [];
19
+ // FIX (Round 15): Cap the waiter queue to prevent unbounded memory growth
20
+ // if the semaphore is held for a long period and many tasks accumulate.
21
+ static readonly MAX_QUEUE = 10_000;
22
+
23
+ constructor(max: number) {
24
+ this.#max = Math.max(1, max);
25
+ }
26
+
27
+ async acquire(): Promise<void> {
28
+ if (this.#current < this.#max) {
29
+ this.#current++;
30
+ return;
31
+ }
32
+ // FIX (Round 15): Reject when the waiter queue is full. The previous
33
+ // implementation let #queue grow without bound, risking memory
34
+ // exhaustion under sustained high concurrency with slow releases.
35
+ if (this.#queue.length >= Semaphore.MAX_QUEUE) {
36
+ throw new Error(`Semaphore queue full: ${this.#queue.length} waiters (max ${Semaphore.MAX_QUEUE}); cannot acquire slot`);
37
+ }
38
+ const { promise, resolve } = (() => {
39
+ let res: () => void;
40
+ const p = new Promise<void>((r) => {
41
+ res = r;
42
+ });
43
+ return { promise: p, resolve: res! };
44
+ })();
45
+ this.#queue.push(resolve);
46
+ return promise;
47
+ }
48
+
49
+ release(): void {
50
+ const next = this.#queue.shift();
51
+ if (next) {
52
+ next();
53
+ } else if (this.#current > 0) {
54
+ this.#current--;
55
+ }
56
+ // Guard: over-release is a no-op to prevent #current going negative
57
+ }
58
+
59
+ /** Current number of acquired slots. */
60
+ get current(): number {
61
+ return this.#current;
62
+ }
63
+
64
+ /** Number of waiters in the queue. */
65
+ get waiting(): number {
66
+ return this.#queue.length;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Result of parallel execution with fail-fast support.
72
+ */
73
+ export interface ParallelResult<R> {
74
+ /** Results array — undefined entries indicate tasks that were skipped due to abort. */
75
+ results: (R | undefined)[];
76
+ /** Whether execution was aborted before all tasks completed. */
77
+ aborted: boolean;
78
+ /** The first error that triggered fail-fast, if any. */
79
+ firstError?: unknown;
80
+ }
81
+
82
+ /**
83
+ * Execute items with a concurrency limit, fail-fast, and abort signal support.
84
+ *
85
+ * - On first error: aborts remaining workers and rethrows.
86
+ * - On external abort: returns partial results with `aborted: true`.
87
+ * - Results are returned in the same order as input items.
88
+ *
89
+ * Adapted from oh-my-pi's `mapWithConcurrencyLimit`.
90
+ */
91
+ /** @internal */
92
+ async function mapWithFailFast<T, R>(
93
+ items: T[],
94
+ concurrency: number,
95
+ fn: (item: T, index: number, signal: AbortSignal) => Promise<R>,
96
+ signal?: AbortSignal,
97
+ ): Promise<ParallelResult<R>> {
98
+ const limit = Math.max(1, Math.min(concurrency, items.length));
99
+ const results: (R | undefined)[] = new Array(items.length);
100
+ let nextIndex = 0;
101
+
102
+ // Internal abort controller for fail-fast
103
+ const abortController = new AbortController();
104
+ const workerSignal = signal ? AbortSignal.any([signal, abortController.signal]) : abortController.signal;
105
+
106
+ // Promise that rejects on first error — used for fail-fast
107
+ let rejectFirst: (error: unknown) => void;
108
+ const firstErrorPromise = new Promise<never>((_, reject) => {
109
+ rejectFirst = reject;
110
+ });
111
+
112
+ const worker = async (): Promise<void> => {
113
+ while (true) {
114
+ if (workerSignal.aborted) return;
115
+ const index = nextIndex++;
116
+ if (index >= items.length) return;
117
+ try {
118
+ results[index] = await fn(items[index], index, workerSignal);
119
+ } catch (error) {
120
+ if (!workerSignal.aborted) {
121
+ abortController.abort();
122
+ rejectFirst(error);
123
+ throw error;
124
+ }
125
+ }
126
+ }
127
+ };
128
+
129
+ const workers = Array.from({ length: limit }, () => worker());
130
+
131
+ try {
132
+ await Promise.race([Promise.all(workers), firstErrorPromise]);
133
+ } catch (error) {
134
+ if (signal?.aborted) {
135
+ return { results, aborted: true, firstError: error };
136
+ }
137
+ throw error;
138
+ }
139
+
140
+ return { results, aborted: signal?.aborted ?? false };
141
+ }