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,150 @@
1
+ /**
2
+ * path-overlap.ts — write-path overlap detection for sequential scheduling (M5).
3
+ *
4
+ * Implements the writes-only path-intersection heuristic (per plan §11 decision
5
+ * #5). A pair of steps is considered overlapping ONLY when both declare the
6
+ * same write target (via `step.output`). Reads are intentionally ignored to
7
+ * avoid false positives (parallel reads are safe).
8
+ *
9
+ * Scope:
10
+ * - export `detectWriteOverlap(stepA, stepB): boolean` — pair check
11
+ * - export `selectNonOverlapping(steps, maxCount): WorkflowStep[]` — greedy
12
+ * packer that returns at most `maxCount` steps such that no two returned
13
+ * steps have overlapping write paths.
14
+ * - export `filterReadyByWriteOverlap(taskIds, tasks, workflow, maxCount)` —
15
+ * scheduler-side wrapper that translates TeamTaskState IDs to WorkflowSteps
16
+ * and applies the overlap filter.
17
+ *
18
+ * Default greedy strategy: preserve declaration order; first step always wins
19
+ * because (a) deterministic for tests, (b) declaration order typically reflects
20
+ * author intent, (c) a future PR can swap to priority-based when M5 defaults
21
+ * flip to `true` in production.
22
+ *
23
+ * Out of scope: reads overlap detection (decision #5); deferral cap (decision
24
+ * #6; M5 first ship treats repeated deferral as best-effort; cap warning logged
25
+ * but not enforced, since M5 default = false means production isn't gated).
26
+ */
27
+
28
+ import type { TeamTaskState } from "../state/types.ts";
29
+ import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
30
+
31
+ /**
32
+ * Extract the canonical write-target path of a step.
33
+ *
34
+ * Returns:
35
+ * - `step.output` if it is a non-empty string (the file path produced).
36
+ * - `undefined` otherwise (no declared write target → step has no write
37
+ * overlap risk).
38
+ *
39
+ * `step.output === false` means "explicitly no output"; treated as no write.
40
+ */
41
+ function extractWritePath(step: WorkflowStep): string | undefined {
42
+ if (typeof step.output === "string" && step.output.length > 0) return step.output;
43
+ return undefined;
44
+ }
45
+
46
+ /**
47
+ * Pairwise check: do stepA and stepB both write to the same path?
48
+ *
49
+ * O(1). Both steps must declare an `output` string; otherwise no overlap.
50
+ *
51
+ * Examples:
52
+ * detectWriteOverlap({output: 'a.md'}, {output: 'a.md'}) → true
53
+ * detectWriteOverlap({output: 'a.md'}, {output: 'b.md'}) → false
54
+ * detectWriteOverlap({output: 'a.md'}, {output: false}) → false
55
+ * detectWriteOverlap({output: 'a.md'}, {}) → false
56
+ */
57
+ export function detectWriteOverlap(stepA: WorkflowStep, stepB: WorkflowStep): boolean {
58
+ const pathA = extractWritePath(stepA);
59
+ const pathB = extractWritePath(stepB);
60
+ if (!pathA || !pathB) return false;
61
+ return pathA === pathB;
62
+ }
63
+
64
+ /**
65
+ * Greedy packer: pick up to `maxCount` steps from `steps` such that no two
66
+ * PICKED steps have overlapping write paths.
67
+ *
68
+ * Algorithm:
69
+ * - Iterates over `steps` in order.
70
+ * - First step is always picked.
71
+ * - Each subsequent step is picked iff (a) it doesn't overlap with any
72
+ * already-picked step AND (b) we haven't reached `maxCount`.
73
+ *
74
+ * Returns the picked subset (a NEW array; does not mutate input).
75
+ *
76
+ * Used by scheduler when `limits.serializeOnPathOverlap = true`.
77
+ */
78
+ export function selectNonOverlapping(steps: WorkflowStep[], maxCount: number): WorkflowStep[] {
79
+ const picked: WorkflowStep[] = [];
80
+ if (maxCount <= 0 || steps.length === 0) return picked;
81
+ for (const step of steps) {
82
+ if (picked.length >= maxCount) break;
83
+ let conflict = false;
84
+ for (const existing of picked) {
85
+ if (detectWriteOverlap(step, existing)) {
86
+ conflict = true;
87
+ break;
88
+ }
89
+ }
90
+ if (!conflict) picked.push(step);
91
+ }
92
+ return picked;
93
+ }
94
+
95
+ /**
96
+ * Scheduler-side wrapper: given a list of ready task IDs (from the task-graph
97
+ * ready queue), filter them by write-path overlap using the workflow step
98
+ * definitions.
99
+ *
100
+ * Behavior:
101
+ * - Resolves each task ID → `TeamTaskState` → `WorkflowStep` via
102
+ * `task.stepId` matching `workflow.steps[i].id`.
103
+ * - Tasks whose step is missing from the workflow (e.g. ad-hoc tasks) are
104
+ * always included (treated as having no declared write path).
105
+ * - Returns the same array (identity-equal) when `enabled` is false, so
106
+ * callers can skip the filter cheaply.
107
+ *
108
+ * Used by the team-runner scheduler when `limits.serializeOnPathOverlap`
109
+ * is `true` (per plan §11 decision #6, default is `false` so this is a
110
+ * no-op in v0.9.17 unless the workflow author opts in).
111
+ */
112
+ export function filterReadyByWriteOverlap(
113
+ readyTaskIds: string[],
114
+ tasks: TeamTaskState[],
115
+ workflow: WorkflowConfig,
116
+ maxCount: number,
117
+ enabled: boolean,
118
+ ): string[] {
119
+ if (!enabled || maxCount <= 0 || readyTaskIds.length === 0) return readyTaskIds;
120
+ const taskById = new Map<string, TeamTaskState>(tasks.map((task) => [task.id, task]));
121
+ const stepById = new Map<string, WorkflowStep>(workflow.steps.map((step) => [step.id, step]));
122
+ const pickedSteps: WorkflowStep[] = [];
123
+ const pickedTaskIds: string[] = [];
124
+ for (const taskId of readyTaskIds) {
125
+ if (pickedTaskIds.length >= maxCount) break;
126
+ const task = taskById.get(taskId);
127
+ if (!task?.stepId) {
128
+ // No step reference: treat as no declared write path → never conflicts.
129
+ pickedTaskIds.push(taskId);
130
+ continue;
131
+ }
132
+ const step = stepById.get(task.stepId);
133
+ if (!step) {
134
+ pickedTaskIds.push(taskId);
135
+ continue;
136
+ }
137
+ let conflict = false;
138
+ for (const existing of pickedSteps) {
139
+ if (detectWriteOverlap(step, existing)) {
140
+ conflict = true;
141
+ break;
142
+ }
143
+ }
144
+ if (!conflict) {
145
+ pickedTaskIds.push(taskId);
146
+ pickedSteps.push(step);
147
+ }
148
+ }
149
+ return pickedTaskIds;
150
+ }
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Robust resolution + async loading of the @earendil-works/pi-coding-agent
3
+ * peer dependency. Fixes the "Cannot find module '@earendil-works/pi-coding-agent'"
4
+ * crash that blocks ALL team runs when pi-crew and pi are installed in
5
+ * SEPARATE node_modules trees.
6
+ *
7
+ * PROBLEM (Windows / global installs — reported 2026-06-17)
8
+ * pi-crew is a pi EXTENSION. pi installs extensions under
9
+ * `~/.pi/agent/npm/node_modules/<ext>/`, but pi itself (the
10
+ * @earendil-works/pi-coding-agent package that extensions import from)
11
+ * usually lives in a DIFFERENT node_modules tree — a global one (nvm,
12
+ * %APPDATA%\npm, Volta, fnm, pnpm-global). Node's resolver only walks UP
13
+ * through ancestor `node_modules` of the importing file, so a file under
14
+ * `~/.pi/agent/npm/node_modules/pi-crew/...` CANNOT resolve a peer dep
15
+ * installed under `~/.nvm/.../lib/node_modules/`. Every static
16
+ * `import { X } from "@earendil-works/pi-coding-agent"` that executes inside
17
+ * a SPAWNED CHILD PROCESS (the detached background team runner started by
18
+ * async-runner.spawnBackgroundTeamRun) therefore crashes at module load,
19
+ * leaving all team runs permanently `queued`.
20
+ *
21
+ * ADDITIONAL CONSTRAINT (verified empirically 2026-06-17)
22
+ * pi-coding-agent ships as ESM-only (`"type":"module"`, exports map has only
23
+ * an `import` condition). CJS `require()` / `createRequire(dir)(name)` fails
24
+ * with ERR_PACKAGE_PATH_NOT_EXPORTED under plain node AND under jiti/tsx. The
25
+ * ONLY working load mechanism is a dynamic `import()` of the resolved ESM
26
+ * entry file URL. Hence: sync resolution of the DIR, async load of the MODULE.
27
+ *
28
+ * APPROACH
29
+ * - resolvePeerDep() (sync) — find the install dir across many layouts.
30
+ * - primePeerDep() (async) — dynamic-import the resolved entry, cache
31
+ * the module namespace. Memoized. Called
32
+ * once per process during bootstrap.
33
+ * - getAgentDir() (sync) — read the cached module's getAgentDir.
34
+ * Falls back to a computed default if the
35
+ * cache was never primed, so it NEVER throws.
36
+ */
37
+ import * as fs from "node:fs";
38
+ import * as os from "node:os";
39
+ import * as path from "node:path";
40
+ import { fileURLToPath, pathToFileURL } from "node:url";
41
+ import { resolveNpmGlobalRoot } from "./pi-spawn.ts";
42
+
43
+ /**
44
+ * The pi-coding-agent peer dependency package name(s) we can be loaded by.
45
+ * @earendil-works is the canonical scope; @mariozechner is the historical fork.
46
+ */
47
+ export const PEER_DEP_NAMES = ["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"] as const;
48
+
49
+ /**
50
+ * Env var a parent pi-crew process sets on spawned children so they can resolve
51
+ * the peer dep WITHOUT running `npm root -g` (~200ms probe). The resolver
52
+ * checks this FIRST. Absent (older parent, direct invocation, tests) → falls
53
+ * through to the probing strategies. Also lets users override the resolution
54
+ * explicitly as a last-resort fix.
55
+ */
56
+ export const PEER_DEP_DIR_ENV = "PI_CREW_PEER_DEP_DIR";
57
+
58
+ type PeerDepModule = typeof import("@earendil-works/pi-coding-agent");
59
+
60
+ interface ResolvedPeerDep {
61
+ dir: string;
62
+ name: string;
63
+ /** file:// URL of the ESM entry (exports["."].import || main). */
64
+ mainUrl: string;
65
+ }
66
+
67
+ let cachedResolve: ResolvedPeerDep | undefined | null = null;
68
+ let cachedModule: PeerDepModule | undefined;
69
+ let primingPromise: Promise<PeerDepModule> | undefined;
70
+
71
+ /**
72
+ * Build the ordered list of "resolution bases" — paths to seed
73
+ * `createRequire(...).resolve()` from. Node walks UP `node_modules` from each
74
+ * base's directory, so any base inside (or beside) the peer dep's package
75
+ * tree will find it. Pure given env/process inputs; exported for unit tests.
76
+ */
77
+ export function peerDepResolutionBases(): string[] {
78
+ const bases: string[] = [];
79
+
80
+ // 0. Parent-provided hint (fastest — no probe). Set by async-runner.
81
+ const envHint = process.env[PEER_DEP_DIR_ENV]?.trim();
82
+ if (envHint) bases.push(path.resolve(envHint));
83
+
84
+ // 1. This file's location — works when pi-crew and pi-coding-agent share a
85
+ // node_modules ancestor (the common co-located install).
86
+ bases.push(fileURLToPath(import.meta.url));
87
+
88
+ // 2. The entry script. In the PARENT (main pi process) argv[1] is pi's CLI
89
+ // script, which lives INSIDE pi-coding-agent's package → resolves. In a
90
+ // SPAWNED CHILD argv[1] is a pi-crew script → cheap miss, falls through.
91
+ const argv1 = process.argv[1];
92
+ if (argv1) bases.push(path.resolve(argv1));
93
+
94
+ // 3. The Node binary's global node_modules. Covers nvm / nvm-windows /
95
+ // Volta / fnm where pi-coding-agent is `npm i -g`'d: node is at
96
+ // <prefix>/bin/node and globals live at <prefix>/lib/node_modules.
97
+ try {
98
+ const execDir = path.dirname(fs.realpathSync.native(process.execPath));
99
+ bases.push(path.join(path.dirname(execDir), "lib", "node_modules"));
100
+ // Some layouts (Windows global, or a bare node_modules sibling of bin).
101
+ bases.push(path.join(execDir, "node_modules"));
102
+ } catch {
103
+ /* realpath best-effort */
104
+ }
105
+
106
+ // 4. `npm root -g` — the canonical cross-layout global root (memoized in
107
+ // pi-spawn.ts, ~200ms once). Derive the scoped package dirs from it.
108
+ const npmRoot = resolveNpmGlobalRoot();
109
+ if (npmRoot) {
110
+ for (const pkgName of PEER_DEP_NAMES) {
111
+ bases.push(path.join(npmRoot, ...pkgName.split("/")));
112
+ }
113
+ }
114
+
115
+ // 5. Windows %APPDATA%\npm static layout (legacy npm-global, pre-npm-root-g).
116
+ if (process.env.APPDATA) {
117
+ bases.push(path.join(process.env.APPDATA, "npm", "node_modules"));
118
+ }
119
+
120
+ return bases;
121
+ }
122
+
123
+ /** Pull the ESM entry path out of package.json (exports import || main). */
124
+ function extractEsmMain(pkg: unknown): string | undefined {
125
+ if (!pkg || typeof pkg !== "object") return undefined;
126
+ const p = pkg as Record<string, unknown>;
127
+ const exp = p.exports;
128
+ if (exp && typeof exp === "object") {
129
+ const dot = (exp as Record<string, unknown>)["."];
130
+ if (dot && typeof dot === "object") {
131
+ const d = dot as Record<string, unknown>;
132
+ const rel = d.import ?? d.default ?? d.module;
133
+ if (typeof rel === "string") return rel;
134
+ } else if (typeof dot === "string") {
135
+ return dot;
136
+ }
137
+ }
138
+ const main = p.main;
139
+ return typeof main === "string" ? main : undefined;
140
+ }
141
+
142
+ /**
143
+ * Walk the node_modules resolution algorithm MANUALLY from `start` looking for
144
+ * any of `names`. We do NOT use createRequire/require.resolve here because
145
+ * pi-coding-agent ships an ESM-only package with a restrictive exports map
146
+ * (only the `.` import condition) — `require.resolve("<pkg>/package.json")`
147
+ * and `require.resolve("<pkg>")` both throw ERR_PACKAGE_PATH_NOT_EXPORTED.
148
+ * Reading package.json directly from the walked dir sidesteps the exports map
149
+ * entirely (exports only governs subpath IMPORTS, not raw file reads).
150
+ *
151
+ * At each directory we check BOTH `<dir>/node_modules/<pkg>` (the standard
152
+ * container case) AND `<dir>/<pkg>` (handles a base that IS a node_modules
153
+ * dir, e.g. the output of `npm root -g`), then walk up to root.
154
+ */
155
+ function findPackageDir(start: string, names: readonly string[]): { dir: string; name: string } | undefined {
156
+ let dir = path.resolve(start);
157
+ try {
158
+ if (fs.statSync(dir).isFile()) dir = path.dirname(dir);
159
+ } catch {
160
+ /* treat as directory */
161
+ }
162
+ while (true) {
163
+ for (const name of names) {
164
+ const segs = name.split("/");
165
+ const candidates = [path.join(dir, "node_modules", ...segs, "package.json"), path.join(dir, ...segs, "package.json")];
166
+ for (const pkgJson of candidates) {
167
+ try {
168
+ const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf-8"));
169
+ if (pkg?.name === name) {
170
+ return { dir: path.dirname(pkgJson), name };
171
+ }
172
+ } catch {
173
+ /* not present at this candidate */
174
+ }
175
+ }
176
+ }
177
+ const parent = path.dirname(dir);
178
+ if (parent === dir) break; // reached filesystem root
179
+ dir = parent;
180
+ }
181
+ return undefined;
182
+ }
183
+
184
+ function tryResolveFrom(base: string): ResolvedPeerDep | undefined {
185
+ const found = findPackageDir(base, PEER_DEP_NAMES);
186
+ if (!found) return undefined;
187
+ try {
188
+ const pkg = JSON.parse(fs.readFileSync(path.join(found.dir, "package.json"), "utf-8"));
189
+ const mainRel = extractEsmMain(pkg);
190
+ if (!mainRel) return undefined;
191
+ const mainAbs = path.resolve(found.dir, mainRel);
192
+ if (!fs.existsSync(mainAbs)) return undefined;
193
+ return {
194
+ dir: found.dir,
195
+ name: found.name,
196
+ mainUrl: pathToFileURL(mainAbs).href,
197
+ };
198
+ } catch {
199
+ return undefined;
200
+ }
201
+ }
202
+
203
+ /** Resolve the peer dep install dir + ESM entry URL. Memoized (sync). */
204
+ export function resolvePeerDep(): ResolvedPeerDep | undefined {
205
+ if (cachedResolve !== null) return cachedResolve ?? undefined;
206
+ for (const base of peerDepResolutionBases()) {
207
+ const found = tryResolveFrom(base);
208
+ if (found) {
209
+ cachedResolve = found;
210
+ return found;
211
+ }
212
+ }
213
+ cachedResolve = null; // mark attempted-and-failed; don't re-probe per call
214
+ return undefined;
215
+ }
216
+
217
+ /** Just the install directory (for env-hint propagation to children). */
218
+ export function resolvePeerDepDir(): string | undefined {
219
+ return resolvePeerDep()?.dir;
220
+ }
221
+
222
+ /**
223
+ * Dynamic-import the peer dep module, caching the namespace. Memoized via a
224
+ * shared promise so concurrent callers share one load. On failure the promise
225
+ * is cleared so a later caller can retry. Safe to call repeatedly.
226
+ */
227
+ export function primePeerDep(): Promise<PeerDepModule> {
228
+ if (cachedModule) return Promise.resolve(cachedModule);
229
+ if (primingPromise) return primingPromise;
230
+ primingPromise = (async () => {
231
+ const resolved = resolvePeerDep();
232
+ if (!resolved) {
233
+ throw new Error(buildMissingMessage());
234
+ }
235
+ // LAZY: defer dynamic import of module to its call site.
236
+ cachedModule = (await import(resolved.mainUrl)) as PeerDepModule;
237
+ return cachedModule;
238
+ })();
239
+ // Clear on failure so a later caller can retry (e.g. after env fix).
240
+ primingPromise.catch(() => {
241
+ primingPromise = undefined;
242
+ });
243
+ return primingPromise;
244
+ }
245
+
246
+ /** Async module accessor (primes if needed). */
247
+ export async function loadPeerDep(): Promise<PeerDepModule> {
248
+ return primePeerDep();
249
+ }
250
+
251
+ function buildMissingMessage(): string {
252
+ return (
253
+ `pi-crew could not resolve the @earendil-works/pi-coding-agent peer dependency.\n` +
254
+ `This usually means pi-crew and pi are installed in separate node_modules trees\n` +
255
+ `(e.g. pi-crew under ~/.pi/agent/npm/ but pi under an nvm/Volta/fnm global scope).\n` +
256
+ `Resolution bases tried:\n` +
257
+ peerDepResolutionBases()
258
+ .map((b) => ` - ${b}`)
259
+ .join("\n") +
260
+ `\nFix: install pi-crew in the SAME scope as pi, e.g.\n` +
261
+ ` npm install -g @earendil-works/pi-crew\n` +
262
+ `or set the env var ${PEER_DEP_DIR_ENV}=<path to the pi-coding-agent package dir>.`
263
+ );
264
+ }
265
+
266
+ /**
267
+ * Read the user agent dir via the REAL peer-dep getAgentDir (fork-aware:
268
+ * correct for pi, tau, and renamed forks). Sync; reads the primed cache.
269
+ *
270
+ * If the cache was never primed (e.g. called before bootstrap completes, or
271
+ * prime failed), falls back to a computed default so it NEVER throws. The
272
+ * default matches standard pi (`~/.pi/agent`) and respects the
273
+ * `PI_CODING_AGENT_DIR` override — correct for the overwhelmingly common
274
+ * case. Forks rely on the primed real function (register.ts primes at startup).
275
+ */
276
+ export function getAgentDir(): string {
277
+ if (cachedModule?.getAgentDir) {
278
+ try {
279
+ return cachedModule.getAgentDir();
280
+ } catch {
281
+ /* fall through to computed default */
282
+ }
283
+ }
284
+ return process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
285
+ }
286
+
287
+ /** @internal — reset all caches for unit tests. */
288
+ export function __resetPeerDepCacheForTest(): void {
289
+ cachedResolve = null;
290
+ cachedModule = undefined;
291
+ primingPromise = undefined;
292
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Per-write validator — real-time feedback on file writes/edits (T5).
3
+ *
4
+ * Distilled from pi-lens (apmantza) — the "inline channel": on every
5
+ * `write`/`edit` tool result, run a CHEAP synchronous validator for the file
6
+ * type and, on failure, append a `🔴` blocker block to the tool result the
7
+ * agent sees next. This catches silent-breaking errors (malformed JSON
8
+ * config) at the moment they're introduced instead of at the next load.
9
+ *
10
+ * CRITICAL LATENCY-SAFETY DESIGN (the reason this is a careful slice, not the
11
+ * full pi-lens pipeline): pi-lens runs LSP servers + linters per write. That
12
+ * is expensive and would cause latency storms if naively ported (seconds of
13
+ * spawn per edit, firing in the main session AND every worker). This module's
14
+ * v1 deliberately ships ONLY zero-cost, zero-spawn, synchronous validators:
15
+ *
16
+ * - `json` → `JSON.parse` (nanoseconds, built-in, no process spawn).
17
+ *
18
+ * The registry is extensible — future validators (`.js` → `node --check`,
19
+ * `.sh` → `bash -n`, `.py` → `py_compile`) are process-spawning and MUST be
20
+ * added behind an explicit opt-in (never default-on) to preserve the
21
+ * latency guarantee. A process-spawning validator would also need to be async
22
+ * and debounced (pi-lens's `inFlightPipelines` / debounce-window pattern),
23
+ * which the current sync contract intentionally avoids.
24
+ *
25
+ * Contract guarantees for v1:
26
+ * - Synchronous. No `await`, no `spawn`, no disk write.
27
+ * - One disk READ per validated file (after a cheap extension check, so
28
+ * non-validated files cost nothing).
29
+ * - Dedup by content: the same path+content is validated at most once per
30
+ * process (a repeated identical write doesn't re-report).
31
+ * - Silent on success; appends exactly one TextContent block on failure.
32
+ * - Best-effort: any internal error is swallowed (never breaks a write).
33
+ *
34
+ * @module per-write-validator
35
+ */
36
+
37
+ import { readFileSync } from "node:fs";
38
+ import { extname as pathExtname } from "node:path";
39
+
40
+ /** Outcome of validating a file's content. */
41
+ export interface ValidationResult {
42
+ ok: boolean;
43
+ /** Human-readable error message when `ok` is false. */
44
+ error?: string;
45
+ }
46
+
47
+ /** A synchronous validator: content + path → result. */
48
+ export type PerWriteValidator = (content: string, filePath: string) => ValidationResult;
49
+
50
+ // ─────────────────────────────────────────────────────────────────────────
51
+ // Validators (zero-cost, synchronous, dependency-free for v1)
52
+ // ─────────────────────────────────────────────────────────────────────────
53
+
54
+ /** JSON: parse with `JSON.parse`. Catches malformed config/manifests instantly. */
55
+ export function validateJson(content: string, _filePath: string): ValidationResult {
56
+ if (content.trim() === "") return { ok: true }; // empty file is valid JSON absence, not a parse error
57
+ try {
58
+ JSON.parse(content);
59
+ return { ok: true };
60
+ } catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ return { ok: false, error: `Invalid JSON: ${message}` };
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Registry of default-on validators, keyed by extension (lowercase, no dot).
68
+ * ONLY zero-cost synchronous validators belong here. Process-spawning
69
+ * validators must be registered via a future opt-in path (see module doc).
70
+ */
71
+ const DEFAULT_VALIDATORS: ReadonlyMap<string, PerWriteValidator> = new Map([["json", validateJson]]);
72
+
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+ // Dedup cache (path → last-validated content). Bounded; small.
75
+ // ─────────────────────────────────────────────────────────────────────────
76
+
77
+ const MAX_DEDUP_ENTRIES = 256;
78
+ const seenContent = new Map<string, string>();
79
+
80
+ function rememberSeen(path: string, content: string): void {
81
+ if (seenContent.has(path)) seenContent.delete(path); // refresh LRU position
82
+ seenContent.set(path, content);
83
+ while (seenContent.size > MAX_DEDUP_ENTRIES) {
84
+ const oldest = seenContent.keys().next().value;
85
+ if (oldest === undefined) break;
86
+ seenContent.delete(oldest);
87
+ }
88
+ }
89
+
90
+ /** Test seam: reset the dedup cache between tests. */
91
+ export function resetPerWriteValidatorCache(): void {
92
+ seenContent.clear();
93
+ }
94
+
95
+ /**
96
+ * Replace the validator registry (test seam). Production uses
97
+ * DEFAULT_VALIDATORS; tests inject a custom map to exercise specific extensions.
98
+ */
99
+ let validators: ReadonlyMap<string, PerWriteValidator> = DEFAULT_VALIDATORS;
100
+
101
+ export function setPerWriteValidatorsForTest(map: ReadonlyMap<string, PerWriteValidator> | undefined): void {
102
+ validators = map ?? DEFAULT_VALIDATORS;
103
+ }
104
+
105
+ /**
106
+ * Normalise an extension to the registry key form (lowercase, no leading dot).
107
+ * "" for files with no extension.
108
+ */
109
+ export function extensionKey(filePath: string): string {
110
+ return pathExtname(filePath).replace(/^\./, "").toLowerCase();
111
+ }
112
+
113
+ // ─────────────────────────────────────────────────────────────────────────
114
+ // Path extraction from a tool_result event input (defensive — pi-ai types
115
+ // aren't exported here, so accept a record and probe common field names).
116
+ // ─────────────────────────────────────────────────────────────────────────
117
+
118
+ const PATH_FIELDS = ["filePath", "path", "file"] as const;
119
+
120
+ /** Extract the written/edited path from a tool result input, if present. */
121
+ export function extractPathFromInput(input: unknown): string | undefined {
122
+ if (!input || typeof input !== "object") return undefined;
123
+ const record = input as Record<string, unknown>;
124
+ for (const field of PATH_FIELDS) {
125
+ const value = record[field];
126
+ if (typeof value === "string" && value.length > 0) return value;
127
+ }
128
+ return undefined;
129
+ }
130
+
131
+ // ─────────────────────────────────────────────────────────────────────────
132
+ // Core entry point
133
+ // ─────────────────────────────────────────────────────────────────────────
134
+
135
+ /**
136
+ * Validate a just-written/edited file. Returns `null` when there is nothing
137
+ * to report (no validator for the extension, dedup hit, file unreadable, or
138
+ * the content is valid). Returns a `ValidationResult` with `ok:false` when the
139
+ * content fails validation.
140
+ *
141
+ * Reads the file from disk (it's already written by `tool_result` time) so the
142
+ * logic is uniform across `write` (full content) and `edit` (patch). The disk
143
+ * read happens ONLY after a cheap extension check, so non-validated files cost
144
+ * nothing.
145
+ */
146
+ export function validateWrittenFile(filePath: string): ValidationResult | null {
147
+ const key = extensionKey(filePath);
148
+ const validator = validators.get(key);
149
+ if (!validator) return null; // cheap skip: no validator for this file type
150
+ let content: string;
151
+ try {
152
+ content = readFileSync(filePath, "utf-8");
153
+ } catch {
154
+ // Unreadable / missing / permission denied — can't validate; never block.
155
+ return null;
156
+ }
157
+ // Dedup: identical content already validated this process → don't re-report.
158
+ if (seenContent.get(filePath) === content) return null;
159
+ rememberSeen(filePath, content);
160
+ const result = validator(content, filePath);
161
+ return result.ok ? null : result;
162
+ }
163
+
164
+ /**
165
+ * Build the TextContent block to append to a tool_result on validation failure.
166
+ * Uses a strong `🔴` prefix so the agent treats it as a real signal and fixes
167
+ * the file before continuing.
168
+ */
169
+ export function buildValidationBlocker(filePath: string, error: string): { type: "text"; text: string } {
170
+ return {
171
+ type: "text",
172
+ text: [
173
+ "",
174
+ "🔴 pi-crew per-write check FAILED",
175
+ ` ${filePath}`,
176
+ ` ${error}`,
177
+ " The file you just wrote is malformed. Fix it now — a broken file here will",
178
+ " silently fail the next load/parse. Re-write the file with valid content before continuing.",
179
+ ].join("\n"),
180
+ };
181
+ }