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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 1.5 #2 — Git-worktree verification sandbox.
|
|
3
|
+
*
|
|
4
|
+
* RFC: research-findings/goal-workflow/16-PHASE1.5-WORKTREE-SANDBOX-RFC.md
|
|
5
|
+
*
|
|
6
|
+
* Closes the two remaining Med-High tampering residuals from RFC 13 §6:
|
|
7
|
+
* (1) Round-trip manifest tamper (MAJ#2): worker edits package.json, runs
|
|
8
|
+
* test, reverts before T_verify_done → snapshot matches → undetected.
|
|
9
|
+
* (2) Invoked-script tampering: worker rewrites a script the verification
|
|
10
|
+
* command invokes; only MANIFEST_FILES are hashed → invisible.
|
|
11
|
+
*
|
|
12
|
+
* Mitigation: run verification commands in a pristine `git worktree` at the
|
|
13
|
+
* T_snap commit SHA. Worker edits in the main workspace are invisible to
|
|
14
|
+
* the verification subprocess. Content-addressed execution: the test passes
|
|
15
|
+
* (or fails) against the original code, regardless of worker edits.
|
|
16
|
+
*
|
|
17
|
+
* Opt-in via PI_CREW_VERIFICATION_WORKTREE=1 (mirrors Phase 1.5 #1 pattern).
|
|
18
|
+
* Auto-fallback to existing behavior when: not a git repo, dirty index,
|
|
19
|
+
* git unavailable, or opt-out explicitly set. NEVER blocks the goal loop.
|
|
20
|
+
*
|
|
21
|
+
* Pure leaf module: depends only on node: built-ins + git CLI. No imports
|
|
22
|
+
* from goal-loop-runner or verification-gates (keeps unit-testable).
|
|
23
|
+
*/
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
|
|
29
|
+
export interface VerificationWorktree {
|
|
30
|
+
/** Absolute path to the pristine worktree directory. */
|
|
31
|
+
worktreePath: string;
|
|
32
|
+
/** Commit SHA the worktree is checked out at (matches T_snap). */
|
|
33
|
+
commitSha: string;
|
|
34
|
+
/** Cleanup handle — call to remove the worktree + temp dir. Idempotent. */
|
|
35
|
+
cleanup: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Whether the worktree sandbox is enabled (env var opt-in). */
|
|
39
|
+
export function isWorktreeSandboxEnabled(): boolean {
|
|
40
|
+
const v = process.env.PI_CREW_VERIFICATION_WORKTREE ?? process.env.PI_TEAMS_VERIFICATION_WORKTREE;
|
|
41
|
+
return v === "1" || v === "true";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Detect whether the worktree sandbox is AVAILABLE at `cwd`:
|
|
46
|
+
* - opt-in env var set
|
|
47
|
+
* - git executable on PATH
|
|
48
|
+
* - cwd is inside a git repo
|
|
49
|
+
* - git index is clean (no uncommitted changes that would be lost)
|
|
50
|
+
*
|
|
51
|
+
* Returns false (with reason) when any precondition fails. Callers MUST
|
|
52
|
+
* gracefully fall back to non-sandboxed execution — never block the goal.
|
|
53
|
+
*/
|
|
54
|
+
export function checkWorktreeSandboxAvailable(cwd: string): { available: true; commitSha: string } | { available: false; reason: string } {
|
|
55
|
+
if (!isWorktreeSandboxEnabled()) {
|
|
56
|
+
return {
|
|
57
|
+
available: false,
|
|
58
|
+
reason: "PI_CREW_VERIFICATION_WORKTREE not set (opt-in)",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
// Is cwd inside a git repo? `git rev-parse --show-toplevel` errors out
|
|
63
|
+
// (non-zero exit) when not in a repo. execFileSync throws on non-zero.
|
|
64
|
+
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
65
|
+
cwd,
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
encoding: "utf-8",
|
|
68
|
+
}).trim();
|
|
69
|
+
if (!toplevel)
|
|
70
|
+
return {
|
|
71
|
+
available: false,
|
|
72
|
+
reason: "git rev-parse returned empty toplevel",
|
|
73
|
+
};
|
|
74
|
+
// Current commit SHA (this is what T_snap will pin to).
|
|
75
|
+
const commitSha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
encoding: "utf-8",
|
|
79
|
+
}).trim();
|
|
80
|
+
if (!commitSha)
|
|
81
|
+
return {
|
|
82
|
+
available: false,
|
|
83
|
+
reason: "git rev-parse HEAD returned empty SHA",
|
|
84
|
+
};
|
|
85
|
+
// Dirty index? `git status --porcelain` outputs non-empty if there are
|
|
86
|
+
// uncommitted changes. We refuse to sandbox a dirty workspace because
|
|
87
|
+
// the worktree would NOT contain the in-progress edits (T_snap would
|
|
88
|
+
// pin to a stale commit). Better to fall back + warn than silently
|
|
89
|
+
// verify against the wrong code.
|
|
90
|
+
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
91
|
+
cwd,
|
|
92
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
93
|
+
encoding: "utf-8",
|
|
94
|
+
}).trim();
|
|
95
|
+
if (status.length > 0)
|
|
96
|
+
return {
|
|
97
|
+
available: false,
|
|
98
|
+
reason: `dirty git index (${status.split("\n").length} changed files); refusing to sandbox — worktree would pin to stale commit`,
|
|
99
|
+
};
|
|
100
|
+
return { available: true, commitSha };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return {
|
|
104
|
+
available: false,
|
|
105
|
+
reason: `git precondition check failed: ${msg.slice(0, 200)}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Prepare a pristine git worktree at `commitSha`. The worktree is a fresh
|
|
112
|
+
* checkout of the project at that commit — it does NOT contain worker edits
|
|
113
|
+
* from the main workspace.
|
|
114
|
+
*
|
|
115
|
+
* `git worktree add --detach <tmp>/wt-<sha8> <sha>` creates a detached-HEAD
|
|
116
|
+
* worktree (no branch pollution). Returns the worktree path + cleanup handle.
|
|
117
|
+
*
|
|
118
|
+
* Cleanup is idempotent (safe to call multiple times) and best-effort (swallows
|
|
119
|
+
* errors so a stuck worktree doesn't propagate into the goal loop).
|
|
120
|
+
*/
|
|
121
|
+
export function prepareVerificationWorktree(cwd: string, commitSha: string): VerificationWorktree {
|
|
122
|
+
// Temp parent dir under os.tmpdir() so worktrees are auto-cleaned on reboot.
|
|
123
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-wt-"));
|
|
124
|
+
const shortSha = commitSha.slice(0, 8);
|
|
125
|
+
const worktreePath = path.join(tmpRoot, `wt-${shortSha}`);
|
|
126
|
+
let cleaned = false;
|
|
127
|
+
const cleanup = (): void => {
|
|
128
|
+
if (cleaned) return;
|
|
129
|
+
cleaned = true;
|
|
130
|
+
// Remove the worktree (force = proceed even if it has untracked files).
|
|
131
|
+
try {
|
|
132
|
+
execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 5000 });
|
|
133
|
+
} catch {
|
|
134
|
+
// Fall back to `git worktree prune` if remove fails (already gone).
|
|
135
|
+
try {
|
|
136
|
+
execFileSync("git", ["worktree", "prune"], {
|
|
137
|
+
cwd,
|
|
138
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
139
|
+
timeout: 5000,
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
/* best-effort */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Remove the temp parent dir.
|
|
146
|
+
try {
|
|
147
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
148
|
+
} catch {
|
|
149
|
+
/* best-effort */
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
execFileSync("git", ["worktree", "add", "--detach", worktreePath, commitSha], {
|
|
154
|
+
cwd,
|
|
155
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
156
|
+
timeout: 30_000,
|
|
157
|
+
});
|
|
158
|
+
return { worktreePath, commitSha, cleanup };
|
|
159
|
+
} catch (error) {
|
|
160
|
+
cleanup();
|
|
161
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
162
|
+
throw new Error(`git worktree add failed (cwd=${cwd}, sha=${shortSha}): ${msg.slice(0, 300)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* RAII wrapper: prepare worktree, run `fn(worktree)`, ALWAYS cleanup in finally.
|
|
168
|
+
*
|
|
169
|
+
* `fn` may throw — the worktree is removed regardless. The original error
|
|
170
|
+
* propagates (cleanup errors are swallowed and best-effort).
|
|
171
|
+
*
|
|
172
|
+
* If preparation fails, the function rethrows WITHOUT calling fn — caller
|
|
173
|
+
* must handle the prep failure (typically by falling back to non-sandboxed).
|
|
174
|
+
*/
|
|
175
|
+
export async function withVerificationWorktree<T>(
|
|
176
|
+
cwd: string,
|
|
177
|
+
commitSha: string,
|
|
178
|
+
fn: (worktree: VerificationWorktree) => Promise<T> | T,
|
|
179
|
+
): Promise<T> {
|
|
180
|
+
const worktree = prepareVerificationWorktree(cwd, commitSha);
|
|
181
|
+
try {
|
|
182
|
+
return await fn(worktree);
|
|
183
|
+
} finally {
|
|
184
|
+
worktree.cleanup();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface WorkerHeartbeatState {
|
|
2
|
+
workerId: string;
|
|
3
|
+
pid?: number;
|
|
4
|
+
lastSeenAt: string;
|
|
5
|
+
lastStdoutAt?: string;
|
|
6
|
+
lastEventAt?: string;
|
|
7
|
+
turnCount?: number;
|
|
8
|
+
alive?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createWorkerHeartbeat(workerId: string, pid?: number, now = new Date()): WorkerHeartbeatState {
|
|
12
|
+
return { workerId, pid, lastSeenAt: now.toISOString(), alive: true };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function touchWorkerHeartbeat(
|
|
16
|
+
heartbeat: WorkerHeartbeatState,
|
|
17
|
+
updates: Partial<Omit<WorkerHeartbeatState, "workerId">> = {},
|
|
18
|
+
now = new Date(),
|
|
19
|
+
): WorkerHeartbeatState {
|
|
20
|
+
return { ...heartbeat, ...updates, lastSeenAt: now.toISOString() };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isWorkerHeartbeatStale(heartbeat: WorkerHeartbeatState, staleMs: number, now = new Date()): boolean {
|
|
24
|
+
return now.getTime() - Date.parse(heartbeat.lastSeenAt) > staleMs;
|
|
25
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type WorkerLifecycleState = "spawning" | "trust_required" | "ready_for_prompt" | "running" | "finished" | "failed";
|
|
2
|
+
export type StartupFailureClassification =
|
|
3
|
+
| "trust_required"
|
|
4
|
+
| "prompt_misdelivery"
|
|
5
|
+
| "prompt_acceptance_timeout"
|
|
6
|
+
| "transport_dead"
|
|
7
|
+
| "worker_crashed"
|
|
8
|
+
| "rate_limited"
|
|
9
|
+
| "provider_error"
|
|
10
|
+
| "unknown";
|
|
11
|
+
|
|
12
|
+
export interface WorkerStartupEvidence {
|
|
13
|
+
lastLifecycleState: WorkerLifecycleState;
|
|
14
|
+
command: string;
|
|
15
|
+
promptSentAt?: string;
|
|
16
|
+
promptAccepted: boolean;
|
|
17
|
+
trustPromptDetected: boolean;
|
|
18
|
+
transportHealthy: boolean;
|
|
19
|
+
childProcessAlive: boolean;
|
|
20
|
+
elapsedMs: number;
|
|
21
|
+
classification: StartupFailureClassification;
|
|
22
|
+
stderrPreview?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function detectTrustPrompt(text: string): boolean {
|
|
26
|
+
const lowered = text.toLowerCase();
|
|
27
|
+
return (
|
|
28
|
+
lowered.includes("do you trust") ||
|
|
29
|
+
lowered.includes("trust this") ||
|
|
30
|
+
lowered.includes("untrusted") ||
|
|
31
|
+
lowered.includes("workspace trust") ||
|
|
32
|
+
lowered.includes("allow this folder")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function classifyStartupFailure(evidence: Omit<WorkerStartupEvidence, "classification">): StartupFailureClassification {
|
|
37
|
+
if (evidence.stderrPreview && /429|rate.?limit/i.test(evidence.stderrPreview)) return "rate_limited";
|
|
38
|
+
if (evidence.stderrPreview && /5\d{2}|server.?error|internal.?error|provider.?error/i.test(evidence.stderrPreview))
|
|
39
|
+
return "provider_error";
|
|
40
|
+
if (!evidence.transportHealthy) return "transport_dead";
|
|
41
|
+
if (evidence.trustPromptDetected || evidence.lastLifecycleState === "trust_required") return "trust_required";
|
|
42
|
+
if (evidence.promptSentAt && !evidence.promptAccepted && evidence.childProcessAlive) return "prompt_acceptance_timeout";
|
|
43
|
+
if (evidence.promptSentAt && !evidence.promptAccepted && !evidence.childProcessAlive) return "worker_crashed";
|
|
44
|
+
if (
|
|
45
|
+
evidence.stderrPreview?.toLowerCase().includes("command not found") ||
|
|
46
|
+
evidence.stderrPreview?.toLowerCase().includes("not recognized")
|
|
47
|
+
)
|
|
48
|
+
return "prompt_misdelivery";
|
|
49
|
+
if (!evidence.childProcessAlive && evidence.lastLifecycleState !== "finished") return "worker_crashed";
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createStartupEvidence(input: {
|
|
54
|
+
command: string;
|
|
55
|
+
startedAt: Date;
|
|
56
|
+
finishedAt?: Date;
|
|
57
|
+
promptSentAt?: Date;
|
|
58
|
+
promptAccepted?: boolean;
|
|
59
|
+
stderr?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
exitCode?: number | null;
|
|
62
|
+
}): WorkerStartupEvidence {
|
|
63
|
+
const stderrPreview = (input.error || input.stderr || "").slice(0, 500) || undefined;
|
|
64
|
+
const trustPromptDetected = detectTrustPrompt(stderrPreview ?? "");
|
|
65
|
+
const childProcessAlive = input.exitCode === undefined || input.exitCode === null ? !input.finishedAt : false;
|
|
66
|
+
const base: Omit<WorkerStartupEvidence, "classification"> = {
|
|
67
|
+
lastLifecycleState:
|
|
68
|
+
input.error || (input.exitCode !== undefined && input.exitCode !== null && input.exitCode !== 0)
|
|
69
|
+
? "failed"
|
|
70
|
+
: input.finishedAt
|
|
71
|
+
? "finished"
|
|
72
|
+
: "running",
|
|
73
|
+
command: input.command,
|
|
74
|
+
promptSentAt: input.promptSentAt?.toISOString(),
|
|
75
|
+
promptAccepted: input.promptAccepted ?? !input.error,
|
|
76
|
+
trustPromptDetected,
|
|
77
|
+
transportHealthy: !input.error || !/enoent|spawn|transport/i.test(input.error),
|
|
78
|
+
childProcessAlive,
|
|
79
|
+
elapsedMs: Math.max(0, (input.finishedAt ?? new Date()).getTime() - input.startedAt.getTime()),
|
|
80
|
+
stderrPreview,
|
|
81
|
+
};
|
|
82
|
+
return { ...base, classification: classifyStartupFailure(base) };
|
|
83
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Phase State Machine
|
|
3
|
+
*
|
|
4
|
+
* Tracks phase-level progression through a workflow, validating that each
|
|
5
|
+
* phase's declared inputs (required artifacts) are satisfied before it can
|
|
6
|
+
* start. All transition functions are immutable — they return new machine
|
|
7
|
+
* state rather than mutating in place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── Public types ──────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export type PhaseStatus = "pending" | "running" | "completed" | "failed" | "skipped";
|
|
13
|
+
|
|
14
|
+
export interface PhaseGuardContext {
|
|
15
|
+
/** Artifact paths that currently exist on disk. */
|
|
16
|
+
completedArtifacts: string[];
|
|
17
|
+
/** Status of the phase immediately preceding the target phase. */
|
|
18
|
+
previousPhaseStatus: PhaseStatus;
|
|
19
|
+
/** Results from tasks that have already completed. */
|
|
20
|
+
taskResults: Array<{ taskId: string; status: string; outputPath?: string }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GuardResult {
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PhaseTransition {
|
|
29
|
+
from: PhaseStatus;
|
|
30
|
+
to: PhaseStatus;
|
|
31
|
+
guard?: (context: PhaseGuardContext) => GuardResult;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PhaseState {
|
|
35
|
+
name: string;
|
|
36
|
+
status: PhaseStatus;
|
|
37
|
+
/** Artifact names required before this phase can start. */
|
|
38
|
+
inputs: string[];
|
|
39
|
+
/** Artifact names this phase produces. */
|
|
40
|
+
outputs: string[];
|
|
41
|
+
startedAt?: string;
|
|
42
|
+
finishedAt?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WorkflowStateMachine {
|
|
46
|
+
phases: PhaseState[];
|
|
47
|
+
currentPhaseIndex: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new workflow state machine from an initial set of phases.
|
|
54
|
+
* All phases start as "pending" and the current index is set to 0.
|
|
55
|
+
*/
|
|
56
|
+
export function createWorkflowStateMachine(phases: PhaseState[]): WorkflowStateMachine {
|
|
57
|
+
return {
|
|
58
|
+
phases: phases.map((phase) => ({
|
|
59
|
+
...phase,
|
|
60
|
+
status: phase.status ?? "pending",
|
|
61
|
+
})),
|
|
62
|
+
currentPhaseIndex: 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Queries ───────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Return the declared inputs for a given phase index.
|
|
70
|
+
* Returns an empty array for out-of-bounds indices.
|
|
71
|
+
*/
|
|
72
|
+
export function getPhaseInputs(machine: WorkflowStateMachine, phaseIndex: number): string[] {
|
|
73
|
+
const phase = machine.phases[phaseIndex];
|
|
74
|
+
if (!phase) return [];
|
|
75
|
+
return phase.inputs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check whether a transition to a specific phase is allowed.
|
|
80
|
+
*
|
|
81
|
+
* Rules (evaluated in order):
|
|
82
|
+
* 1. `phaseIndex` must be in bounds.
|
|
83
|
+
* 2. If this is not the first phase, the previous phase must be "completed" or "skipped".
|
|
84
|
+
* 3. All declared `inputs` for the target phase must appear in `completedArtifacts`.
|
|
85
|
+
* 4. If a custom `guard` is supplied on a transition, it must also approve.
|
|
86
|
+
*/
|
|
87
|
+
export function canTransitionToPhase(machine: WorkflowStateMachine, phaseIndex: number, context: PhaseGuardContext): GuardResult {
|
|
88
|
+
if (phaseIndex < 0 || phaseIndex >= machine.phases.length) {
|
|
89
|
+
return {
|
|
90
|
+
allowed: false,
|
|
91
|
+
reason: `Phase index ${phaseIndex} is out of bounds (0..${machine.phases.length - 1}).`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Previous phase must have completed (or been skipped).
|
|
96
|
+
if (phaseIndex > 0) {
|
|
97
|
+
const prevStatus = context.previousPhaseStatus;
|
|
98
|
+
if (prevStatus !== "completed" && prevStatus !== "skipped") {
|
|
99
|
+
return {
|
|
100
|
+
allowed: false,
|
|
101
|
+
reason: `Previous phase status is '${prevStatus}'; expected 'completed' or 'skipped'.`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// All declared inputs must be satisfied.
|
|
107
|
+
const phase = machine.phases[phaseIndex]!;
|
|
108
|
+
if (phase.inputs.length > 0) {
|
|
109
|
+
const artifactSet = new Set(context.completedArtifacts);
|
|
110
|
+
const missing = phase.inputs.filter((input) => !artifactSet.has(input));
|
|
111
|
+
if (missing.length > 0) {
|
|
112
|
+
return {
|
|
113
|
+
allowed: false,
|
|
114
|
+
reason: `Missing required artifacts: ${missing.join(", ")}.`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { allowed: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validate that all preconditions for the *current* phase are met.
|
|
124
|
+
* Returns the subset of declared inputs that are not yet present in `completedArtifacts`.
|
|
125
|
+
*/
|
|
126
|
+
export function validatePhasePreconditions(
|
|
127
|
+
machine: WorkflowStateMachine,
|
|
128
|
+
context: PhaseGuardContext,
|
|
129
|
+
): { ready: boolean; blocking: string[] } {
|
|
130
|
+
const phase = machine.phases[machine.currentPhaseIndex];
|
|
131
|
+
if (!phase) {
|
|
132
|
+
return {
|
|
133
|
+
ready: false,
|
|
134
|
+
blocking: [`No phase at index ${machine.currentPhaseIndex}.`],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const artifactSet = new Set(context.completedArtifacts);
|
|
139
|
+
const missing = phase.inputs.filter((input) => !artifactSet.has(input));
|
|
140
|
+
return { ready: missing.length === 0, blocking: missing };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Transitions ───────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/** Result of a phase transition: the (potentially unchanged) machine and an optional guard result. */
|
|
146
|
+
export interface TransitionResult {
|
|
147
|
+
machine: WorkflowStateMachine;
|
|
148
|
+
guardResult?: GuardResult;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Transition a phase to a new status. Returns a `TransitionResult` containing
|
|
153
|
+
* the new `WorkflowStateMachine` and an optional `guardResult` when the guard
|
|
154
|
+
* was checked.
|
|
155
|
+
*
|
|
156
|
+
* If `context` is provided and the guard fails, the machine is returned
|
|
157
|
+
* **UNCHANGED** (the phase is NOT auto-failed) along with the `guardResult`
|
|
158
|
+
* so the caller can decide what to do.
|
|
159
|
+
*
|
|
160
|
+
* For the "pending → running" transition the `startedAt` timestamp is set.
|
|
161
|
+
* For terminal transitions ("completed", "failed", "skipped") the `finishedAt` timestamp is set.
|
|
162
|
+
* On success the `currentPhaseIndex` advances to `phaseIndex` (or stays if already there).
|
|
163
|
+
*/
|
|
164
|
+
export function transitionPhase(
|
|
165
|
+
machine: WorkflowStateMachine,
|
|
166
|
+
phaseIndex: number,
|
|
167
|
+
status: PhaseStatus,
|
|
168
|
+
context?: PhaseGuardContext,
|
|
169
|
+
): TransitionResult {
|
|
170
|
+
if (context) {
|
|
171
|
+
const check = canTransitionToPhase(machine, phaseIndex, context);
|
|
172
|
+
if (!check.allowed) {
|
|
173
|
+
// Return machine UNCHANGED with guard result — caller decides what to do
|
|
174
|
+
return { machine, guardResult: check };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
const phases = machine.phases.map((phase, index) => {
|
|
180
|
+
if (index !== phaseIndex) return phase;
|
|
181
|
+
return {
|
|
182
|
+
...phase,
|
|
183
|
+
status,
|
|
184
|
+
...(status === "running" && !phase.startedAt ? { startedAt: now } : {}),
|
|
185
|
+
...((status === "completed" || status === "failed" || status === "skipped") && !phase.finishedAt ? { finishedAt: now } : {}),
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
machine: {
|
|
191
|
+
phases,
|
|
192
|
+
currentPhaseIndex: Math.max(machine.currentPhaseIndex, phaseIndex),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|