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.
- package/CHANGELOG.md +45 -0
- package/package.json +6 -3
- package/scripts/clean-strip-types.mjs +60 -0
- package/src/adapters/claude-adapter.ts +23 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +143 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +194 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -0
- package/src/agents/discover-agents.ts +623 -0
- package/src/benchmark/benchmark-runner.ts +313 -0
- package/src/benchmark/feedback-loop.ts +73 -0
- package/src/config/config.ts +1270 -0
- package/src/config/defaults.ts +193 -0
- package/src/config/drift-detector.ts +290 -0
- package/src/config/markers.ts +330 -0
- package/src/config/resilient-parser.ts +117 -0
- package/src/config/role-tools.ts +118 -0
- package/src/config/suggestions.ts +75 -0
- package/src/config/types.ts +280 -0
- package/src/errors.ts +194 -0
- package/src/extension/action-suggestions.ts +117 -0
- package/src/extension/async-notifier.ts +179 -0
- package/src/extension/autonomous-policy.ts +209 -0
- package/src/extension/command-completions.ts +120 -0
- package/src/extension/context-status-injection.ts +185 -0
- package/src/extension/crew-autocomplete.ts +131 -0
- package/src/extension/crew-cleanup.ts +168 -0
- package/src/extension/crew-input-router.ts +93 -0
- package/src/extension/crew-shortcuts.ts +87 -0
- package/src/extension/crew-vibes/cat-frames.ts +18 -0
- package/src/extension/crew-vibes/config.ts +195 -0
- package/src/extension/crew-vibes/figures.ts +101 -0
- package/src/extension/crew-vibes/font-detect.ts +71 -0
- package/src/extension/crew-vibes/footer.ts +292 -0
- package/src/extension/crew-vibes/index.ts +432 -0
- package/src/extension/crew-vibes/provider-usage.ts +396 -0
- package/src/extension/crew-vibes/render.ts +206 -0
- package/src/extension/crew-vibes/speed.ts +286 -0
- package/src/extension/cross-extension-rpc.ts +350 -0
- package/src/extension/help.ts +62 -0
- package/src/extension/import-index.ts +74 -0
- package/src/extension/knowledge-injection.ts +402 -0
- package/src/extension/management.ts +571 -0
- package/src/extension/message-renderers.ts +108 -0
- package/src/extension/notification-router.ts +152 -0
- package/src/extension/notification-sink.ts +54 -0
- package/src/extension/pi-api.ts +56 -0
- package/src/extension/plan-orchestrate.ts +302 -0
- package/src/extension/project-init.ts +170 -0
- package/src/extension/register.ts +113 -0
- package/src/extension/registration/artifact-cleanup.ts +20 -0
- package/src/extension/registration/command-registration.ts +58 -0
- package/src/extension/registration/command-utils.ts +58 -0
- package/src/extension/registration/commands.ts +1228 -0
- package/src/extension/registration/compaction-guard.ts +333 -0
- package/src/extension/registration/context-builder.ts +135 -0
- package/src/extension/registration/crash-recovery-cache.ts +54 -0
- package/src/extension/registration/foreground-run-controller.ts +238 -0
- package/src/extension/registration/hook-registration.ts +119 -0
- package/src/extension/registration/lazy-configurers.ts +124 -0
- package/src/extension/registration/lifecycle-handlers.ts +923 -0
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +322 -0
- package/src/extension/registration/registration-types.ts +158 -0
- package/src/extension/registration/runtime-cleanup.ts +230 -0
- package/src/extension/registration/subagent-helpers.ts +125 -0
- package/src/extension/registration/subagent-manager-setup.ts +330 -0
- package/src/extension/registration/subagent-tools.ts +456 -0
- package/src/extension/registration/team-tool.ts +225 -0
- package/src/extension/registration/tool-registration.ts +50 -0
- package/src/extension/registration/ui.ts +172 -0
- package/src/extension/registration/viewers.ts +121 -0
- package/src/extension/registration/wire-cross-extension.ts +37 -0
- package/src/extension/result-watcher.ts +139 -0
- package/src/extension/rpc-hmac.ts +256 -0
- package/src/extension/run-bundle-schema.ts +104 -0
- package/src/extension/run-export.ts +97 -0
- package/src/extension/run-import.ts +168 -0
- package/src/extension/run-index.ts +131 -0
- package/src/extension/run-maintenance.ts +190 -0
- package/src/extension/session-summary.ts +18 -0
- package/src/extension/team-manager-command.ts +153 -0
- package/src/extension/team-onboard.ts +180 -0
- package/src/extension/team-recommendation.ts +288 -0
- package/src/extension/team-tool/anchor.ts +172 -0
- package/src/extension/team-tool/api.ts +1244 -0
- package/src/extension/team-tool/auto-summarize.ts +142 -0
- package/src/extension/team-tool/cache-control.ts +19 -0
- package/src/extension/team-tool/cancel.ts +337 -0
- package/src/extension/team-tool/chain-dispatch.ts +95 -0
- package/src/extension/team-tool/chain-executor.ts +379 -0
- package/src/extension/team-tool/config-patch.ts +50 -0
- package/src/extension/team-tool/context.ts +149 -0
- package/src/extension/team-tool/destructive-gate.ts +52 -0
- package/src/extension/team-tool/dispatch/automate.ts +80 -0
- package/src/extension/team-tool/dispatch/control.ts +42 -0
- package/src/extension/team-tool/dispatch/index.ts +95 -0
- package/src/extension/team-tool/dispatch/manage.ts +161 -0
- package/src/extension/team-tool/dispatch/run.ts +53 -0
- package/src/extension/team-tool/dispatch/status.ts +187 -0
- package/src/extension/team-tool/doctor.ts +431 -0
- package/src/extension/team-tool/explain.ts +282 -0
- package/src/extension/team-tool/failure-patterns.ts +118 -0
- package/src/extension/team-tool/goal-wrap.ts +330 -0
- package/src/extension/team-tool/goal.ts +556 -0
- package/src/extension/team-tool/handle-schedule.ts +332 -0
- package/src/extension/team-tool/handle-settings.ts +520 -0
- package/src/extension/team-tool/health-monitor.ts +481 -0
- package/src/extension/team-tool/inspect.ts +88 -0
- package/src/extension/team-tool/intent-policy.ts +45 -0
- package/src/extension/team-tool/lifecycle-actions.ts +615 -0
- package/src/extension/team-tool/orchestrate.ts +98 -0
- package/src/extension/team-tool/parallel-dispatch.ts +183 -0
- package/src/extension/team-tool/plan.ts +50 -0
- package/src/extension/team-tool/respond.ts +150 -0
- package/src/extension/team-tool/run-deadline.ts +66 -0
- package/src/extension/team-tool/run-not-found.ts +49 -0
- package/src/extension/team-tool/run.ts +1051 -0
- package/src/extension/team-tool/status.ts +260 -0
- package/src/extension/team-tool/workflow-manage.ts +261 -0
- package/src/extension/team-tool-types.ts +26 -0
- package/src/extension/team-tool.ts +750 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/extension/validate-resources.ts +108 -0
- package/src/hooks/registry.ts +200 -0
- package/src/hooks/types.ts +69 -0
- package/src/i18n.ts +212 -0
- package/src/observability/correlation.ts +50 -0
- package/src/observability/event-bus.ts +86 -0
- package/src/observability/event-to-metric.ts +170 -0
- package/src/observability/exporters/adapter.ts +27 -0
- package/src/observability/exporters/otlp-exporter.ts +356 -0
- package/src/observability/exporters/prometheus-exporter.ts +54 -0
- package/src/observability/metric-registry.ts +100 -0
- package/src/observability/metric-retention.ts +64 -0
- package/src/observability/metric-sink.ts +99 -0
- package/src/observability/metrics-primitives.ts +219 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +10 -0
- package/src/plugins/plugins/vitest.ts +9 -0
- package/src/prompt/prompt-runtime.ts +380 -0
- package/src/runtime/adaptive-plan.ts +563 -0
- package/src/runtime/agent-control.ts +215 -0
- package/src/runtime/agent-memory.ts +81 -0
- package/src/runtime/agent-observability.ts +122 -0
- package/src/runtime/anchor-manager.ts +475 -0
- package/src/runtime/async-marker.ts +32 -0
- package/src/runtime/async-runner.ts +381 -0
- package/src/runtime/attention-events.ts +28 -0
- package/src/runtime/auto-summarize.ts +344 -0
- package/src/runtime/background-runner.ts +840 -0
- package/src/runtime/batch-barrier.ts +147 -0
- package/src/runtime/broker-issuer.ts +39 -0
- package/src/runtime/cancellation-token.ts +99 -0
- package/src/runtime/cancellation.ts +99 -0
- package/src/runtime/capability-inventory.ts +137 -0
- package/src/runtime/chain-parser.ts +237 -0
- package/src/runtime/chain-runner.ts +606 -0
- package/src/runtime/checkpoint.ts +291 -0
- package/src/runtime/child-pi-constants.ts +42 -0
- package/src/runtime/child-pi-kill.ts +180 -0
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi-spawn.ts +287 -0
- package/src/runtime/child-pi-steering.ts +128 -0
- package/src/runtime/child-pi-streams.ts +296 -0
- package/src/runtime/child-pi-transcript.ts +169 -0
- package/src/runtime/child-pi.ts +1105 -0
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +292 -0
- package/src/runtime/command-trace.ts +105 -0
- package/src/runtime/compact-pipeline.ts +56 -0
- package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
- package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
- package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
- package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
- package/src/runtime/compact-stages/index.ts +23 -0
- package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
- package/src/runtime/compact-stages/truncation-stage.ts +74 -0
- package/src/runtime/compaction-summary.ts +278 -0
- package/src/runtime/completion-guard.ts +207 -0
- package/src/runtime/concurrency.ts +58 -0
- package/src/runtime/crash-classification.ts +229 -0
- package/src/runtime/crash-recovery.ts +594 -0
- package/src/runtime/crew-agent-records.ts +595 -0
- package/src/runtime/crew-agent-runtime.ts +62 -0
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +159 -0
- package/src/runtime/crew-broker.ts +1304 -0
- package/src/runtime/crew-hooks.ts +227 -0
- package/src/runtime/cross-extension-rpc.ts +143 -0
- package/src/runtime/custom-tools/irc-tool.ts +282 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
- package/src/runtime/deadletter.ts +47 -0
- package/src/runtime/delivery-coordinator.ts +211 -0
- package/src/runtime/delta-conflict.ts +348 -0
- package/src/runtime/deterministic-ast.ts +161 -0
- package/src/runtime/diagnostic-export.ts +171 -0
- package/src/runtime/direct-run.ts +45 -0
- package/src/runtime/dwf-state-store.ts +102 -0
- package/src/runtime/dynamic-workflow-context.ts +1013 -0
- package/src/runtime/dynamic-workflow-runner.ts +325 -0
- package/src/runtime/effectiveness.ts +90 -0
- package/src/runtime/errors/crew-errors.ts +162 -0
- package/src/runtime/event-stream-bridge.ts +98 -0
- package/src/runtime/foreground-control.ts +193 -0
- package/src/runtime/foreground-watchdog.ts +132 -0
- package/src/runtime/global-worker-cap.ts +96 -0
- package/src/runtime/goal-achievement.ts +148 -0
- package/src/runtime/goal-evaluator.ts +365 -0
- package/src/runtime/goal-loop-runner.ts +825 -0
- package/src/runtime/goal-state-store.ts +219 -0
- package/src/runtime/green-contract.ts +55 -0
- package/src/runtime/group-join.ts +267 -0
- package/src/runtime/handoff-manager.ts +598 -0
- package/src/runtime/heartbeat-gradient.ts +36 -0
- package/src/runtime/heartbeat-watcher.ts +239 -0
- package/src/runtime/hidden-handoff.ts +407 -0
- package/src/runtime/important-line-classifier.ts +140 -0
- package/src/runtime/intercom-bridge.ts +187 -0
- package/src/runtime/iteration-hooks.ts +305 -0
- package/src/runtime/live-agent-control.ts +136 -0
- package/src/runtime/live-agent-manager.ts +704 -0
- package/src/runtime/live-control-realtime.ts +56 -0
- package/src/runtime/live-extension-bridge.ts +148 -0
- package/src/runtime/live-irc.ts +97 -0
- package/src/runtime/live-session-health.ts +108 -0
- package/src/runtime/live-session-runtime.ts +1146 -0
- package/src/runtime/loop-gates.ts +128 -0
- package/src/runtime/manifest-cache.ts +375 -0
- package/src/runtime/mcp-proxy.ts +105 -0
- package/src/runtime/metric-parser.ts +36 -0
- package/src/runtime/model-fallback.ts +409 -0
- package/src/runtime/model-resolver.ts +126 -0
- package/src/runtime/model-scope.ts +158 -0
- package/src/runtime/orphan-worker-registry.ts +465 -0
- package/src/runtime/output-validator.ts +202 -0
- package/src/runtime/overflow-recovery.ts +206 -0
- package/src/runtime/parallel-research.ts +68 -0
- package/src/runtime/parallel-utils.ts +160 -0
- package/src/runtime/parent-guard.ts +134 -0
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +292 -0
- package/src/runtime/per-write-validator.ts +181 -0
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/phase-tracker.ts +385 -0
- package/src/runtime/pi-args.ts +655 -0
- package/src/runtime/pi-json-output.ts +170 -0
- package/src/runtime/pi-spawn.ts +273 -0
- package/src/runtime/pipeline-runner.ts +523 -0
- package/src/runtime/plan-templates.ts +201 -0
- package/src/runtime/policy-engine.ts +115 -0
- package/src/runtime/post-checks.ts +142 -0
- package/src/runtime/post-exit-stdio-guard.ts +109 -0
- package/src/runtime/process-lifecycle.ts +491 -0
- package/src/runtime/process-status.ts +154 -0
- package/src/runtime/progress-event-coalescer.ts +44 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/prose-compressor.ts +162 -0
- package/src/runtime/recovery-recipes.ts +203 -0
- package/src/runtime/replace.ts +570 -0
- package/src/runtime/resilient-edit.ts +153 -0
- package/src/runtime/result-extractor.ts +217 -0
- package/src/runtime/retry-executor.ts +109 -0
- package/src/runtime/retry-runner.ts +336 -0
- package/src/runtime/role-permission.ts +59 -0
- package/src/runtime/run-coalesced-task-group.ts +308 -0
- package/src/runtime/run-drift.ts +219 -0
- package/src/runtime/run-tracker.ts +116 -0
- package/src/runtime/run-worker.ts +78 -0
- package/src/runtime/runtime-policy.ts +29 -0
- package/src/runtime/runtime-resolver.ts +172 -0
- package/src/runtime/runtime-warmup.ts +160 -0
- package/src/runtime/scheduler.ts +353 -0
- package/src/runtime/semaphore.ts +141 -0
- package/src/runtime/sensitive-paths.ts +93 -0
- package/src/runtime/session-resources.ts +25 -0
- package/src/runtime/session-snapshot.ts +59 -0
- package/src/runtime/session-usage.ts +79 -0
- package/src/runtime/settings-store.ts +155 -0
- package/src/runtime/sidechain-output.ts +35 -0
- package/src/runtime/single-agent-compose.ts +84 -0
- package/src/runtime/skill-effectiveness.ts +524 -0
- package/src/runtime/skill-instructions.ts +394 -0
- package/src/runtime/stale-reconciler.ts +680 -0
- package/src/runtime/stream-preview.ts +184 -0
- package/src/runtime/streaming-output.ts +50 -0
- package/src/runtime/subagent-manager.ts +561 -0
- package/src/runtime/subprocess-tool-registry.ts +70 -0
- package/src/runtime/supervisor-contact.ts +64 -0
- package/src/runtime/task-display.ts +52 -0
- package/src/runtime/task-graph-scheduler.ts +227 -0
- package/src/runtime/task-graph.ts +290 -0
- package/src/runtime/task-health.ts +83 -0
- package/src/runtime/task-id.ts +161 -0
- package/src/runtime/task-output-context.ts +602 -0
- package/src/runtime/task-packet.ts +268 -0
- package/src/runtime/task-quality.ts +199 -0
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/child-executor.ts +790 -0
- package/src/runtime/task-runner/context-retrieval.ts +163 -0
- package/src/runtime/task-runner/live-executor.ts +227 -0
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/post-execution.ts +480 -0
- package/src/runtime/task-runner/pre-execution.ts +365 -0
- package/src/runtime/task-runner/progress.ts +157 -0
- package/src/runtime/task-runner/prompt-builder.ts +278 -0
- package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
- package/src/runtime/task-runner/result-utils.ts +16 -0
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +125 -0
- package/src/runtime/task-runner/scaffold-executor.ts +25 -0
- package/src/runtime/task-runner/state-helpers.ts +176 -0
- package/src/runtime/task-runner/tail-read.ts +34 -0
- package/src/runtime/task-runner.ts +216 -0
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +2396 -0
- package/src/runtime/tool-output-pruner.ts +333 -0
- package/src/runtime/tool-progress.ts +278 -0
- package/src/runtime/usage-tracker.ts +73 -0
- package/src/runtime/verification-gates.ts +579 -0
- package/src/runtime/verification-integrity.ts +108 -0
- package/src/runtime/verification-worktree.ts +186 -0
- package/src/runtime/worker-heartbeat.ts +25 -0
- package/src/runtime/worker-startup.ts +83 -0
- package/src/runtime/workflow-state.ts +195 -0
- package/src/runtime/workspace-lock.ts +443 -0
- package/src/runtime/workspace-tree.ts +312 -0
- package/src/runtime/yield-handler.ts +224 -0
- package/src/runtime/zombie-scanner.ts +298 -0
- package/src/schema/config-schema.ts +327 -0
- package/src/schema/team-tool-schema.ts +492 -0
- package/src/schema/validation-types.ts +159 -0
- package/src/skills/discover-skills.ts +203 -0
- package/src/skills/skill-templates.ts +456 -0
- package/src/skills/validate.ts +285 -0
- package/src/state/active-run-registry.ts +439 -0
- package/src/state/artifact-store.ts +176 -0
- package/src/state/atomic-write.ts +970 -0
- package/src/state/blob-store.ts +308 -0
- package/src/state/contracts.ts +160 -0
- package/src/state/crew-init.ts +269 -0
- package/src/state/decision-ledger.ts +372 -0
- package/src/state/event-log-rotation.ts +399 -0
- package/src/state/event-log.ts +1465 -0
- package/src/state/event-reconstructor.ts +229 -0
- package/src/state/gitignore-manager.ts +46 -0
- package/src/state/health-store.ts +79 -0
- package/src/state/hook-instinct-bridge.ts +94 -0
- package/src/state/hook-integrations.ts +51 -0
- package/src/state/instinct-store.ts +275 -0
- package/src/state/jsonl-writer.ts +107 -0
- package/src/state/locks.ts +562 -0
- package/src/state/mailbox.ts +916 -0
- package/src/state/observation-store.ts +176 -0
- package/src/state/run-cache.ts +193 -0
- package/src/state/run-graph.ts +183 -0
- package/src/state/run-metrics.ts +165 -0
- package/src/state/schedule.ts +166 -0
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +973 -0
- package/src/state/task-claims.ts +61 -0
- package/src/state/tiered-eval.ts +480 -0
- package/src/state/types-eval.ts +58 -0
- package/src/state/types.ts +447 -0
- package/src/state/usage.ts +138 -0
- package/src/state/worker-atomic-writer.ts +198 -0
- package/src/subagents/async-entry.ts +1 -0
- package/src/subagents/index.ts +3 -0
- package/src/subagents/live/control.ts +1 -0
- package/src/subagents/live/manager.ts +1 -0
- package/src/subagents/live/realtime.ts +1 -0
- package/src/subagents/live/session-runtime.ts +1 -0
- package/src/subagents/manager.ts +1 -0
- package/src/subagents/spawn.ts +1 -0
- package/src/teams/discover-teams.ts +187 -0
- package/src/teams/team-config.ts +27 -0
- package/src/teams/team-serializer.ts +38 -0
- package/src/tools/safe-bash-extension.ts +54 -0
- package/src/tools/safe-bash.ts +505 -0
- package/src/types/diff.d.ts +18 -0
- package/src/types/new-api-types.ts +35 -0
- package/src/ui/agent-management-overlay.ts +160 -0
- package/src/ui/card-colors.ts +139 -0
- package/src/ui/crew-footer.ts +102 -0
- package/src/ui/crew-select-list.ts +112 -0
- package/src/ui/dashboard-panes/agents-pane.ts +164 -0
- package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
- package/src/ui/dashboard-panes/capability-pane.ts +77 -0
- package/src/ui/dashboard-panes/health-pane.ts +31 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
- package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
- package/src/ui/dashboard-panes/progress-pane.ts +38 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
- package/src/ui/deploy-bundled-themes.ts +71 -0
- package/src/ui/dwf-phase-display.ts +151 -0
- package/src/ui/dynamic-border.ts +35 -0
- package/src/ui/format-helpers.ts +31 -0
- package/src/ui/heartbeat-aggregator.ts +74 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +248 -0
- package/src/ui/layout-primitives.ts +106 -0
- package/src/ui/live-conversation-overlay.ts +183 -0
- package/src/ui/live-duration.ts +55 -0
- package/src/ui/live-run-sidebar.ts +293 -0
- package/src/ui/loaders.ts +6 -0
- package/src/ui/mascot.ts +449 -0
- package/src/ui/overlays/agent-picker-overlay.ts +66 -0
- package/src/ui/overlays/confirm-overlay.ts +60 -0
- package/src/ui/overlays/help-overlay.ts +177 -0
- package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
- package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
- package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
- package/src/ui/pi-ui-compat.ts +68 -0
- package/src/ui/powerbar-publisher.ts +459 -0
- package/src/ui/render-coalescer.ts +101 -0
- package/src/ui/render-diff.ts +160 -0
- package/src/ui/render-scheduler.ts +278 -0
- package/src/ui/run-action-dispatcher.ts +182 -0
- package/src/ui/run-dashboard.ts +910 -0
- package/src/ui/run-event-bus.ts +381 -0
- package/src/ui/run-snapshot-cache.ts +1057 -0
- package/src/ui/settings-overlay.ts +1067 -0
- package/src/ui/shared-overlay-scheduler.ts +96 -0
- package/src/ui/snapshot-types.ts +90 -0
- package/src/ui/spinner.ts +17 -0
- package/src/ui/status-colors.ts +143 -0
- package/src/ui/syntax-highlight.ts +136 -0
- package/src/ui/terminal-status.ts +269 -0
- package/src/ui/theme-adapter.ts +271 -0
- package/src/ui/theme-discovery.ts +199 -0
- package/src/ui/tool-progress-formatter.ts +93 -0
- package/src/ui/tool-renderers/brief-mode.ts +225 -0
- package/src/ui/tool-renderers/index.ts +726 -0
- package/src/ui/transcript-cache.ts +128 -0
- package/src/ui/transcript-entries.ts +256 -0
- package/src/ui/transcript-viewer.ts +424 -0
- package/src/ui/widget/index.ts +418 -0
- package/src/ui/widget/widget-formatters.ts +182 -0
- package/src/ui/widget/widget-model.ts +110 -0
- package/src/ui/widget/widget-renderer.ts +182 -0
- package/src/ui/widget/widget-types.ts +36 -0
- package/src/utils/bm25-search.ts +235 -0
- package/src/utils/completion-dedupe.ts +63 -0
- package/src/utils/conflict-detect.ts +668 -0
- package/src/utils/env-allowlist.ts +30 -0
- package/src/utils/env-filter.ts +155 -0
- package/src/utils/file-coalescer.ts +98 -0
- package/src/utils/fingerprint.ts +180 -0
- package/src/utils/frontmatter.ts +70 -0
- package/src/utils/fs-watch.ts +37 -0
- package/src/utils/gh-protocol.ts +556 -0
- package/src/utils/git.ts +260 -0
- package/src/utils/guards.ts +107 -0
- package/src/utils/ids.ts +24 -0
- package/src/utils/incremental-reader.ts +220 -0
- package/src/utils/internal-error.ts +10 -0
- package/src/utils/names.ts +36 -0
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/paths.ts +227 -0
- package/src/utils/project-detector.ts +160 -0
- package/src/utils/redaction.ts +370 -0
- package/src/utils/resolve-shell.ts +36 -0
- package/src/utils/run-watcher-registry.ts +152 -0
- package/src/utils/safe-paths.ts +393 -0
- package/src/utils/scan-cache.ts +144 -0
- package/src/utils/session-utils.ts +110 -0
- package/src/utils/sleep.ts +54 -0
- package/src/utils/socket-path.ts +164 -0
- package/src/utils/sse-parser.ts +131 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/timings.ts +33 -0
- package/src/utils/token-counter.ts +200 -0
- package/src/utils/visual.ts +207 -0
- package/src/workflows/cost-estimator.ts +34 -0
- package/src/workflows/discover-workflows.ts +308 -0
- package/src/workflows/intermediate-store.ts +166 -0
- package/src/workflows/preflight-validator.ts +168 -0
- package/src/workflows/topology-analyzer.ts +203 -0
- package/src/workflows/validate-workflow.ts +50 -0
- package/src/workflows/workflow-config.ts +75 -0
- package/src/workflows/workflow-serializer.ts +32 -0
- package/src/worktree/branch-freshness.ts +112 -0
- package/src/worktree/cleanup.ts +341 -0
- 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
|
+
}
|