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,409 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { errors } from "../errors.ts";
5
+ import { fuzzyResolveModelId } from "./model-resolver.ts";
6
+ import { checkModelScope } from "./model-scope.ts";
7
+
8
+ export interface AvailableModelInfo {
9
+ provider: string;
10
+ id: string;
11
+ fullId: string;
12
+ }
13
+
14
+ export interface ModelAttemptSummary {
15
+ model: string;
16
+ success: boolean;
17
+ exitCode?: number | null;
18
+ error?: string;
19
+ }
20
+
21
+ export interface ModelLike {
22
+ provider?: unknown;
23
+ id?: unknown;
24
+ }
25
+
26
+ export interface ModelRegistryLike {
27
+ getAvailable?: () => unknown[];
28
+ getAll?: () => unknown[];
29
+ }
30
+
31
+ interface PiSettingsLike {
32
+ defaultProvider?: unknown;
33
+ defaultModel?: unknown;
34
+ }
35
+
36
+ interface PiModelsJsonLike {
37
+ providers?: unknown;
38
+ }
39
+
40
+ interface PiProviderConfigLike {
41
+ models?: unknown;
42
+ modelOverrides?: unknown;
43
+ }
44
+
45
+ function modelInfoFromUnknown(value: unknown): AvailableModelInfo | undefined {
46
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
47
+ const record = value as ModelLike;
48
+ if (typeof record.provider !== "string" || typeof record.id !== "string") return undefined;
49
+ return {
50
+ provider: record.provider,
51
+ id: record.id,
52
+ fullId: `${record.provider}/${record.id}`,
53
+ };
54
+ }
55
+
56
+ export function availableModelInfosFromRegistry(registry: unknown): AvailableModelInfo[] | undefined {
57
+ if (!registry || typeof registry !== "object" || Array.isArray(registry)) return undefined;
58
+ const candidate = registry as ModelRegistryLike;
59
+ const raw =
60
+ typeof candidate.getAvailable === "function"
61
+ ? candidate.getAvailable()
62
+ : typeof candidate.getAll === "function"
63
+ ? candidate.getAll()
64
+ : undefined;
65
+ if (!Array.isArray(raw)) return undefined;
66
+ return raw.map(modelInfoFromUnknown).filter((entry): entry is AvailableModelInfo => entry !== undefined);
67
+ }
68
+
69
+ export function modelStringFromUnknown(model: unknown): string | undefined {
70
+ return modelInfoFromUnknown(model)?.fullId;
71
+ }
72
+
73
+ function uniqueModelInfos(models: AvailableModelInfo[]): AvailableModelInfo[] {
74
+ const seen = new Set<string>();
75
+ return models.filter((model) => {
76
+ if (seen.has(model.fullId)) return false;
77
+ seen.add(model.fullId);
78
+ return true;
79
+ });
80
+ }
81
+
82
+ function readJsonObject(filePath: string): Record<string, unknown> | undefined {
83
+ try {
84
+ if (!fs.existsSync(filePath)) return undefined;
85
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
86
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : undefined;
87
+ } catch {
88
+ return undefined;
89
+ }
90
+ }
91
+
92
+ function piAgentDir(): string {
93
+ const envDir = process.env.PI_CODING_AGENT_DIR?.trim();
94
+ if (envDir) {
95
+ if (envDir === "~") return os.homedir();
96
+ if (envDir.startsWith("~/")) return path.join(os.homedir(), envDir.slice(2));
97
+ return envDir;
98
+ }
99
+ return path.join(os.homedir(), ".pi", "agent");
100
+ }
101
+
102
+ function settingsModelInfo(settings: PiSettingsLike | undefined): AvailableModelInfo | undefined {
103
+ if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return undefined;
104
+ return {
105
+ provider: settings.defaultProvider,
106
+ id: settings.defaultModel,
107
+ fullId: `${settings.defaultProvider}/${settings.defaultModel}`,
108
+ };
109
+ }
110
+
111
+ function modelsJsonInfos(modelsJson: PiModelsJsonLike | undefined): AvailableModelInfo[] {
112
+ if (!modelsJson?.providers || typeof modelsJson.providers !== "object" || Array.isArray(modelsJson.providers)) return [];
113
+ const infos: AvailableModelInfo[] = [];
114
+ for (const [provider, rawConfig] of Object.entries(modelsJson.providers as Record<string, unknown>)) {
115
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) continue;
116
+ const config = rawConfig as PiProviderConfigLike;
117
+ if (Array.isArray(config.models)) {
118
+ for (const rawModel of config.models) {
119
+ if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) continue;
120
+ const id = (rawModel as { id?: unknown }).id;
121
+ if (typeof id === "string") infos.push({ provider, id, fullId: `${provider}/${id}` });
122
+ }
123
+ }
124
+ if (config.modelOverrides && typeof config.modelOverrides === "object" && !Array.isArray(config.modelOverrides)) {
125
+ for (const id of Object.keys(config.modelOverrides)) infos.push({ provider, id, fullId: `${provider}/${id}` });
126
+ }
127
+ }
128
+ return infos;
129
+ }
130
+
131
+ export function configuredModelInfosFromPiConfig(cwd?: string): AvailableModelInfo[] {
132
+ const agentDir = piAgentDir();
133
+ const globalSettings = readJsonObject(path.join(agentDir, "settings.json")) as PiSettingsLike | undefined;
134
+ const projectSettings = cwd ? (readJsonObject(path.join(cwd, ".pi", "settings.json")) as PiSettingsLike | undefined) : undefined;
135
+ const effectiveSettings = {
136
+ ...(globalSettings ?? {}),
137
+ ...(projectSettings ?? {}),
138
+ };
139
+ const defaultModel = settingsModelInfo(effectiveSettings);
140
+ return uniqueModelInfos([
141
+ ...(defaultModel ? [defaultModel] : []),
142
+ ...modelsJsonInfos(readJsonObject(path.join(agentDir, "models.json")) as PiModelsJsonLike | undefined),
143
+ ]);
144
+ }
145
+
146
+ export function splitThinkingSuffix(model: string): {
147
+ baseModel: string;
148
+ thinkingSuffix: string;
149
+ } {
150
+ const colonIdx = model.lastIndexOf(":");
151
+ if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
152
+ return {
153
+ baseModel: model.substring(0, colonIdx),
154
+ thinkingSuffix: model.substring(colonIdx),
155
+ };
156
+ }
157
+
158
+ export function resolveModelCandidate(
159
+ model: string | undefined,
160
+ availableModels: AvailableModelInfo[] | undefined,
161
+ preferredProvider?: string,
162
+ ): string | undefined {
163
+ if (!model) return undefined;
164
+ if (model.includes("/")) return model;
165
+ if (!availableModels || availableModels.length === 0) return model;
166
+
167
+ const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
168
+ const matches = availableModels.filter((entry) => entry.id === baseModel);
169
+ if (preferredProvider) {
170
+ const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
171
+ if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
172
+ }
173
+ // When multiple providers share the same model id, return the raw model string.
174
+ // Callers should use the preferredProvider hint via resolveModelCandidate.
175
+ if (matches.length !== 1) {
176
+ // Fuzzy fallback: try to resolve via partial name matching
177
+ const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
178
+ if (fuzzy) return `${fuzzy}${thinkingSuffix}`;
179
+ return model;
180
+ }
181
+ return `${matches[0]!.fullId}${thinkingSuffix}`;
182
+ }
183
+
184
+ const RETRYABLE_MODEL_FAILURE_PATTERNS = [
185
+ /rate.?limit/i,
186
+ /too many requests/i,
187
+ /\b429\b/,
188
+ /rate_limit_error/i,
189
+ /quota/i,
190
+ /provider.*unavailable/i,
191
+ /model.*unavailable/i,
192
+ /model.*disabled/i,
193
+ /model.*not found/i,
194
+ /unknown model/i,
195
+ /overloaded/i,
196
+ /service unavailable/i,
197
+ /temporar(?:ily)? unavailable/i,
198
+ /connection refused/i,
199
+ /fetch failed/i,
200
+ /network error/i,
201
+ /socket hang up/i,
202
+ /upstream/i,
203
+ /timed? out/i,
204
+ /timeout/i,
205
+ /\b502\b/,
206
+ /\b503\b/,
207
+ /\b504\b/,
208
+ //
209
+ // Provider-side 5xx / generic api_error. The pi-core retry layer already
210
+ // retries these (agent-session.ts matches `500|server error|internal error`),
211
+ // but the pi-crew MODEL FALLBACK layer must ALSO treat them as retryable so
212
+ // that when the provider is hard-down across all 3 provider retries, we fail
213
+ // over to the next configured model instead of giving up. Reported case
214
+ // (2026-06-17): `500 {"type":"error","error":{"type":"api_error",
215
+ // "message":"unknown error, 999 (1000)"}}` — a transient provider outage that
216
+ // should trigger the fallback chain, not abort.
217
+ //
218
+ // `api_error` is the OpenAI-compatible generic error type (vs rate_limit_error
219
+ // / overloaded_error / etc.) and almost always means a transient server fault.
220
+ //
221
+ // `unknown error` is the body of the generic message; `internal`/`server`
222
+ // catch the common phrasings. `\b500\b`/`\b501\b` catch the HTTP status in
223
+ // the rendered error string.
224
+ /\b500\b/,
225
+ /\b501\b/,
226
+ /api_error/i,
227
+ /unknown error/i,
228
+ /internal(?:_server)?[ _]error/i,
229
+ /server error/i,
230
+ /bad gateway/i,
231
+ //
232
+ // Broader retryable patterns (added 2026-06-25, FIX 2):
233
+ // - `/provider[_ ]?error/i`: OpenAI-compatible "Provider error" generic fault.
234
+ // - `/context[_ ]?length[_ ]?exceeded/i`: "context_length_exceeded" from
235
+ // OpenAI/Anthropic — when the configured model is the bottleneck, a
236
+ // different model in the fallback chain may have a larger window.
237
+ // - `/safety/i`: Anthropic safety blocks — typically retryable on a
238
+ // different model in the fallback chain.
239
+ // - `/is[_ ]?overloaded/i`: alias to the existing `/overloaded/i` pattern
240
+ // to catch phrasings like "upstream is overloaded".
241
+ // - `/\b408\b/`: HTTP 408 Request Timeout — transient, provider-side.
242
+ //
243
+ // Intentionally NOT added: `/bad_request/` — can mean bad input (e.g.
244
+ // invalid schema), which is non-retryable.
245
+ /provider[_ ]?error/i,
246
+ /context[_ ]?length[_ ]?exceeded/i,
247
+ /safety/i,
248
+ /is[_ ]?overloaded/i,
249
+ /\b408\b/,
250
+ ];
251
+
252
+ // These patterns indicate auth/key/billing issues that will never succeed on retry.
253
+ const NON_RETRYABLE_MODEL_FAILURE_PATTERNS = [
254
+ /auth(?:entication)?/i,
255
+ /unauthori[sz]ed/i,
256
+ /forbidden/i,
257
+ /api key/i,
258
+ /token expired/i,
259
+ /invalid key/i,
260
+ /billing/i,
261
+ /credit/i,
262
+ ];
263
+
264
+ export function isRetryableModelFailure(error: string | undefined): boolean {
265
+ if (!error) return false;
266
+ // Auth / billing / invalid-key failures will never succeed on retry.
267
+ if (NON_RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error))) return false;
268
+ return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
269
+ }
270
+
271
+ export function formatModelAttemptNote(attempt: ModelAttemptSummary, nextModel?: string): string {
272
+ const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
273
+ return nextModel
274
+ ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.`
275
+ : `[fallback] ${attempt.model} failed: ${failure}.`;
276
+ }
277
+
278
+ export function buildModelCandidates(
279
+ primaryModel: string | undefined,
280
+ fallbackModels: string[] | undefined,
281
+ availableModels: AvailableModelInfo[] | undefined,
282
+ preferredProvider?: string,
283
+ ): string[] {
284
+ const seen = new Set<string>();
285
+ const candidates: string[] = [];
286
+ for (const raw of [primaryModel, ...(fallbackModels ?? [])]) {
287
+ if (!raw) continue;
288
+ const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
289
+ if (!normalized || seen.has(normalized)) continue;
290
+ seen.add(normalized);
291
+ candidates.push(normalized);
292
+ }
293
+ return candidates;
294
+ }
295
+
296
+ function isAvailableModel(model: string, availableModels: AvailableModelInfo[] | undefined): boolean {
297
+ if (!availableModels || availableModels.length === 0) return true;
298
+ const { baseModel } = splitThinkingSuffix(model);
299
+ if (baseModel.includes("/")) return availableModels.some((entry) => entry.fullId === baseModel);
300
+ if (availableModels.some((entry) => entry.id === baseModel)) return true;
301
+ const fuzzy = fuzzyResolveModelId(baseModel, availableModels);
302
+ return fuzzy !== undefined;
303
+ }
304
+
305
+ export interface ConfiguredModelRouting {
306
+ requested?: string;
307
+ candidates: string[];
308
+ reason?: string;
309
+ /**
310
+ * F7 scope gate verdict. Populated when the caller passed `scopeModelsPatterns`.
311
+ * - `inScope: true` → the resolved model is inside the allowlist (or no allowlist).
312
+ * - `inScope: false, source: "caller"` → caller override is out-of-scope; the
313
+ * function throws `errors.modelOutOfScope` (hard error before spawn) UNLESS
314
+ * the caller marked it as a frontmatter override (`isFrontmatterOverride: true`),
315
+ * in which case the verdict is returned for the caller to log as a warning.
316
+ */
317
+ scopeVerdict?: import("./model-scope.ts").ModelScopeCheck;
318
+ }
319
+
320
+ export function buildConfiguredModelRouting(input: {
321
+ overrideModel?: string;
322
+ stepModel?: string;
323
+ teamRoleModel?: string;
324
+ agentModel?: string;
325
+ fallbackModels?: string[];
326
+ parentModel?: unknown;
327
+ modelRegistry?: unknown;
328
+ cwd?: string;
329
+ /**
330
+ * F7: when set, enforce the enabledModels allowlist. Caller-supplied out-of-
331
+ * scope models throw `errors.modelOutOfScope`; frontmatter-pinned out-of-scope
332
+ * models are returned as a `scopeVerdict` for the caller to log.
333
+ */
334
+ scopeModelsPatterns?: string[];
335
+ /**
336
+ * F7: when true, the `overrideModel` (if any) is treated as a frontmatter
337
+ * (agent) override rather than a per-spawn caller override — out-of-scope
338
+ * is a warning, not a hard error. Used when the agent config is the
339
+ * authoritative source.
340
+ */
341
+ isFrontmatterOverride?: boolean;
342
+ }): ConfiguredModelRouting {
343
+ const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
344
+ const configModels = configuredModelInfosFromPiConfig(input.cwd);
345
+ const availableModels =
346
+ registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
347
+ const parentModel = modelStringFromUnknown(input.parentModel);
348
+ const preferredProvider = parentModel?.split("/")[0] ?? availableModels?.[0]?.provider;
349
+ // B3: Parent model inheritance — when agent has no model specified,
350
+ // inherit from parent session model before falling back to defaults.
351
+ const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : parentModel;
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
+ };
361
+ const rawModels = availableModels
362
+ ? [
363
+ input.overrideModel,
364
+ input.stepModel,
365
+ input.teamRoleModel,
366
+ effectiveAgentModel,
367
+ ...(input.fallbackModels ?? []),
368
+ ...availableModels.map((model) => model.fullId),
369
+ ]
370
+ : [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...(input.fallbackModels ?? []), parentModel];
371
+ // Fix (Round 18): when an agent has `model: false` (frontmatter) the
372
+ // inherited `parentModel` (= session chính's model, e.g. minimax-M3) IS the
373
+ // desired primary. It must NOT be filtered out by isAvailableModel — which
374
+ // only knows about models from models.json / registry, NOT builtin Pi models.
375
+ // Pin the inherited parentModel at index 0 regardless of availability.
376
+ const parentModelRaw = effectiveAgentModel?.trim() || undefined;
377
+ const configuredModels = rawModels
378
+ .filter((model): model is string => Boolean(model?.trim()))
379
+ .filter((model, idx) => {
380
+ if (parentModelRaw && idx === 0 && model.trim() === parentModelRaw) return true;
381
+ return isAvailableModel(model.trim(), availableModels);
382
+ });
383
+ const candidates = buildModelCandidates(configuredModels[0], configuredModels.slice(1), availableModels, preferredProvider);
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;
390
+ // F7 scope gate: when `scopeModelsPatterns` is configured, check the
391
+ // resolved model. Caller-supplied (override/step/team role) out-of-scope
392
+ // is a HARD ERROR (we surface it via the verdict AND throw, so spawn aborts
393
+ // before any cost is incurred). Frontmatter-pinned out-of-scope is a
394
+ // WARNING returned on the verdict for the caller to log.
395
+ let scopeVerdict: ConfiguredModelRouting["scopeVerdict"];
396
+ if (input.scopeModelsPatterns && input.scopeModelsPatterns.length > 0) {
397
+ const resolved = candidates[0] ?? requested;
398
+ const source = input.overrideModel ? "caller" : input.agentModel ? "frontmatter" : "resolved";
399
+ scopeVerdict = checkModelScope(resolved, input.scopeModelsPatterns, source);
400
+ if (!scopeVerdict.inScope && source === "caller" && !input.isFrontmatterOverride) {
401
+ throw errors.modelOutOfScope(resolved ?? "", input.scopeModelsPatterns);
402
+ }
403
+ }
404
+ return { requested, candidates, reason, scopeVerdict };
405
+ }
406
+
407
+ export function buildConfiguredModelCandidates(input: Parameters<typeof buildConfiguredModelRouting>[0]): string[] {
408
+ return buildConfiguredModelRouting(input).candidates;
409
+ }
@@ -0,0 +1,126 @@
1
+ export interface ModelEntry {
2
+ id: string;
3
+ name: string;
4
+ provider: string;
5
+ }
6
+
7
+ /**
8
+ * Core Model interface representing a resolved model instance.
9
+ * Used by resolveModel return type to ensure proper typing.
10
+ */
11
+ export interface Model {
12
+ id: string;
13
+ name?: string;
14
+ provider?: string;
15
+ // Allow additional properties from the registry
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface ModelRegistry {
20
+ find(provider: string, modelId: string): Model | undefined;
21
+ getAll(): Model[];
22
+ getAvailable?(): Model[];
23
+ }
24
+
25
+ /**
26
+ * Resolve a model string to a Model instance.
27
+ * Exact match first ("provider/modelId"), then fuzzy match.
28
+ * Returns Model on success, error message string on failure.
29
+ */
30
+ export function resolveModel(input: string, registry: ModelRegistry): Model | string {
31
+ const all = (registry.getAvailable?.() ?? registry.getAll()) as ModelEntry[];
32
+ const availableSet = new Set(all.map((m) => `${m.provider}/${m.id}`.toLowerCase()));
33
+
34
+ // Exact match
35
+ const slashIdx = input.indexOf("/");
36
+ if (slashIdx !== -1) {
37
+ const provider = input.slice(0, slashIdx);
38
+ const modelId = input.slice(slashIdx + 1);
39
+ if (availableSet.has(input.toLowerCase())) {
40
+ const found = registry.find(provider, modelId);
41
+ if (found) return found;
42
+ }
43
+ }
44
+
45
+ // Fuzzy match
46
+ const query = input.toLowerCase();
47
+ let bestMatch: ModelEntry | undefined;
48
+ let bestScore = 0;
49
+
50
+ for (const m of all) {
51
+ const id = m.id.toLowerCase();
52
+ const name = m.name.toLowerCase();
53
+ const full = `${m.provider}/${m.id}`.toLowerCase();
54
+
55
+ let score = 0;
56
+ if (id === query || full === query) {
57
+ score = 100;
58
+ } else if (id.includes(query) || full.includes(query)) {
59
+ score = 60 + (query.length / id.length) * 30;
60
+ } else if (name.includes(query)) {
61
+ score = 40 + (query.length / name.length) * 20;
62
+ } else if (
63
+ query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
64
+ ) {
65
+ score = 20;
66
+ }
67
+
68
+ if (score > bestScore) {
69
+ bestScore = score;
70
+ bestMatch = m;
71
+ }
72
+ }
73
+
74
+ if (bestMatch && bestScore >= 20) {
75
+ const found = registry.find(bestMatch.provider, bestMatch.id);
76
+ if (found) return found;
77
+ }
78
+
79
+ const modelList = all
80
+ .map((m) => ` ${m.provider}/${m.id}`)
81
+ .sort()
82
+ .join("\n");
83
+ return `Model not found: "${input}".\n\nAvailable models:\n${modelList}`;
84
+ }
85
+
86
+ export interface SimpleModelEntry {
87
+ id: string;
88
+ name?: string;
89
+ provider: string;
90
+ }
91
+
92
+ /**
93
+ * Fuzzy-match a model query against a flat list of available models.
94
+ * Returns the best-match fullId (provider/id) or undefined.
95
+ */
96
+ export function fuzzyResolveModelId(input: string, models: SimpleModelEntry[]): string | undefined {
97
+ const query = input.toLowerCase();
98
+ let bestMatch: SimpleModelEntry | undefined;
99
+ let bestScore = 0;
100
+
101
+ for (const m of models) {
102
+ const id = m.id.toLowerCase();
103
+ const name = (m.name ?? "").toLowerCase();
104
+ const full = `${m.provider}/${m.id}`.toLowerCase();
105
+
106
+ let score = 0;
107
+ if (id === query || full === query) {
108
+ score = 100;
109
+ } else if (id.includes(query) || full.includes(query)) {
110
+ score = 60 + (query.length / id.length) * 30;
111
+ } else if (name.includes(query)) {
112
+ score = 40 + (query.length / (name.length || 1)) * 20;
113
+ } else if (
114
+ query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
115
+ ) {
116
+ score = 20;
117
+ }
118
+
119
+ if (score > bestScore) {
120
+ bestScore = score;
121
+ bestMatch = m;
122
+ }
123
+ }
124
+
125
+ return bestMatch && bestScore >= 20 ? `${bestMatch.provider}/${bestMatch.id}` : undefined;
126
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * model-scope.ts — Opt-in model-scope enforcement (F7).
3
+ *
4
+ * When `runtime.reliability.scopeModels` is enabled, subagent model choices
5
+ * that fall outside the user's pi `enabledModels` allowlist are flagged:
6
+ * - Caller-supplied (per-spawn override / step / team role) out-of-scope
7
+ * → HARD ERROR to orchestrator (fail fast before spawn).
8
+ * - Frontmatter-pinned (AgentConfig.model) out-of-scope
9
+ * → WARNING + runs anyway (frontmatter is authoritative; the agent
10
+ * author made a deliberate choice).
11
+ *
12
+ * Pattern semantics match pi's `--models` CLI / `enabledModels` allowlist:
13
+ * - `"anthropic/claude-opus-4-5"` — exact match (case-insensitive).
14
+ * - `"claude-*"`, `"*sonnet*"`, `"github-copilot/*"` — glob (single `*`).
15
+ * - Any other string — case-insensitive substring fallback (pi's
16
+ * `tryMatchModel` behavior, model-resolver.ts).
17
+ *
18
+ * This module is pure (no I/O, no globals). Reading the actual
19
+ * `enabledModels` from pi's settings is the caller's job (instantiate
20
+ * `SettingsManager.create(cwd, agentDir).getEnabledModels()`).
21
+ *
22
+ * The toggle itself lives in `config/defaults.ts` (`reliability.scopeModels`,
23
+ * default `false` = opt-in, fully back-compat).
24
+ */
25
+
26
+ export type ModelScopeSource = "caller" | "frontmatter" | "resolved" | "fallback";
27
+
28
+ export interface ModelScopeCheck {
29
+ /** True when the model is in scope, or no allowlist is configured. */
30
+ inScope: boolean;
31
+ /** What the model came from. Informational; the gate decision lives in `enforce`. */
32
+ source: ModelScopeSource;
33
+ /** The model id that was checked. */
34
+ model: string;
35
+ /** The pattern(s) that matched, or undefined when no allowlist was configured. */
36
+ matchedPattern?: string;
37
+ /** Human-readable reason for out-of-scope (caller-facing when rejected). */
38
+ reason?: string;
39
+ }
40
+
41
+ /**
42
+ * Convert a glob pattern with `*` wildcards into a RegExp.
43
+ * Escape all regex meta-characters except `*`, which becomes `.*`.
44
+ * Anchored (^...$) and case-insensitive.
45
+ */
46
+ export function patternToRegExp(pattern: string): RegExp {
47
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
48
+ return new RegExp(`${escaped.replace(/\*/g, ".*")}`, "i");
49
+ }
50
+
51
+ /**
52
+ * Does a model id match a single allowlist pattern?
53
+ * Semantics (in order):
54
+ * 1. Exact case-insensitive match.
55
+ * 2. Glob match (pattern contains `*`).
56
+ * 3. Case-insensitive substring match (pi's fallback).
57
+ * Returns true on first hit; false otherwise.
58
+ */
59
+ export function matchesModelPattern(modelId: string, pattern: string): boolean {
60
+ if (!modelId || !pattern) return false;
61
+ const id = modelId.trim();
62
+ const pat = pattern.trim();
63
+ if (!id || !pat) return false;
64
+ if (id.toLowerCase() === pat.toLowerCase()) return true;
65
+ if (pat.includes("*")) {
66
+ try {
67
+ return patternToRegExp(pat).test(id);
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+ return id.toLowerCase().includes(pat.toLowerCase());
73
+ }
74
+
75
+ /**
76
+ * Is the model id accepted by ANY of the allowlist patterns?
77
+ * Returns false when patterns is empty/undefined (caller treats as "no scope").
78
+ */
79
+ export function isModelInScope(modelId: string | undefined, patterns: readonly string[] | undefined): boolean {
80
+ if (!modelId || !patterns || patterns.length === 0) return false;
81
+ return patterns.some((p) => matchesModelPattern(modelId, p));
82
+ }
83
+
84
+ /**
85
+ * Check a model against the allowlist and return a verdict.
86
+ * Returns `inScope: true` with no `reason` when no allowlist is configured
87
+ * (so callers can no-op cleanly).
88
+ */
89
+ export function checkModelScope(
90
+ modelId: string | undefined,
91
+ patterns: readonly string[] | undefined,
92
+ source: ModelScopeSource,
93
+ ): ModelScopeCheck {
94
+ if (!modelId) {
95
+ return {
96
+ inScope: true,
97
+ source,
98
+ model: "",
99
+ reason: "no model specified",
100
+ };
101
+ }
102
+ if (!patterns || patterns.length === 0) {
103
+ // No allowlist → not enforcing. The toggle is opt-in; the user hasn't
104
+ // configured `enabledModels` so there is nothing to enforce against.
105
+ return { inScope: true, source, model: modelId };
106
+ }
107
+ for (const pattern of patterns) {
108
+ if (matchesModelPattern(modelId, pattern)) {
109
+ return {
110
+ inScope: true,
111
+ source,
112
+ model: modelId,
113
+ matchedPattern: pattern,
114
+ };
115
+ }
116
+ }
117
+ return {
118
+ inScope: false,
119
+ source,
120
+ model: modelId,
121
+ reason: `model "${modelId}" is not in enabledModels allowlist (${patterns.join(", ")})`,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Read the user's `enabledModels` allowlist from pi's SettingsManager.
127
+ * Returns an empty array when the SettingsManager export is unavailable, the
128
+ * allowlist is unset, or any error occurs (best-effort, never throws). The
129
+ * caller should still gate on `runtime.reliability.scopeModels` — an empty
130
+ * patterns array is a no-op (nothing to enforce against).
131
+ *
132
+ * @internal Only the runtime spawn layers should call this. Pure module: pure
133
+ * function over a cwd + optional agentDir.
134
+ */
135
+ export async function readEnabledModelsPatterns(cwd: string, agentDir?: string): Promise<string[]> {
136
+ try {
137
+ // Match the pattern live-session-runtime.ts:428 uses to bridge to pi's
138
+ // SDK. SettingsManager is dynamically imported because the module
139
+ // shape differs across pi versions; the create() factory is the
140
+ // canonical, version-stable entry point.
141
+ // LAZY: defer dynamic import of @earendil-works/pi-coding-agent to its call site.
142
+ const mod = await import("@earendil-works/pi-coding-agent" as string).catch(() => null);
143
+ if (!mod) return [];
144
+ const SettingsManagerCtor = (
145
+ mod as {
146
+ SettingsManager?: {
147
+ create?: (cwd: string, agentDir?: string) => { getEnabledModels?: () => string[] | undefined };
148
+ };
149
+ }
150
+ ).SettingsManager;
151
+ if (!SettingsManagerCtor?.create) return [];
152
+ const sm = SettingsManagerCtor.create(cwd, agentDir);
153
+ const patterns = sm.getEnabledModels?.();
154
+ return Array.isArray(patterns) ? patterns : [];
155
+ } catch {
156
+ return [];
157
+ }
158
+ }