pi-crew 0.9.16 → 0.9.17

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 (432) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +19 -0
  3. package/dist/build-meta.json +21790 -0
  4. package/dist/index.mjs +83070 -0
  5. package/dist/index.mjs.map +7 -0
  6. package/docs/A +358 -0
  7. package/docs/M-A +357 -0
  8. package/docs/REVIEW-FINDINGS-2026-06 +357 -0
  9. package/docs/Y +357 -0
  10. package/docs/a +357 -0
  11. package/docs/aA +358 -0
  12. package/docs/archive/README.md +91 -0
  13. package/docs/patterns/command-agent-skill.md +1 -1
  14. package/docs/skills/REFERENCE.md +0 -17
  15. package/docs//303/242mmaAAA/303/242 +357 -0
  16. package/index.bundle.ts +25 -0
  17. package/index.ts +95 -4
  18. package/package.json +15 -4
  19. package/skills/iterative-audit/SKILL.md +0 -1
  20. package/skills/widget-rendering/SKILL.md +17 -0
  21. package/src/adapters/claude-adapter.ts +1 -3
  22. package/src/adapters/export-util.ts +16 -16
  23. package/src/adapters/index.ts +5 -5
  24. package/src/agents/agent-config.ts +20 -14
  25. package/src/agents/agent-serializer.ts +1 -1
  26. package/src/agents/discover-agents.ts +83 -39
  27. package/src/benchmark/benchmark-runner.ts +51 -51
  28. package/src/benchmark/feedback-loop.ts +1 -1
  29. package/src/config/config.ts +166 -590
  30. package/src/config/defaults.ts +28 -2
  31. package/src/config/drift-detector.ts +11 -14
  32. package/src/config/markers.ts +201 -203
  33. package/src/config/resilient-parser.ts +14 -6
  34. package/src/config/role-tools.ts +3 -3
  35. package/src/config/suggestions.ts +5 -12
  36. package/src/config/types.ts +9 -25
  37. package/src/errors.ts +151 -157
  38. package/src/extension/action-suggestions.ts +54 -9
  39. package/src/extension/async-notifier.ts +43 -13
  40. package/src/extension/autonomous-policy.ts +77 -37
  41. package/src/extension/command-completions.ts +9 -8
  42. package/src/extension/context-status-injection.ts +14 -12
  43. package/src/extension/crew-autocomplete.ts +8 -16
  44. package/src/extension/crew-cleanup.ts +2 -1
  45. package/src/extension/crew-shortcuts.ts +9 -6
  46. package/src/extension/cross-extension-rpc.ts +136 -42
  47. package/src/extension/help.ts +1 -1
  48. package/src/extension/import-index.ts +10 -5
  49. package/src/extension/knowledge-injection.ts +88 -15
  50. package/src/extension/management.ts +89 -349
  51. package/src/extension/message-renderers.ts +3 -4
  52. package/src/extension/notification-router.ts +22 -5
  53. package/src/extension/notification-sink.ts +6 -3
  54. package/src/extension/pi-api.ts +17 -8
  55. package/src/extension/plan-orchestrate.ts +8 -27
  56. package/src/extension/project-init.ts +21 -6
  57. package/src/extension/register.ts +329 -975
  58. package/src/extension/registration/artifact-cleanup.ts +9 -4
  59. package/src/extension/registration/command-utils.ts +5 -1
  60. package/src/extension/registration/commands.ts +806 -314
  61. package/src/extension/registration/compaction-guard.ts +15 -15
  62. package/src/extension/registration/lifecycle.ts +259 -0
  63. package/src/extension/registration/observability.ts +293 -0
  64. package/src/extension/registration/subagent-helpers.ts +29 -7
  65. package/src/extension/registration/subagent-tools.ts +253 -44
  66. package/src/extension/registration/team-tool.ts +19 -84
  67. package/src/extension/registration/ui.ts +168 -0
  68. package/src/extension/registration/viewers.ts +64 -24
  69. package/src/extension/result-watcher.ts +18 -7
  70. package/src/extension/run-bundle-schema.ts +21 -6
  71. package/src/extension/run-export.ts +15 -6
  72. package/src/extension/run-import.ts +64 -29
  73. package/src/extension/run-index.ts +28 -9
  74. package/src/extension/run-maintenance.ts +32 -12
  75. package/src/extension/session-summary.ts +1 -1
  76. package/src/extension/team-manager-command.ts +64 -7
  77. package/src/extension/team-onboard.ts +151 -150
  78. package/src/extension/team-recommendation.ts +121 -21
  79. package/src/extension/team-tool/anchor.ts +31 -64
  80. package/src/extension/team-tool/api.ts +929 -141
  81. package/src/extension/team-tool/auto-summarize.ts +14 -26
  82. package/src/extension/team-tool/cache-control.ts +1 -5
  83. package/src/extension/team-tool/cancel.ts +138 -38
  84. package/src/extension/team-tool/chain-dispatch.ts +5 -13
  85. package/src/extension/team-tool/chain-executor.ts +29 -51
  86. package/src/extension/team-tool/config-patch.ts +1 -1
  87. package/src/extension/team-tool/context.ts +40 -20
  88. package/src/extension/team-tool/destructive-gate.ts +10 -5
  89. package/src/extension/team-tool/doctor.ts +170 -51
  90. package/src/extension/team-tool/explain.ts +228 -214
  91. package/src/extension/team-tool/failure-patterns.ts +3 -9
  92. package/src/extension/team-tool/goal-wrap.ts +71 -29
  93. package/src/extension/team-tool/goal.ts +185 -35
  94. package/src/extension/team-tool/handle-schedule.ts +34 -15
  95. package/src/extension/team-tool/handle-settings.ts +94 -56
  96. package/src/extension/team-tool/health-monitor.ts +27 -108
  97. package/src/extension/team-tool/inspect.ts +42 -9
  98. package/src/extension/team-tool/intent-policy.ts +4 -1
  99. package/src/extension/team-tool/lifecycle-actions.ts +244 -48
  100. package/src/extension/team-tool/orchestrate.ts +7 -18
  101. package/src/extension/team-tool/parallel-dispatch.ts +51 -29
  102. package/src/extension/team-tool/plan.ts +29 -5
  103. package/src/extension/team-tool/respond.ts +49 -16
  104. package/src/extension/team-tool/run-not-found.ts +3 -8
  105. package/src/extension/team-tool/run.ts +80 -317
  106. package/src/extension/team-tool/status.ts +135 -43
  107. package/src/extension/team-tool/workflow-manage.ts +92 -25
  108. package/src/extension/team-tool-types.ts +8 -1
  109. package/src/extension/team-tool.ts +142 -551
  110. package/src/extension/validate-resources.ts +39 -8
  111. package/src/hooks/registry.ts +66 -17
  112. package/src/hooks/types.ts +1 -1
  113. package/src/i18n.ts +25 -11
  114. package/src/observability/correlation.ts +17 -3
  115. package/src/observability/event-bus.ts +51 -56
  116. package/src/observability/event-to-metric.ts +117 -15
  117. package/src/observability/exporters/otlp-exporter.ts +56 -17
  118. package/src/observability/exporters/prometheus-exporter.ts +1 -1
  119. package/src/observability/metric-registry.ts +16 -4
  120. package/src/observability/metric-retention.ts +8 -2
  121. package/src/observability/metric-sink.ts +11 -3
  122. package/src/observability/metrics-primitives.ts +42 -9
  123. package/src/plugins/plugin-define.ts +2 -2
  124. package/src/plugins/plugin-registry.ts +25 -25
  125. package/src/plugins/plugins/index.ts +1 -1
  126. package/src/plugins/plugins/nextjs.ts +16 -16
  127. package/src/plugins/plugins/vite.ts +7 -11
  128. package/src/plugins/plugins/vitest.ts +6 -11
  129. package/src/runtime/adaptive-plan.ts +225 -40
  130. package/src/runtime/agent-control.ts +58 -15
  131. package/src/runtime/agent-memory.ts +15 -9
  132. package/src/runtime/agent-observability.ts +10 -2
  133. package/src/runtime/anchor-manager.ts +27 -25
  134. package/src/runtime/async-marker.ts +7 -1
  135. package/src/runtime/async-runner.ts +27 -23
  136. package/src/runtime/auto-summarize.ts +7 -13
  137. package/src/runtime/background-runner.ts +189 -251
  138. package/src/runtime/batch-barrier.ts +18 -16
  139. package/src/runtime/cancellation-token.ts +16 -6
  140. package/src/runtime/cancellation.ts +46 -8
  141. package/src/runtime/capability-inventory.ts +6 -4
  142. package/src/runtime/chain-parser.ts +44 -11
  143. package/src/runtime/chain-runner.ts +63 -69
  144. package/src/runtime/checkpoint.ts +12 -56
  145. package/src/runtime/child-pi.ts +343 -78
  146. package/src/runtime/coalesce-tasks.ts +268 -0
  147. package/src/runtime/code-summary.ts +71 -26
  148. package/src/runtime/compact-stages/index.ts +15 -5
  149. package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
  150. package/src/runtime/compact-stages/truncation-stage.ts +4 -1
  151. package/src/runtime/compaction-summary.ts +17 -10
  152. package/src/runtime/completion-guard.ts +33 -16
  153. package/src/runtime/crash-classification.ts +28 -7
  154. package/src/runtime/crash-recovery.ts +177 -37
  155. package/src/runtime/crew-agent-records.ts +145 -47
  156. package/src/runtime/crew-hooks.ts +9 -24
  157. package/src/runtime/cross-extension-rpc.ts +25 -23
  158. package/src/runtime/custom-tools/irc-tool.ts +43 -15
  159. package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
  160. package/src/runtime/delivery-coordinator.ts +34 -7
  161. package/src/runtime/delta-conflict.ts +9 -21
  162. package/src/runtime/deterministic-ast.ts +1 -1
  163. package/src/runtime/diagnostic-export.ts +53 -20
  164. package/src/runtime/direct-run.ts +12 -2
  165. package/src/runtime/dwf-state-store.ts +1 -1
  166. package/src/runtime/dynamic-workflow-context.ts +186 -58
  167. package/src/runtime/dynamic-workflow-runner.ts +42 -20
  168. package/src/runtime/effectiveness.ts +16 -8
  169. package/src/runtime/errors/crew-errors.ts +7 -11
  170. package/src/runtime/event-stream-bridge.ts +9 -3
  171. package/src/runtime/foreground-control.ts +54 -14
  172. package/src/runtime/foreground-watchdog.ts +9 -6
  173. package/src/runtime/goal-achievement.ts +27 -10
  174. package/src/runtime/goal-evaluator.ts +54 -19
  175. package/src/runtime/goal-loop-runner.ts +280 -99
  176. package/src/runtime/goal-state-store.ts +27 -17
  177. package/src/runtime/green-contract.ts +11 -2
  178. package/src/runtime/group-join.ts +64 -31
  179. package/src/runtime/handoff-manager.ts +37 -42
  180. package/src/runtime/heartbeat-gradient.ts +10 -2
  181. package/src/runtime/heartbeat-watcher.ts +32 -6
  182. package/src/runtime/hidden-handoff.ts +12 -30
  183. package/src/runtime/important-line-classifier.ts +12 -2
  184. package/src/runtime/iteration-hooks.ts +25 -15
  185. package/src/runtime/live-agent-control.ts +55 -7
  186. package/src/runtime/live-agent-manager.ts +130 -27
  187. package/src/runtime/live-control-realtime.ts +23 -3
  188. package/src/runtime/live-irc.ts +4 -1
  189. package/src/runtime/live-session-health.ts +12 -4
  190. package/src/runtime/live-session-runtime.ts +379 -117
  191. package/src/runtime/manifest-cache.ts +28 -12
  192. package/src/runtime/mcp-proxy.ts +8 -16
  193. package/src/runtime/metric-parser.ts +1 -5
  194. package/src/runtime/model-fallback.ts +55 -18
  195. package/src/runtime/model-resolver.ts +2 -6
  196. package/src/runtime/model-scope.ts +19 -3
  197. package/src/runtime/notebook-helpers.ts +60 -62
  198. package/src/runtime/orphan-worker-registry.ts +37 -26
  199. package/src/runtime/output-validator.ts +18 -3
  200. package/src/runtime/overflow-recovery.ts +5 -4
  201. package/src/runtime/parallel-research.ts +29 -5
  202. package/src/runtime/parallel-utils.ts +9 -11
  203. package/src/runtime/path-overlap.ts +150 -0
  204. package/src/runtime/peer-dep.ts +12 -17
  205. package/src/runtime/per-write-validator.ts +1 -3
  206. package/src/runtime/phase-tracker.ts +342 -330
  207. package/src/runtime/pi-args.ts +26 -8
  208. package/src/runtime/pi-json-output.ts +1 -1
  209. package/src/runtime/pi-spawn.ts +30 -19
  210. package/src/runtime/pipeline-runner.ts +56 -55
  211. package/src/runtime/plan-templates.ts +5 -6
  212. package/src/runtime/policy-engine.ts +43 -7
  213. package/src/runtime/post-checks.ts +12 -4
  214. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  215. package/src/runtime/process-lifecycle.ts +20 -10
  216. package/src/runtime/process-status.ts +16 -5
  217. package/src/runtime/progress-event-coalescer.ts +2 -1
  218. package/src/runtime/progress-tracker.ts +103 -103
  219. package/src/runtime/prose-compressor.ts +9 -11
  220. package/src/runtime/recovery-recipes.ts +118 -24
  221. package/src/runtime/replace.ts +25 -10
  222. package/src/runtime/resilient-edit.ts +10 -25
  223. package/src/runtime/result-extractor.ts +1 -2
  224. package/src/runtime/retry-executor.ts +20 -4
  225. package/src/runtime/retry-runner.ts +26 -50
  226. package/src/runtime/role-permission.ts +6 -1
  227. package/src/runtime/run-coalesced-task-group.ts +256 -0
  228. package/src/runtime/run-drift.ts +14 -15
  229. package/src/runtime/run-tracker.ts +6 -28
  230. package/src/runtime/runtime-policy.ts +1 -6
  231. package/src/runtime/runtime-resolver.ts +80 -15
  232. package/src/runtime/scheduler.ts +47 -18
  233. package/src/runtime/semaphore.ts +5 -7
  234. package/src/runtime/sensitive-paths.ts +2 -1
  235. package/src/runtime/session-usage.ts +1 -1
  236. package/src/runtime/settings-store.ts +16 -12
  237. package/src/runtime/sidechain-output.ts +6 -2
  238. package/src/runtime/single-agent-compose.ts +1 -4
  239. package/src/runtime/skill-effectiveness.ts +29 -97
  240. package/src/runtime/skill-instructions.ts +40 -83
  241. package/src/runtime/stale-reconciler.ts +73 -197
  242. package/src/runtime/stream-preview.ts +1 -1
  243. package/src/runtime/streaming-output.ts +1 -1
  244. package/src/runtime/subagent-manager.ts +44 -169
  245. package/src/runtime/subprocess-tool-registry.ts +4 -1
  246. package/src/runtime/supervisor-contact.ts +10 -5
  247. package/src/runtime/task-display.ts +19 -5
  248. package/src/runtime/task-graph-scheduler.ts +95 -21
  249. package/src/runtime/task-graph.ts +5 -11
  250. package/src/runtime/task-health.ts +54 -47
  251. package/src/runtime/task-id.ts +11 -18
  252. package/src/runtime/task-output-context.ts +265 -81
  253. package/src/runtime/task-packet.ts +12 -20
  254. package/src/runtime/task-quality.ts +6 -14
  255. package/src/runtime/task-runner/context-retrieval.ts +4 -13
  256. package/src/runtime/task-runner/live-executor.ts +114 -36
  257. package/src/runtime/task-runner/output-splitter.ts +152 -0
  258. package/src/runtime/task-runner/progress.ts +50 -12
  259. package/src/runtime/task-runner/prompt-builder.ts +31 -7
  260. package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
  261. package/src/runtime/task-runner/result-utils.ts +3 -1
  262. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  263. package/src/runtime/task-runner/run-projection.ts +27 -8
  264. package/src/runtime/task-runner/state-helpers.ts +29 -8
  265. package/src/runtime/task-runner/tail-read.ts +2 -6
  266. package/src/runtime/task-runner.ts +180 -326
  267. package/src/runtime/team-runner-artifacts.ts +13 -0
  268. package/src/runtime/team-runner.ts +456 -974
  269. package/src/runtime/tool-output-pruner.ts +6 -9
  270. package/src/runtime/tool-progress.ts +11 -21
  271. package/src/runtime/verification-gates.ts +33 -24
  272. package/src/runtime/verification-integrity.ts +1 -4
  273. package/src/runtime/verification-worktree.ts +62 -12
  274. package/src/runtime/worker-heartbeat.ts +5 -1
  275. package/src/runtime/worker-startup.ts +29 -5
  276. package/src/runtime/workflow-state.ts +17 -9
  277. package/src/runtime/workspace-lock.ts +30 -35
  278. package/src/runtime/workspace-tree.ts +20 -32
  279. package/src/runtime/yield-handler.ts +43 -9
  280. package/src/runtime/zombie-scanner.ts +5 -4
  281. package/src/schema/config-schema.ts +266 -172
  282. package/src/schema/team-tool-schema.ts +29 -74
  283. package/src/schema/validation-types.ts +25 -17
  284. package/src/skills/discover-skills.ts +35 -10
  285. package/src/skills/skill-templates.ts +109 -27
  286. package/src/skills/validate.ts +26 -8
  287. package/src/state/active-run-registry.ts +87 -28
  288. package/src/state/artifact-store.ts +9 -5
  289. package/src/state/atomic-write-v2.ts +85 -63
  290. package/src/state/atomic-write.ts +105 -26
  291. package/src/state/blob-store.ts +47 -20
  292. package/src/state/contracts.ts +20 -5
  293. package/src/state/crew-init.ts +5 -22
  294. package/src/state/decision-ledger.ts +19 -73
  295. package/src/state/event-log-rotation.ts +41 -15
  296. package/src/state/event-log.ts +168 -53
  297. package/src/state/event-reconstructor.ts +11 -1
  298. package/src/state/gitignore-manager.ts +2 -8
  299. package/src/state/health-store.ts +57 -57
  300. package/src/state/hook-instinct-bridge.ts +1 -1
  301. package/src/state/hook-integrations.ts +1 -1
  302. package/src/state/instinct-store.ts +17 -5
  303. package/src/state/jsonl-writer.ts +1 -1
  304. package/src/state/locks.ts +23 -9
  305. package/src/state/mailbox.ts +151 -28
  306. package/src/state/observation-store.ts +20 -14
  307. package/src/state/run-cache.ts +142 -135
  308. package/src/state/run-graph.ts +6 -15
  309. package/src/state/run-metrics.ts +5 -18
  310. package/src/state/schedule.ts +15 -9
  311. package/src/state/state-store.ts +129 -45
  312. package/src/state/task-claims.ts +12 -2
  313. package/src/state/tiered-eval.ts +52 -43
  314. package/src/state/types-eval.ts +2 -2
  315. package/src/state/types.ts +24 -20
  316. package/src/state/usage.ts +20 -4
  317. package/src/state/worker-atomic-writer.ts +6 -3
  318. package/src/subagents/index.ts +2 -2
  319. package/src/teams/discover-teams.ts +21 -6
  320. package/src/tools/safe-bash-extension.ts +5 -6
  321. package/src/tools/safe-bash.ts +10 -11
  322. package/src/types/new-api-types.ts +6 -10
  323. package/src/ui/agent-management-overlay.ts +52 -40
  324. package/src/ui/card-colors.ts +13 -4
  325. package/src/ui/crew-footer.ts +8 -7
  326. package/src/ui/crew-select-list.ts +1 -1
  327. package/src/ui/dashboard-panes/agents-pane.ts +41 -25
  328. package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
  329. package/src/ui/dashboard-panes/capability-pane.ts +32 -15
  330. package/src/ui/dashboard-panes/health-pane.ts +2 -1
  331. package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
  332. package/src/ui/dashboard-panes/progress-pane.ts +12 -9
  333. package/src/ui/heartbeat-aggregator.ts +15 -4
  334. package/src/ui/keybinding-map.ts +40 -7
  335. package/src/ui/live-conversation-overlay.ts +36 -20
  336. package/src/ui/live-duration.ts +1 -4
  337. package/src/ui/live-run-sidebar.ts +88 -25
  338. package/src/ui/loaders.ts +2 -8
  339. package/src/ui/mascot.ts +20 -36
  340. package/src/ui/overlays/agent-picker-overlay.ts +11 -3
  341. package/src/ui/overlays/confirm-overlay.ts +4 -2
  342. package/src/ui/overlays/help-overlay.ts +25 -14
  343. package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
  344. package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
  345. package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
  346. package/src/ui/pi-ui-compat.ts +7 -7
  347. package/src/ui/powerbar-publisher.ts +120 -42
  348. package/src/ui/render-diff.ts +10 -3
  349. package/src/ui/render-scheduler.ts +12 -4
  350. package/src/ui/run-action-dispatcher.ts +81 -18
  351. package/src/ui/run-dashboard.ts +172 -76
  352. package/src/ui/run-event-bus.ts +59 -37
  353. package/src/ui/run-snapshot-cache.ts +276 -86
  354. package/src/ui/settings-overlay.ts +361 -74
  355. package/src/ui/status-colors.ts +12 -1
  356. package/src/ui/syntax-highlight.ts +1 -1
  357. package/src/ui/theme-adapter.ts +16 -14
  358. package/src/ui/theme-discovery.ts +13 -2
  359. package/src/ui/tool-progress-formatter.ts +7 -7
  360. package/src/ui/tool-render.ts +128 -56
  361. package/src/ui/tool-renderers/brief-mode.ts +45 -27
  362. package/src/ui/tool-renderers/index.ts +109 -43
  363. package/src/ui/transcript-cache.ts +32 -6
  364. package/src/ui/transcript-entries.ts +25 -23
  365. package/src/ui/transcript-viewer.ts +78 -29
  366. package/src/ui/widget/index.ts +99 -40
  367. package/src/ui/widget/widget-formatters.ts +13 -6
  368. package/src/ui/widget/widget-model.ts +19 -8
  369. package/src/ui/widget/widget-renderer.ts +23 -13
  370. package/src/ui/widget/widget-types.ts +1 -1
  371. package/src/utils/bm25-search.ts +199 -199
  372. package/src/utils/conflict-detect.ts +22 -21
  373. package/src/utils/env-filter.ts +14 -7
  374. package/src/utils/file-coalescer.ts +5 -1
  375. package/src/utils/fingerprint.ts +3 -6
  376. package/src/utils/frontmatter.ts +3 -1
  377. package/src/utils/fs-watch.ts +2 -6
  378. package/src/utils/gh-protocol.ts +119 -44
  379. package/src/utils/git.ts +13 -15
  380. package/src/utils/guards.ts +2 -5
  381. package/src/utils/ids.ts +9 -2
  382. package/src/utils/incremental-reader.ts +14 -3
  383. package/src/utils/internal-error.ts +2 -1
  384. package/src/utils/names.ts +12 -3
  385. package/src/utils/paths.ts +49 -5
  386. package/src/utils/project-detector.ts +2 -2
  387. package/src/utils/redaction.ts +29 -19
  388. package/src/utils/resolve-shell.ts +9 -7
  389. package/src/utils/run-watcher-registry.ts +19 -31
  390. package/src/utils/safe-paths.ts +45 -24
  391. package/src/utils/scan-cache.ts +9 -2
  392. package/src/utils/session-utils.ts +2 -4
  393. package/src/utils/sleep.ts +12 -5
  394. package/src/utils/sse-parser.ts +5 -17
  395. package/src/utils/visual.ts +52 -32
  396. package/src/workflows/discover-workflows.ts +41 -109
  397. package/src/workflows/intermediate-store.ts +5 -21
  398. package/src/workflows/preflight-validator.ts +6 -33
  399. package/src/workflows/topology-analyzer.ts +5 -21
  400. package/src/workflows/workflow-config.ts +7 -6
  401. package/src/worktree/branch-freshness.ts +66 -8
  402. package/src/worktree/cleanup.ts +130 -20
  403. package/src/worktree/worktree-manager.ts +212 -69
  404. package/skills/artifact-analysis-loop/SKILL.md +0 -303
  405. package/skills/detection-pipeline-design/SKILL.md +0 -286
  406. package/skills/hunting-investigation-loop/SKILL.md +0 -402
  407. package/skills/incident-playbook-construction/SKILL.md +0 -384
  408. package/skills/security-review/SKILL.md +0 -561
  409. package/skills/threat-hypothesis-framework/SKILL.md +0 -176
  410. package/skills/ui-render-performance/SKILL.md +0 -58
  411. /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
  412. /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
  413. /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
  414. /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
  415. /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
  416. /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
  417. /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
  418. /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
  419. /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
  420. /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
  421. /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
  422. /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
  423. /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
  424. /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
  425. /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
  426. /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
  427. /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
  428. /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
  429. /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
  430. /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
  431. /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
  432. /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
@@ -1,11 +1,11 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
4
+ import { activeRunEntries } from "../state/active-run-registry.ts";
5
+ import type { TeamRunManifest } from "../state/types.ts";
3
6
  import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
4
7
  import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
5
- import { activeRunEntries } from "../state/active-run-registry.ts";
6
8
  import { isSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "../utils/safe-paths.ts";
7
- import type { TeamRunManifest } from "../state/types.ts";
8
- import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
9
9
 
10
10
  export interface ManifestCache {
11
11
  list(limit?: number): TeamRunManifest[];
@@ -74,7 +74,14 @@ function validateManifestForRoot(root: string, runId: string, manifest: TeamRunM
74
74
  const stateRoot = resolveContainedRelativePath(root, runId, "runId");
75
75
  const crewRoot = path.dirname(path.dirname(root));
76
76
  const artifactsRoot = resolveContainedRelativePath(path.join(crewRoot, DEFAULT_PATHS.state.artifactsSubdir), runId, "runId");
77
- if (manifest.runId !== runId || !sameFilesystemPath(manifest.stateRoot, stateRoot) || !sameFilesystemPath(manifest.tasksPath, path.join(stateRoot, DEFAULT_PATHS.state.tasksFile)) || !sameFilesystemPath(manifest.eventsPath, path.join(stateRoot, DEFAULT_PATHS.state.eventsFile)) || !sameFilesystemPath(manifest.artifactsRoot, artifactsRoot)) return false;
77
+ if (
78
+ manifest.runId !== runId ||
79
+ !sameFilesystemPath(manifest.stateRoot, stateRoot) ||
80
+ !sameFilesystemPath(manifest.tasksPath, path.join(stateRoot, DEFAULT_PATHS.state.tasksFile)) ||
81
+ !sameFilesystemPath(manifest.eventsPath, path.join(stateRoot, DEFAULT_PATHS.state.eventsFile)) ||
82
+ !sameFilesystemPath(manifest.artifactsRoot, artifactsRoot)
83
+ )
84
+ return false;
78
85
  if (fs.existsSync(artifactsRoot)) {
79
86
  if (fs.lstatSync(artifactsRoot).isSymbolicLink()) return false;
80
87
  resolveRealContainedPath(path.dirname(artifactsRoot), path.basename(artifactsRoot));
@@ -125,7 +132,10 @@ function collectRoots(root: string): ParsedEntry[] {
125
132
  }
126
133
  return entries
127
134
  .filter((entry) => entry.length > 0 && isSafePathId(entry))
128
- .map((entry) => ({ runId: entry, path: manifestPathForRun(root, entry) }))
135
+ .map((entry) => ({
136
+ runId: entry,
137
+ path: manifestPathForRun(root, entry),
138
+ }))
129
139
  .filter((entry): entry is ParsedEntry => entry.path !== undefined);
130
140
  }
131
141
 
@@ -201,7 +211,10 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
201
211
  }
202
212
  const parsedEntries = [
203
213
  ...roots.flatMap((root) => collectRoots(root)),
204
- ...activeRunEntries().map((entry) => ({ runId: entry.runId, path: entry.manifestPath })),
214
+ ...activeRunEntries().map((entry) => ({
215
+ runId: entry.runId,
216
+ path: entry.manifestPath,
217
+ })),
205
218
  ];
206
219
  const unique = new Map<string, CachedManifest | undefined>();
207
220
  for (const entry of parsedEntries) {
@@ -216,7 +229,6 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
216
229
  if (cached) unique.set(entry.runId, cached);
217
230
  }
218
231
 
219
-
220
232
  const runs = [...unique.values()].filter((value): value is CachedManifest => value !== undefined).map((value) => value.manifest);
221
233
  const sorted = runs.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
222
234
  const limited = sorted.slice(0, Math.max(0, limit));
@@ -238,11 +250,15 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
238
250
 
239
251
  if (options.watch ?? true) {
240
252
  for (const root of roots) {
241
- const watcher = watchWithErrorHandler(root, () => {
242
- scheduleListRefresh();
243
- }, () => {
244
- scheduleListRefresh();
245
- });
253
+ const watcher = watchWithErrorHandler(
254
+ root,
255
+ () => {
256
+ scheduleListRefresh();
257
+ },
258
+ () => {
259
+ scheduleListRefresh();
260
+ },
261
+ );
246
262
  if (watcher) {
247
263
  watcher.unref();
248
264
  watchers.push(watcher);
@@ -34,10 +34,7 @@ export interface McpProxyConfig {
34
34
  * @param options.parentMcpTools — MCP tool names from the parent session (if available)
35
35
  * @param options.shareMcp — Whether to share MCP connections (default: true)
36
36
  */
37
- export function buildMcpProxyConfig(options: {
38
- parentMcpTools?: string[];
39
- shareMcp?: boolean;
40
- }): McpProxyConfig {
37
+ export function buildMcpProxyConfig(options: { parentMcpTools?: string[]; shareMcp?: boolean }): McpProxyConfig {
41
38
  if (options.shareMcp === false) {
42
39
  return { enableMcp: true, proxyTools: [], proxyToolNames: [] };
43
40
  }
@@ -74,9 +71,7 @@ export function buildMcpProxyConfig(options: {
74
71
  * In a future iteration, these can be wired to the actual MCP connections
75
72
  * via an inter-process bridge.
76
73
  */
77
- function createMcpProxyTools(
78
- toolNames: string[],
79
- ): Array<ToolDefinition<TSchema, unknown>> {
74
+ function createMcpProxyTools(toolNames: string[]): Array<ToolDefinition<TSchema, unknown>> {
80
75
  // For now, we don't create individual proxy tools because we can't
81
76
  // forward MCP calls without the parent's MCP manager reference.
82
77
  //
@@ -93,10 +88,7 @@ function createMcpProxyTools(
93
88
  */
94
89
  export function discoverMcpToolNames(activeToolNames: string[]): string[] {
95
90
  return activeToolNames.filter(
96
- (name) =>
97
- name.startsWith("mcp__") ||
98
- name.startsWith("mcp-") ||
99
- (name.includes("__") && !name.startsWith("submit_result")),
91
+ (name) => name.startsWith("mcp__") || name.startsWith("mcp-") || (name.includes("__") && !name.startsWith("submit_result")),
100
92
  );
101
93
  }
102
94
 
@@ -104,10 +96,10 @@ export function discoverMcpToolNames(activeToolNames: string[]): string[] {
104
96
  * Build MCP proxy config from a real Pi SDK session's active tools.
105
97
  * This is the preferred way — inspect what the parent session has available.
106
98
  */
107
- export function buildMcpProxyFromSession(
108
- activeToolNames: string[],
109
- options?: { shareMcp?: boolean },
110
- ): McpProxyConfig {
99
+ export function buildMcpProxyFromSession(activeToolNames: string[], options?: { shareMcp?: boolean }): McpProxyConfig {
111
100
  const mcpTools = discoverMcpToolNames(activeToolNames);
112
- return buildMcpProxyConfig({ parentMcpTools: mcpTools, shareMcp: options?.shareMcp });
101
+ return buildMcpProxyConfig({
102
+ parentMcpTools: mcpTools,
103
+ shareMcp: options?.shareMcp,
104
+ });
113
105
  }
@@ -1,11 +1,7 @@
1
1
  /**
2
2
  * Metric names that are denied to prevent prototype pollution.
3
3
  */
4
- export const DENIED_METRIC_NAMES: ReadonlySet<string> = new Set([
5
- "__proto__",
6
- "constructor",
7
- "prototype",
8
- ]);
4
+ export const DENIED_METRIC_NAMES: ReadonlySet<string> = new Set(["__proto__", "constructor", "prototype"]);
9
5
 
10
6
  const METRIC_LINE_RE = /^CREW_METRIC\s+(\w+)=(\S+)$/;
11
7
 
@@ -2,8 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { errors } from "../errors.ts";
5
- import { checkModelScope } from "./model-scope.ts";
6
5
  import { fuzzyResolveModelId } from "./model-resolver.ts";
6
+ import { checkModelScope } from "./model-scope.ts";
7
7
 
8
8
  export interface AvailableModelInfo {
9
9
  provider: string;
@@ -46,13 +46,22 @@ function modelInfoFromUnknown(value: unknown): AvailableModelInfo | undefined {
46
46
  if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
47
47
  const record = value as ModelLike;
48
48
  if (typeof record.provider !== "string" || typeof record.id !== "string") return undefined;
49
- return { provider: record.provider, id: record.id, fullId: `${record.provider}/${record.id}` };
49
+ return {
50
+ provider: record.provider,
51
+ id: record.id,
52
+ fullId: `${record.provider}/${record.id}`,
53
+ };
50
54
  }
51
55
 
52
56
  export function availableModelInfosFromRegistry(registry: unknown): AvailableModelInfo[] | undefined {
53
57
  if (!registry || typeof registry !== "object" || Array.isArray(registry)) return undefined;
54
58
  const candidate = registry as ModelRegistryLike;
55
- const raw = typeof candidate.getAvailable === "function" ? candidate.getAvailable() : typeof candidate.getAll === "function" ? candidate.getAll() : undefined;
59
+ const raw =
60
+ typeof candidate.getAvailable === "function"
61
+ ? candidate.getAvailable()
62
+ : typeof candidate.getAll === "function"
63
+ ? candidate.getAll()
64
+ : undefined;
56
65
  if (!Array.isArray(raw)) return undefined;
57
66
  return raw.map(modelInfoFromUnknown).filter((entry): entry is AvailableModelInfo => entry !== undefined);
58
67
  }
@@ -74,7 +83,7 @@ function readJsonObject(filePath: string): Record<string, unknown> | undefined {
74
83
  try {
75
84
  if (!fs.existsSync(filePath)) return undefined;
76
85
  const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
77
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
86
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : undefined;
78
87
  } catch {
79
88
  return undefined;
80
89
  }
@@ -92,7 +101,11 @@ function piAgentDir(): string {
92
101
 
93
102
  function settingsModelInfo(settings: PiSettingsLike | undefined): AvailableModelInfo | undefined {
94
103
  if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return undefined;
95
- return { provider: settings.defaultProvider, id: settings.defaultModel, fullId: `${settings.defaultProvider}/${settings.defaultModel}` };
104
+ return {
105
+ provider: settings.defaultProvider,
106
+ id: settings.defaultModel,
107
+ fullId: `${settings.defaultProvider}/${settings.defaultModel}`,
108
+ };
96
109
  }
97
110
 
98
111
  function modelsJsonInfos(modelsJson: PiModelsJsonLike | undefined): AvailableModelInfo[] {
@@ -118,8 +131,11 @@ function modelsJsonInfos(modelsJson: PiModelsJsonLike | undefined): AvailableMod
118
131
  export function configuredModelInfosFromPiConfig(cwd?: string): AvailableModelInfo[] {
119
132
  const agentDir = piAgentDir();
120
133
  const globalSettings = readJsonObject(path.join(agentDir, "settings.json")) as PiSettingsLike | undefined;
121
- const projectSettings = cwd ? readJsonObject(path.join(cwd, ".pi", "settings.json")) as PiSettingsLike | undefined : undefined;
122
- const effectiveSettings = { ...(globalSettings ?? {}), ...(projectSettings ?? {}) };
134
+ const projectSettings = cwd ? (readJsonObject(path.join(cwd, ".pi", "settings.json")) as PiSettingsLike | undefined) : undefined;
135
+ const effectiveSettings = {
136
+ ...(globalSettings ?? {}),
137
+ ...(projectSettings ?? {}),
138
+ };
123
139
  const defaultModel = settingsModelInfo(effectiveSettings);
124
140
  return uniqueModelInfos([
125
141
  ...(defaultModel ? [defaultModel] : []),
@@ -127,7 +143,10 @@ export function configuredModelInfosFromPiConfig(cwd?: string): AvailableModelIn
127
143
  ]);
128
144
  }
129
145
 
130
- export function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
146
+ export function splitThinkingSuffix(model: string): {
147
+ baseModel: string;
148
+ thinkingSuffix: string;
149
+ } {
131
150
  const colonIdx = model.lastIndexOf(":");
132
151
  if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
133
152
  return {
@@ -251,7 +270,9 @@ export function isRetryableModelFailure(error: string | undefined): boolean {
251
270
 
252
271
  export function formatModelAttemptNote(attempt: ModelAttemptSummary, nextModel?: string): string {
253
272
  const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
254
- return nextModel ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.` : `[fallback] ${attempt.model} failed: ${failure}.`;
273
+ return nextModel
274
+ ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.`
275
+ : `[fallback] ${attempt.model} failed: ${failure}.`;
255
276
  }
256
277
 
257
278
  export function buildModelCandidates(
@@ -321,16 +342,31 @@ export function buildConfiguredModelRouting(input: {
321
342
  }): ConfiguredModelRouting {
322
343
  const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
323
344
  const configModels = configuredModelInfosFromPiConfig(input.cwd);
324
- const availableModels = registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
345
+ const availableModels =
346
+ registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
325
347
  const parentModel = modelStringFromUnknown(input.parentModel);
326
348
  const preferredProvider = parentModel?.split("/")[0] ?? availableModels?.[0]?.provider;
327
349
  // B3: Parent model inheritance — when agent has no model specified,
328
350
  // inherit from parent session model before falling back to defaults.
329
351
  const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : parentModel;
330
- const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find((model): model is string => Boolean(model?.trim()));
331
- if (availableModels && availableModels.length === 0) return { requested, candidates: [], reason: "no configured Pi models available" };
352
+ const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find((model): model is string =>
353
+ Boolean(model?.trim()),
354
+ );
355
+ if (availableModels && availableModels.length === 0)
356
+ return {
357
+ requested,
358
+ candidates: [],
359
+ reason: "no configured Pi models available",
360
+ };
332
361
  const rawModels = availableModels
333
- ? [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...(input.fallbackModels ?? []), ...availableModels.map((model) => model.fullId)]
362
+ ? [
363
+ input.overrideModel,
364
+ input.stepModel,
365
+ input.teamRoleModel,
366
+ effectiveAgentModel,
367
+ ...(input.fallbackModels ?? []),
368
+ ...availableModels.map((model) => model.fullId),
369
+ ]
334
370
  : [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...(input.fallbackModels ?? []), parentModel];
335
371
  // Fix (Round 18): when an agent has `model: false` (frontmatter) the
336
372
  // inherited `parentModel` (= session chính's model, e.g. minimax-M3) IS the
@@ -345,11 +381,12 @@ export function buildConfiguredModelRouting(input: {
345
381
  return isAvailableModel(model.trim(), availableModels);
346
382
  });
347
383
  const candidates = buildModelCandidates(configuredModels[0], configuredModels.slice(1), availableModels, preferredProvider);
348
- const reason = requested && candidates[0] && resolveModelCandidate(requested, availableModels, preferredProvider) !== candidates[0]
349
- ? "requested model unavailable; selected configured Pi fallback"
350
- : candidates.length > 1
351
- ? "configured Pi fallback chain"
352
- : undefined;
384
+ const reason =
385
+ requested && candidates[0] && resolveModelCandidate(requested, availableModels, preferredProvider) !== candidates[0]
386
+ ? "requested model unavailable; selected configured Pi fallback"
387
+ : candidates.length > 1
388
+ ? "configured Pi fallback chain"
389
+ : undefined;
353
390
  // F7 scope gate: when `scopeModelsPatterns` is configured, check the
354
391
  // resolved model. Caller-supplied (override/step/team role) out-of-scope
355
392
  // is a HARD ERROR (we surface it via the verdict AND throw, so spawn aborts
@@ -60,9 +60,7 @@ export function resolveModel(input: string, registry: ModelRegistry): Model | st
60
60
  } else if (name.includes(query)) {
61
61
  score = 40 + (query.length / name.length) * 20;
62
62
  } else if (
63
- query
64
- .split(/[\s\-/]+/)
65
- .every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
63
+ query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
66
64
  ) {
67
65
  score = 20;
68
66
  }
@@ -113,9 +111,7 @@ export function fuzzyResolveModelId(input: string, models: SimpleModelEntry[]):
113
111
  } else if (name.includes(query)) {
114
112
  score = 40 + (query.length / (name.length || 1)) * 20;
115
113
  } else if (
116
- query
117
- .split(/[\s\-/]+/)
118
- .every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
114
+ query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
119
115
  ) {
120
116
  score = 20;
121
117
  }
@@ -92,7 +92,12 @@ export function checkModelScope(
92
92
  source: ModelScopeSource,
93
93
  ): ModelScopeCheck {
94
94
  if (!modelId) {
95
- return { inScope: true, source, model: "", reason: "no model specified" };
95
+ return {
96
+ inScope: true,
97
+ source,
98
+ model: "",
99
+ reason: "no model specified",
100
+ };
96
101
  }
97
102
  if (!patterns || patterns.length === 0) {
98
103
  // No allowlist → not enforcing. The toggle is opt-in; the user hasn't
@@ -101,7 +106,12 @@ export function checkModelScope(
101
106
  }
102
107
  for (const pattern of patterns) {
103
108
  if (matchesModelPattern(modelId, pattern)) {
104
- return { inScope: true, source, model: modelId, matchedPattern: pattern };
109
+ return {
110
+ inScope: true,
111
+ source,
112
+ model: modelId,
113
+ matchedPattern: pattern,
114
+ };
105
115
  }
106
116
  }
107
117
  return {
@@ -131,7 +141,13 @@ export async function readEnabledModelsPatterns(cwd: string, agentDir?: string):
131
141
  // LAZY: defer dynamic import of @earendil-works/pi-coding-agent to its call site.
132
142
  const mod = await import("@earendil-works/pi-coding-agent" as string).catch(() => null);
133
143
  if (!mod) return [];
134
- const SettingsManagerCtor = (mod as { SettingsManager?: { create?: (cwd: string, agentDir?: string) => { getEnabledModels?: () => string[] | undefined } } }).SettingsManager;
144
+ const SettingsManagerCtor = (
145
+ mod as {
146
+ SettingsManager?: {
147
+ create?: (cwd: string, agentDir?: string) => { getEnabledModels?: () => string[] | undefined };
148
+ };
149
+ }
150
+ ).SettingsManager;
135
151
  if (!SettingsManagerCtor?.create) return [];
136
152
  const sm = SettingsManagerCtor.create(cwd, agentDir);
137
153
  const patterns = sm.getEnabledModels?.();
@@ -1,90 +1,88 @@
1
1
  export interface NotebookCell {
2
- index: number;
3
- cellType: "code" | "markdown" | "raw";
4
- source: string;
5
- outputs?: unknown[];
6
- metadata?: Record<string, unknown>;
2
+ index: number;
3
+ cellType: "code" | "markdown" | "raw";
4
+ source: string;
5
+ outputs?: unknown[];
6
+ metadata?: Record<string, unknown>;
7
7
  }
8
8
 
9
9
  export interface Notebook {
10
- cells: NotebookCell[];
11
- metadata?: Record<string, unknown>;
12
- nbformat: number;
10
+ cells: NotebookCell[];
11
+ metadata?: Record<string, unknown>;
12
+ nbformat: number;
13
13
  }
14
14
 
15
15
  /** Check if a file path is a notebook */
16
16
  export function isNotebookPath(filePath: string): boolean {
17
- return filePath.toLowerCase().endsWith(".ipynb");
17
+ return filePath.toLowerCase().endsWith(".ipynb");
18
18
  }
19
19
 
20
20
  function normalizeSource(source: unknown): string {
21
- if (typeof source === "string") return source;
22
- if (Array.isArray(source)) return source.join("");
23
- return "";
21
+ if (typeof source === "string") return source;
22
+ if (Array.isArray(source)) return source.join("");
23
+ return "";
24
24
  }
25
25
 
26
26
  /** Parse a .ipynb JSON file into a Notebook structure */
27
27
  export function parseNotebook(content: string): Notebook {
28
- let raw: unknown;
29
- try {
30
- raw = JSON.parse(content);
31
- } catch {
32
- return { cells: [], nbformat: 4 };
33
- }
34
- if (typeof raw !== "object" || raw === null) return { cells: [], nbformat: 4 };
35
- const obj = raw as Record<string, unknown>;
36
- const nbformat = typeof obj.nbformat === "number" ? obj.nbformat : 4;
37
- const rawCells = Array.isArray(obj.cells) ? obj.cells : [];
38
- const cells: NotebookCell[] = rawCells
39
- .map((c: unknown, i: number): NotebookCell | null => {
40
- if (typeof c !== "object" || c === null) return null;
41
- const cell = c as Record<string, unknown>;
42
- const cellType = cell.cell_type;
43
- if (cellType !== "code" && cellType !== "markdown" && cellType !== "raw") return null;
44
- return {
45
- index: i,
46
- cellType,
47
- source: normalizeSource(cell.source),
48
- outputs: Array.isArray(cell.outputs) ? cell.outputs : undefined,
49
- metadata:
50
- cell.metadata && typeof cell.metadata === "object" && !Array.isArray(cell.metadata)
51
- ? (cell.metadata as Record<string, unknown>)
52
- : undefined,
53
- };
54
- })
55
- .filter((c: NotebookCell | null): c is NotebookCell => c !== null);
56
- const metadata =
57
- obj.metadata && typeof obj.metadata === "object" && !Array.isArray(obj.metadata)
58
- ? (obj.metadata as Record<string, unknown>)
59
- : undefined;
60
- return { cells, metadata, nbformat };
28
+ let raw: unknown;
29
+ try {
30
+ raw = JSON.parse(content);
31
+ } catch {
32
+ return { cells: [], nbformat: 4 };
33
+ }
34
+ if (typeof raw !== "object" || raw === null) return { cells: [], nbformat: 4 };
35
+ const obj = raw as Record<string, unknown>;
36
+ const nbformat = typeof obj.nbformat === "number" ? obj.nbformat : 4;
37
+ const rawCells = Array.isArray(obj.cells) ? obj.cells : [];
38
+ const cells: NotebookCell[] = rawCells
39
+ .map((c: unknown, i: number): NotebookCell | null => {
40
+ if (typeof c !== "object" || c === null) return null;
41
+ const cell = c as Record<string, unknown>;
42
+ const cellType = cell.cell_type;
43
+ if (cellType !== "code" && cellType !== "markdown" && cellType !== "raw") return null;
44
+ return {
45
+ index: i,
46
+ cellType,
47
+ source: normalizeSource(cell.source),
48
+ outputs: Array.isArray(cell.outputs) ? cell.outputs : undefined,
49
+ metadata:
50
+ cell.metadata && typeof cell.metadata === "object" && !Array.isArray(cell.metadata)
51
+ ? (cell.metadata as Record<string, unknown>)
52
+ : undefined,
53
+ };
54
+ })
55
+ .filter((c: NotebookCell | null): c is NotebookCell => c !== null);
56
+ const metadata =
57
+ obj.metadata && typeof obj.metadata === "object" && !Array.isArray(obj.metadata)
58
+ ? (obj.metadata as Record<string, unknown>)
59
+ : undefined;
60
+ return { cells, metadata, nbformat };
61
61
  }
62
62
 
63
63
  /** Get a specific cell by index */
64
64
  export function getCell(notebook: Notebook, index: number): NotebookCell | undefined {
65
- return notebook.cells.find((c) => c.index === index);
65
+ return notebook.cells.find((c) => c.index === index);
66
66
  }
67
67
 
68
68
  /** Update a cell's source content, returning a new Notebook */
69
69
  export function updateCell(notebook: Notebook, index: number, source: string): Notebook {
70
- const cells = notebook.cells.map((c) =>
71
- c.index === index ? { ...c, source } : c,
72
- );
73
- return { ...notebook, cells };
70
+ const cells = notebook.cells.map((c) => (c.index === index ? { ...c, source } : c));
71
+ return { ...notebook, cells };
74
72
  }
75
73
 
76
74
  /** Serialize a Notebook back to .ipynb JSON string */
77
75
  export function serializeNotebook(notebook: Notebook): string {
78
- const raw = {
79
- nbformat: notebook.nbformat,
80
- nbformat_minor: 5,
81
- metadata: notebook.metadata ?? {},
82
- cells: notebook.cells.map((c) => ({
83
- cell_type: c.cellType,
84
- source: c.source,
85
- metadata: c.metadata ?? {},
86
- ...(c.cellType === "code" ? { outputs: c.outputs ?? [], execution_count: null } : {}),
87
- })),
88
- };
89
- return JSON.stringify(raw, null, 2) + "\n";
76
+ const raw = {
77
+ nbformat: notebook.nbformat,
78
+ nbformat_minor: 5,
79
+ metadata: notebook.metadata ?? {},
80
+ cells: notebook.cells.map((c) => ({
81
+ cell_type: c.cellType,
82
+ source: c.source,
83
+ metadata: c.metadata ?? {},
84
+ ...(c.cellType === "code" ? { outputs: c.outputs ?? [], execution_count: null } : {}),
85
+ })),
86
+ };
87
+ return JSON.stringify(raw, null, 2) + "\n";
90
88
  }
@@ -21,13 +21,14 @@
21
21
  * cleanupOrphanWorkers) are protected by file locking to prevent concurrent
22
22
  * writes from causing lost updates.
23
23
  */
24
+
25
+ import { execFileSync, execSync } from "node:child_process";
24
26
  import * as fs from "node:fs";
25
27
  import * as path from "node:path";
26
- import { execSync, execFileSync } from "node:child_process";
27
- import { userPiRoot } from "../utils/paths.ts";
28
- import { logInternalError } from "../utils/internal-error.ts";
29
- import { withFileLockSync } from "../state/locks.ts";
30
28
  import { isSymlinkSafePath } from "../state/atomic-write.ts";
29
+ import { withFileLockSync } from "../state/locks.ts";
30
+ import { logInternalError } from "../utils/internal-error.ts";
31
+ import { userPiRoot } from "../utils/paths.ts";
31
32
 
32
33
  const STALE_REGISTRATION_MS = 60 * 60 * 1000; // 1 hour
33
34
  // Grace period before a fresh worker can be cleaned up when parent is dead.
@@ -95,7 +96,10 @@ function getProcessStartTimeLinux(pid: number): number | undefined {
95
96
  // utime(12) stime(13) cutime(14) cstime(15) priority(16) nice(17)
96
97
  // num_threads(18) itrealvalue(19) starttime(20) vsize(21) rss(22)
97
98
  // ...but we only need starttime which is field 22 (index 21 after comm)
98
- const fieldsAfterComm = stat.slice(lastParen + 1).trim().split(/\s+/);
99
+ const fieldsAfterComm = stat
100
+ .slice(lastParen + 1)
101
+ .trim()
102
+ .split(/\s+/);
99
103
  // starttime is at index 19 (the 20th field after comm)
100
104
  const startTimeClockTicks = Number(fieldsAfterComm[19]);
101
105
  if (!Number.isFinite(startTimeClockTicks)) return undefined;
@@ -139,11 +143,10 @@ function getProcessStartTimeWindows(pid: number): number | undefined {
139
143
  // For Node.js without native modules, use tasklist /v and parse output
140
144
  try {
141
145
  // /v verbose, /fo csv, /nh no header
142
- const output = execFileSync(
143
- "powershell",
144
- ["-Command", `Get-Process -Id ${pid} | Select-Object -ExpandProperty StartTime`],
145
- { encoding: "utf-8", timeout: 5000 },
146
- ).trim();
146
+ const output = execFileSync("powershell", ["-Command", `Get-Process -Id ${pid} | Select-Object -ExpandProperty StartTime`], {
147
+ encoding: "utf-8",
148
+ timeout: 5000,
149
+ }).trim();
147
150
  if (!output) return undefined;
148
151
  const date = new Date(output);
149
152
  if (Number.isNaN(date.getTime())) return undefined;
@@ -153,7 +156,6 @@ function getProcessStartTimeWindows(pid: number): number | undefined {
153
156
  }
154
157
  }
155
158
 
156
-
157
159
  let REGISTRY_PATH = path.join(userPiRoot(), "state", "orphan-workers.json");
158
160
 
159
161
  /** @internal Test-only: override the registry path. */
@@ -213,7 +215,11 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
213
215
  // writing to disk.
214
216
  for (const entry of entries) {
215
217
  if (!isValidId(entry.sessionId) || !isValidId(entry.runId)) {
216
- logInternalError("orphan-worker-registry.write", new Error("Refusing to write: invalid sessionId or runId"), `sessionId=${entry.sessionId} runId=${entry.runId}`);
218
+ logInternalError(
219
+ "orphan-worker-registry.write",
220
+ new Error("Refusing to write: invalid sessionId or runId"),
221
+ `sessionId=${entry.sessionId} runId=${entry.runId}`,
222
+ );
217
223
  return;
218
224
  }
219
225
  }
@@ -224,12 +230,20 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
224
230
  // isSymlinkSafePath walks the ancestor chain to detect any symlinks,
225
231
  // preventing attacks where an intermediate ancestor is a symlink.
226
232
  if (!isSymlinkSafePath(p)) {
227
- logInternalError("orphan-worker-registry.write", new Error("Refusing to write: target is a symlink or inside untrusted directory"), `path=${p}`);
233
+ logInternalError(
234
+ "orphan-worker-registry.write",
235
+ new Error("Refusing to write: target is a symlink or inside untrusted directory"),
236
+ `path=${p}`,
237
+ );
228
238
  return;
229
239
  }
230
240
  // Issue 2 fix: Check parent directory safety immediately before creating it.
231
241
  if (!isSymlinkSafePath(dir)) {
232
- logInternalError("orphan-worker-registry.write", new Error("Refusing to create: parent directory is a symlink or inside untrusted directory"), `dir=${dir}`);
242
+ logInternalError(
243
+ "orphan-worker-registry.write",
244
+ new Error("Refusing to create: parent directory is a symlink or inside untrusted directory"),
245
+ `dir=${dir}`,
246
+ );
233
247
  return;
234
248
  }
235
249
  // Ensure parent directory exists to serialize directory
@@ -240,11 +254,7 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
240
254
  try {
241
255
  fs.writeFileSync(p, JSON.stringify(entries, null, 2), { mode: 0o600 });
242
256
  } catch (error) {
243
- logInternalError(
244
- "orphan-worker-registry.write",
245
- error,
246
- `path=${p} entries=${entries.length}`,
247
- );
257
+ logInternalError("orphan-worker-registry.write", error, `path=${p} entries=${entries.length}`);
248
258
  }
249
259
  }
250
260
 
@@ -266,9 +276,7 @@ export function registerWorker(
266
276
  // Issue 3 fix: Validate sessionId and runId to prevent path traversal attacks.
267
277
  if (!isValidId(sessionId) || !isValidId(runId)) return;
268
278
  const startTime = getProcessStartTime(pid) ?? 0;
269
- const parentPidStartTime = Number.isFinite(parentPid) && parentPid > 0
270
- ? (getProcessStartTime(parentPid) ?? 0)
271
- : 0;
279
+ const parentPidStartTime = Number.isFinite(parentPid) && parentPid > 0 ? (getProcessStartTime(parentPid) ?? 0) : 0;
272
280
  withFileLockSync(getRegistryPath(), () => {
273
281
  const entries = readRegistry();
274
282
  // Dedupe by PID
@@ -322,10 +330,13 @@ export interface CleanupOrphanWorkersResult {
322
330
  * ALWAYS kept regardless of age. This protects concurrent sessions.
323
331
  * Pass undefined for unconditional cleanup (e.g. from `pi-crew cleanup`).
324
332
  */
325
- export function cleanupOrphanWorkers(
326
- currentSessionId?: string,
327
- ): CleanupOrphanWorkersResult {
328
- let result: CleanupOrphanWorkersResult = { scanned: 0, killed: 0, pruned: 0, kept: 0 };
333
+ export function cleanupOrphanWorkers(currentSessionId?: string): CleanupOrphanWorkersResult {
334
+ let result: CleanupOrphanWorkersResult = {
335
+ scanned: 0,
336
+ killed: 0,
337
+ pruned: 0,
338
+ kept: 0,
339
+ };
329
340
  withFileLockSync(getRegistryPath(), () => {
330
341
  const entries = readRegistry();
331
342
  const now = Date.now();