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,176 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
3
|
+
import { atomicWriteJson } from "./atomic-write.ts";
|
|
4
|
+
|
|
5
|
+
// ── Types ────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface Observation {
|
|
8
|
+
tool: string;
|
|
9
|
+
input: string;
|
|
10
|
+
output: string;
|
|
11
|
+
filesRead: string[];
|
|
12
|
+
filesModified: string[];
|
|
13
|
+
timestamp: number;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
taskId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CompressedObservation {
|
|
19
|
+
summary: string;
|
|
20
|
+
patterns: string[];
|
|
21
|
+
decisions: string[];
|
|
22
|
+
filesAffected: string[];
|
|
23
|
+
relevanceScore: number;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ObservationStoreConfig {
|
|
29
|
+
maxObservations: number;
|
|
30
|
+
maxCompressed: number;
|
|
31
|
+
privacyTags: string[]; // tags to strip before storage
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEFAULT_CONFIG: ObservationStoreConfig = {
|
|
35
|
+
maxObservations: 1000,
|
|
36
|
+
maxCompressed: 200,
|
|
37
|
+
privacyTags: ["<private>", "<secret>", "<credentials>"],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ── Privacy ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Strip privacy-tagged content from a string.
|
|
44
|
+
*/
|
|
45
|
+
export function stripPrivacyTags(content: string, config = DEFAULT_CONFIG): string {
|
|
46
|
+
let result = content;
|
|
47
|
+
for (const tag of config.privacyTags) {
|
|
48
|
+
const openTag = tag;
|
|
49
|
+
const closeTag = tag.replace("<", "</");
|
|
50
|
+
// Remove everything between open and close tags
|
|
51
|
+
const regex = new RegExp(`${escapeRegex(openTag)}[\\s\\S]*?${escapeRegex(closeTag)}`, "gi");
|
|
52
|
+
result = result.replace(regex, "[REDACTED]");
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function escapeRegex(str: string): string {
|
|
58
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Observation Store ────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export class ObservationStore {
|
|
64
|
+
private observations: Observation[] = [];
|
|
65
|
+
private compressed: CompressedObservation[] = [];
|
|
66
|
+
private config: ObservationStoreConfig;
|
|
67
|
+
private storePath: string;
|
|
68
|
+
|
|
69
|
+
constructor(storePath: string, config: Partial<ObservationStoreConfig> = {}) {
|
|
70
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
71
|
+
this.storePath = storePath;
|
|
72
|
+
if (existsSync(storePath)) {
|
|
73
|
+
this.load();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Record a new observation.
|
|
79
|
+
*/
|
|
80
|
+
record(observation: Observation): void {
|
|
81
|
+
// Strip privacy
|
|
82
|
+
const sanitized: Observation = {
|
|
83
|
+
...observation,
|
|
84
|
+
input: stripPrivacyTags(observation.input, this.config),
|
|
85
|
+
output: stripPrivacyTags(observation.output, this.config),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.observations.push(sanitized);
|
|
89
|
+
|
|
90
|
+
// Enforce capacity
|
|
91
|
+
if (this.observations.length > this.config.maxObservations) {
|
|
92
|
+
this.observations = this.observations.slice(-this.config.maxObservations);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get recent observations.
|
|
98
|
+
*/
|
|
99
|
+
getRecent(count = 10): Observation[] {
|
|
100
|
+
return this.observations.slice(-count);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Store a compressed observation.
|
|
105
|
+
*/
|
|
106
|
+
addCompressed(compressed: CompressedObservation): void {
|
|
107
|
+
this.compressed.push(compressed);
|
|
108
|
+
|
|
109
|
+
if (this.compressed.length > this.config.maxCompressed) {
|
|
110
|
+
this.compressed = this.compressed.slice(-this.config.maxCompressed);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get compressed observations for injection.
|
|
116
|
+
*/
|
|
117
|
+
getCompressed(limit = 5): CompressedObservation[] {
|
|
118
|
+
return this.compressed.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, limit);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Format compressed observations for prompt injection.
|
|
123
|
+
*/
|
|
124
|
+
injectCompressed(limit = 5): string {
|
|
125
|
+
const items = this.getCompressed(limit);
|
|
126
|
+
if (items.length === 0) return "";
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
"## Observations from Previous Sessions\n\n" +
|
|
130
|
+
items
|
|
131
|
+
.map(
|
|
132
|
+
(o) =>
|
|
133
|
+
`### ${o.summary}\n` +
|
|
134
|
+
`Patterns: ${o.patterns.join(", ")}\n` +
|
|
135
|
+
`Decisions: ${o.decisions.join(", ")}\n` +
|
|
136
|
+
`Files: ${o.filesAffected.join(", ")}`,
|
|
137
|
+
)
|
|
138
|
+
.join("\n\n") +
|
|
139
|
+
"\n"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Persist to disk.
|
|
145
|
+
*/
|
|
146
|
+
save(): void {
|
|
147
|
+
try {
|
|
148
|
+
// BUGFIX (Round 12 I4): use atomicWriteJson (temp-file + rename) instead
|
|
149
|
+
// of raw writeFileSync, so a crash mid-write cannot leave a truncated /
|
|
150
|
+
// empty file that breaks load() on restart.
|
|
151
|
+
atomicWriteJson(this.storePath, {
|
|
152
|
+
observations: this.observations,
|
|
153
|
+
compressed: this.compressed,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logInternalError("observation-store.save", error, `path=${this.storePath}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get stats(): { observations: number; compressed: number } {
|
|
161
|
+
return {
|
|
162
|
+
observations: this.observations.length,
|
|
163
|
+
compressed: this.compressed.length,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private load(): void {
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(readFileSync(this.storePath, "utf-8"));
|
|
170
|
+
if (Array.isArray(data.observations)) this.observations = data.observations;
|
|
171
|
+
if (Array.isArray(data.compressed)) this.compressed = data.compressed;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logInternalError("observation-store.load", error, `path=${this.storePath}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
5
|
+
import { atomicWriteJson } from "./atomic-write.ts";
|
|
6
|
+
import { withFileLockSync } from "./locks.ts";
|
|
7
|
+
import type { TeamTaskState } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
10
|
+
|
|
11
|
+
export interface CacheEntry {
|
|
12
|
+
key: string;
|
|
13
|
+
runId: string;
|
|
14
|
+
status: string;
|
|
15
|
+
tasks: TeamTaskState[];
|
|
16
|
+
cachedAt: number;
|
|
17
|
+
expiresAt: number;
|
|
18
|
+
goal: string;
|
|
19
|
+
team: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface CacheIndex {
|
|
23
|
+
[cacheKey: string]: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute a cache key from run parameters.
|
|
28
|
+
* Uses SHA-256 hash of normalized goal + team + workflow.
|
|
29
|
+
*/
|
|
30
|
+
export function computeRunCacheKey(goal: string, team: string, workflow: string, _cwd: string): string {
|
|
31
|
+
const normalized = goal.trim().toLowerCase().replace(/\s+/g, " ");
|
|
32
|
+
return crypto.createHash("sha256").update(normalized).update(team).update(workflow).update(_cwd).digest("hex").slice(0, 16);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the cache directory path.
|
|
37
|
+
*/
|
|
38
|
+
function cacheDir(cwd: string): string {
|
|
39
|
+
return path.join(projectCrewRoot(cwd), "cache");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get cached run result if exists and valid.
|
|
44
|
+
* Returns null if cache miss or expired.
|
|
45
|
+
*/
|
|
46
|
+
export function getCachedRun(cwd: string, cacheKey: string): CacheEntry | null {
|
|
47
|
+
const dir = cacheDir(cwd);
|
|
48
|
+
const indexPath = path.join(dir, "index.json");
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(indexPath)) return null;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const index = JSON.parse(fs.readFileSync(indexPath, "utf-8")) as CacheIndex;
|
|
54
|
+
const entryPath = index[cacheKey];
|
|
55
|
+
|
|
56
|
+
if (!entryPath || !fs.existsSync(entryPath)) return null;
|
|
57
|
+
|
|
58
|
+
const entry = JSON.parse(fs.readFileSync(entryPath, "utf-8")) as CacheEntry;
|
|
59
|
+
|
|
60
|
+
if (Date.now() > entry.expiresAt) {
|
|
61
|
+
// Remove expired entry — use lock + atomic write to prevent index corruption
|
|
62
|
+
withFileLockSync(indexPath, () => {
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(entryPath);
|
|
65
|
+
} catch {
|
|
66
|
+
/* ignore */
|
|
67
|
+
}
|
|
68
|
+
const updatedIndex = JSON.parse(fs.readFileSync(indexPath, "utf-8")) as CacheIndex;
|
|
69
|
+
delete updatedIndex[cacheKey];
|
|
70
|
+
atomicWriteJson(indexPath, updatedIndex);
|
|
71
|
+
});
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return entry;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Save run result to cache.
|
|
83
|
+
*/
|
|
84
|
+
export function saveRunToCache(
|
|
85
|
+
cwd: string,
|
|
86
|
+
cacheKey: string,
|
|
87
|
+
runId: string,
|
|
88
|
+
status: string,
|
|
89
|
+
tasks: TeamTaskState[],
|
|
90
|
+
goal: string,
|
|
91
|
+
team: string,
|
|
92
|
+
ttlMs: number = DEFAULT_CACHE_TTL_MS,
|
|
93
|
+
): void {
|
|
94
|
+
const dir = cacheDir(cwd);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(dir)) {
|
|
97
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const entry: CacheEntry = {
|
|
101
|
+
key: cacheKey,
|
|
102
|
+
runId,
|
|
103
|
+
status,
|
|
104
|
+
tasks,
|
|
105
|
+
cachedAt: Date.now(),
|
|
106
|
+
expiresAt: Date.now() + ttlMs,
|
|
107
|
+
goal,
|
|
108
|
+
team,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const entryPath = path.join(dir, `${cacheKey}.json`);
|
|
112
|
+
// ST-4: atomic entry write (was raw writeFileSync — inverted priority vs the index).
|
|
113
|
+
atomicWriteJson(entryPath, entry);
|
|
114
|
+
|
|
115
|
+
// Update index under a file lock (prevents concurrent saveCache from racing on the
|
|
116
|
+
// RMW) and write atomically (unique temp + rename — fixes the fixed "index.json.tmp"
|
|
117
|
+
// name collision the old manual temp+rename had).
|
|
118
|
+
const indexPath = path.join(dir, "index.json");
|
|
119
|
+
withFileLockSync(indexPath, () => {
|
|
120
|
+
const index: CacheIndex = fs.existsSync(indexPath) ? JSON.parse(fs.readFileSync(indexPath, "utf-8")) : {};
|
|
121
|
+
index[cacheKey] = entryPath;
|
|
122
|
+
atomicWriteJson(indexPath, index);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Clear all cache entries.
|
|
128
|
+
*/
|
|
129
|
+
export function clearCache(cwd: string): void {
|
|
130
|
+
const dir = cacheDir(cwd);
|
|
131
|
+
if (!fs.existsSync(dir)) return;
|
|
132
|
+
|
|
133
|
+
const indexPath = path.join(dir, "index.json");
|
|
134
|
+
if (fs.existsSync(indexPath)) {
|
|
135
|
+
try {
|
|
136
|
+
const index = JSON.parse(fs.readFileSync(indexPath, "utf-8")) as CacheIndex;
|
|
137
|
+
for (const entryPath of Object.values(index)) {
|
|
138
|
+
try {
|
|
139
|
+
fs.unlinkSync(entryPath);
|
|
140
|
+
} catch {
|
|
141
|
+
/* ignore */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
fs.unlinkSync(indexPath);
|
|
145
|
+
} catch {
|
|
146
|
+
/* ignore */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Remove entry files not in index
|
|
151
|
+
const entries = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
try {
|
|
154
|
+
fs.unlinkSync(path.join(dir, entry));
|
|
155
|
+
} catch {
|
|
156
|
+
/* ignore */
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get cache stats.
|
|
163
|
+
*/
|
|
164
|
+
export function getCacheStats(cwd: string): {
|
|
165
|
+
entries: number;
|
|
166
|
+
sizeBytes: number;
|
|
167
|
+
} {
|
|
168
|
+
const dir = cacheDir(cwd);
|
|
169
|
+
if (!fs.existsSync(dir)) return { entries: 0, sizeBytes: 0 };
|
|
170
|
+
|
|
171
|
+
let sizeBytes = 0;
|
|
172
|
+
let entries = 0;
|
|
173
|
+
const indexPath = path.join(dir, "index.json");
|
|
174
|
+
|
|
175
|
+
if (fs.existsSync(indexPath)) {
|
|
176
|
+
try {
|
|
177
|
+
const index = JSON.parse(fs.readFileSync(indexPath, "utf-8")) as CacheIndex;
|
|
178
|
+
entries = Object.keys(index).length;
|
|
179
|
+
for (const entryPath of Object.values(index)) {
|
|
180
|
+
try {
|
|
181
|
+
const stat = fs.statSync(entryPath);
|
|
182
|
+
sizeBytes += stat.size;
|
|
183
|
+
} catch {
|
|
184
|
+
/* ignore */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
/* ignore */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { entries, sizeBytes };
|
|
193
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
4
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteFile } from "./atomic-write.ts";
|
|
6
|
+
import type { TeamRunManifest, TeamTaskState } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
export interface RunGraphNode {
|
|
9
|
+
id: string;
|
|
10
|
+
type: "run" | "task" | "agent" | "artifact" | "file";
|
|
11
|
+
name: string;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RunGraphEdge {
|
|
16
|
+
source: string;
|
|
17
|
+
target: string;
|
|
18
|
+
type: "dependsOn" | "produces" | "runs" | "contains";
|
|
19
|
+
weight?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RunGraphLayer {
|
|
23
|
+
name: string;
|
|
24
|
+
nodeIds: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RunGraph {
|
|
28
|
+
version: "1.0.0";
|
|
29
|
+
runId: string;
|
|
30
|
+
team: string;
|
|
31
|
+
workflow: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
completedAt?: string;
|
|
34
|
+
status: string;
|
|
35
|
+
nodes: RunGraphNode[];
|
|
36
|
+
edges: RunGraphEdge[];
|
|
37
|
+
layers: RunGraphLayer[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a unified run graph from manifest + tasks.
|
|
42
|
+
* Consolidates state into a single graph JSON for dashboard/API use.
|
|
43
|
+
*/
|
|
44
|
+
export function buildRunGraph(manifest: TeamRunManifest, tasks: TeamTaskState[]): RunGraph {
|
|
45
|
+
const nodes: RunGraphNode[] = [];
|
|
46
|
+
const edges: RunGraphEdge[] = [];
|
|
47
|
+
const nodeIds = new Set<string>();
|
|
48
|
+
|
|
49
|
+
// Add run node
|
|
50
|
+
const runId = manifest.runId;
|
|
51
|
+
nodes.push({
|
|
52
|
+
id: `run:${runId}`,
|
|
53
|
+
type: "run",
|
|
54
|
+
name: manifest.goal ?? runId,
|
|
55
|
+
metadata: {
|
|
56
|
+
team: manifest.team,
|
|
57
|
+
workflow: manifest.workflow,
|
|
58
|
+
status: manifest.status,
|
|
59
|
+
createdAt: manifest.createdAt,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
nodeIds.add(`run:${runId}`);
|
|
63
|
+
|
|
64
|
+
// Add task nodes
|
|
65
|
+
for (const task of tasks) {
|
|
66
|
+
const taskId = `task:${task.id}`;
|
|
67
|
+
if (nodeIds.has(taskId)) continue;
|
|
68
|
+
nodeIds.add(taskId);
|
|
69
|
+
|
|
70
|
+
nodes.push({
|
|
71
|
+
id: taskId,
|
|
72
|
+
type: "task",
|
|
73
|
+
name: task.role,
|
|
74
|
+
metadata: {
|
|
75
|
+
status: task.status,
|
|
76
|
+
startedAt: task.startedAt,
|
|
77
|
+
finishedAt: task.finishedAt,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Edge from run to task
|
|
82
|
+
edges.push({
|
|
83
|
+
source: `run:${runId}`,
|
|
84
|
+
target: taskId,
|
|
85
|
+
type: "contains",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Edges from dependencies
|
|
89
|
+
for (const dep of task.dependsOn ?? []) {
|
|
90
|
+
edges.push({
|
|
91
|
+
source: `task:${dep}`,
|
|
92
|
+
target: taskId,
|
|
93
|
+
type: "dependsOn",
|
|
94
|
+
weight: 1.0,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Group by layer (based on phase or role)
|
|
100
|
+
const layerMap = new Map<string, string[]>();
|
|
101
|
+
for (const task of tasks) {
|
|
102
|
+
const layerName = task.adaptive?.phase ?? task.role;
|
|
103
|
+
if (!layerMap.has(layerName)) layerMap.set(layerName, []);
|
|
104
|
+
layerMap.get(layerName)!.push(`task:${task.id}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const layers: RunGraphLayer[] = [...layerMap.entries()].map(([name, nodeIdList]) => ({
|
|
108
|
+
name,
|
|
109
|
+
nodeIds: nodeIdList,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
version: "1.0.0",
|
|
114
|
+
runId,
|
|
115
|
+
team: manifest.team ?? "unknown",
|
|
116
|
+
workflow: manifest.workflow ?? "unknown",
|
|
117
|
+
createdAt: manifest.createdAt,
|
|
118
|
+
completedAt: manifest.updatedAt,
|
|
119
|
+
status: manifest.status,
|
|
120
|
+
nodes,
|
|
121
|
+
edges,
|
|
122
|
+
layers,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Save run graph to disk.
|
|
128
|
+
* Uses projectCrewRoot() to honour the .pi/teams/ fallback for .pi-based
|
|
129
|
+
* projects (issue #29 follow-up). Graph files now live alongside the
|
|
130
|
+
* manifest at `<crewRoot>/graphs/<runId>.json`.
|
|
131
|
+
*/
|
|
132
|
+
export function saveRunGraph(graph: RunGraph, cwd: string): string {
|
|
133
|
+
assertSafePathId("runId", graph.runId);
|
|
134
|
+
const crewRoot = projectCrewRoot(cwd);
|
|
135
|
+
const graphsDir = path.join(crewRoot, "graphs");
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(graphsDir)) {
|
|
138
|
+
fs.mkdirSync(graphsDir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const graphPath = path.join(graphsDir, `${graph.runId}.json`);
|
|
142
|
+
atomicWriteFile(graphPath, JSON.stringify(graph, null, 2));
|
|
143
|
+
|
|
144
|
+
return graphPath;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load run graph from disk.
|
|
149
|
+
*/
|
|
150
|
+
export function loadRunGraph(cwd: string, runId: string): RunGraph | null {
|
|
151
|
+
assertSafePathId("runId", runId);
|
|
152
|
+
const crewRoot = projectCrewRoot(cwd);
|
|
153
|
+
const graphPath = path.join(crewRoot, "graphs", `${runId}.json`);
|
|
154
|
+
if (!fs.existsSync(graphPath)) return null;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(fs.readFileSync(graphPath, "utf-8")) as RunGraph;
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* List all archived run graphs.
|
|
165
|
+
*/
|
|
166
|
+
export function listRunGraphs(cwd: string): string[] {
|
|
167
|
+
const crewRoot = projectCrewRoot(cwd);
|
|
168
|
+
const graphsDir = path.join(crewRoot, "graphs");
|
|
169
|
+
if (!fs.existsSync(graphsDir)) return [];
|
|
170
|
+
|
|
171
|
+
return fs
|
|
172
|
+
.readdirSync(graphsDir)
|
|
173
|
+
.filter((f) => f.endsWith(".json"))
|
|
174
|
+
.map((f) => f.replace(/\.json$/, ""));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Build and save run graph from manifest + tasks.
|
|
179
|
+
*/
|
|
180
|
+
export function buildAndSaveRunGraph(manifest: TeamRunManifest, tasks: TeamTaskState[], cwd: string): string {
|
|
181
|
+
const graph = buildRunGraph(manifest, tasks);
|
|
182
|
+
return saveRunGraph(graph, cwd);
|
|
183
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
4
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteJson, readJsonFile } from "./atomic-write.ts";
|
|
6
|
+
import { loadRunManifestById } from "./state-store.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run metrics snapshot captured after a run completes (or on demand).
|
|
10
|
+
*/
|
|
11
|
+
export interface RunMetrics {
|
|
12
|
+
runId: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
taskCount: number;
|
|
15
|
+
completedCount: number;
|
|
16
|
+
failedCount: number;
|
|
17
|
+
totalTokens: number;
|
|
18
|
+
totalCost: number;
|
|
19
|
+
durationMs: number;
|
|
20
|
+
consistencyScore: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Number of recent metric files to scan when building a summary. */
|
|
24
|
+
const MAX_METRIC_FILES_TO_SCAN = 500;
|
|
25
|
+
|
|
26
|
+
function metricsDir(cwd: string): string {
|
|
27
|
+
const repoRoot = projectCrewRoot(cwd);
|
|
28
|
+
return path.join(repoRoot, "state", "metrics");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function metricsFilePath(cwd: string, runId: string): string {
|
|
32
|
+
assertSafePathId("runId", runId);
|
|
33
|
+
return path.join(metricsDir(cwd), `${runId}.json`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Collect metrics for a run by reading its manifest, tasks, and event log.
|
|
38
|
+
* Returns undefined if the run cannot be loaded.
|
|
39
|
+
*/
|
|
40
|
+
export function collectRunMetrics(cwd: string, runId: string): RunMetrics | undefined {
|
|
41
|
+
assertSafePathId("runId", runId);
|
|
42
|
+
const result = loadRunManifestById(cwd, runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency;
|
|
43
|
+
if (!result) return undefined;
|
|
44
|
+
|
|
45
|
+
const { manifest, tasks } = result;
|
|
46
|
+
const now = new Date().toISOString();
|
|
47
|
+
|
|
48
|
+
// Aggregate token/cost from all tasks that have usage data.
|
|
49
|
+
let totalTokens = 0;
|
|
50
|
+
let totalCost = 0;
|
|
51
|
+
for (const task of tasks) {
|
|
52
|
+
if (task.usage) {
|
|
53
|
+
totalTokens += (task.usage.input ?? 0) + (task.usage.output ?? 0);
|
|
54
|
+
totalCost += task.usage.cost ?? 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Count completed vs failed tasks.
|
|
59
|
+
let completedCount = 0;
|
|
60
|
+
let failedCount = 0;
|
|
61
|
+
for (const task of tasks) {
|
|
62
|
+
if (task.status === "completed") completedCount++;
|
|
63
|
+
else if (task.status === "failed") failedCount++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Duration: from run createdAt to updatedAt (manifest timestamps), or 0 if unavailable.
|
|
67
|
+
const createdAt = new Date(manifest.createdAt).getTime();
|
|
68
|
+
const updatedAt = new Date(manifest.updatedAt).getTime();
|
|
69
|
+
const durationMs = Number.isNaN(createdAt) || Number.isNaN(updatedAt) ? 0 : Math.max(0, updatedAt - createdAt);
|
|
70
|
+
|
|
71
|
+
// Consistency score: proportion of tasks that completed successfully among all non-skipped tasks.
|
|
72
|
+
const nonSkippedTasks = tasks.filter((t) => t.status !== "skipped");
|
|
73
|
+
const consistencyScore = nonSkippedTasks.length > 0 ? completedCount / nonSkippedTasks.length : 1.0;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
runId,
|
|
77
|
+
timestamp: now,
|
|
78
|
+
taskCount: tasks.length,
|
|
79
|
+
completedCount,
|
|
80
|
+
failedCount,
|
|
81
|
+
totalTokens,
|
|
82
|
+
totalCost,
|
|
83
|
+
durationMs,
|
|
84
|
+
consistencyScore: Math.round(consistencyScore * 1000) / 1000, // 3 decimal places
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Persist a metrics snapshot to .crew/state/metrics/<runId>.json.
|
|
90
|
+
* Uses atomicWriteJson to ensure safe writes.
|
|
91
|
+
*/
|
|
92
|
+
export function saveRunMetrics(cwd: string, metrics: RunMetrics): void {
|
|
93
|
+
const dir = metricsDir(cwd);
|
|
94
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
+
atomicWriteJson(metricsFilePath(cwd, metrics.runId), metrics);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Load a previously saved metrics snapshot.
|
|
100
|
+
* Returns undefined if the file does not exist or cannot be parsed.
|
|
101
|
+
*/
|
|
102
|
+
export function loadRunMetrics(cwd: string, runId: string): RunMetrics | undefined {
|
|
103
|
+
return readJsonFile<RunMetrics>(metricsFilePath(cwd, runId));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* List recent metrics files up to `limit` entries (newest first).
|
|
108
|
+
* Returns an array of { runId, timestamp, taskCount, completedCount, failedCount, totalTokens, totalCost, durationMs, consistencyScore }.
|
|
109
|
+
* Gracefully skips files that cannot be read or parsed.
|
|
110
|
+
*
|
|
111
|
+
* FIND-04 perf: sort dirents by mtime descending BEFORE reading any of
|
|
112
|
+
* them, then read at most `limit` files. Malformed files within that
|
|
113
|
+
* selected window reduce the returned count; the scan does not continue
|
|
114
|
+
* past the window to backfill them. Previously the function read up
|
|
115
|
+
* to MAX_METRIC_FILES_TO_SCAN (500) files via loadRunMetrics() and only
|
|
116
|
+
* THEN sorted+sliced to `limit` (default 25) — wasting 475 readFileSync +
|
|
117
|
+
* JSON.parse calls on the hot dashboard path. The sort is O(N log N) and
|
|
118
|
+
* uses the run-id timestamp prefix as a cheap secondary signal when
|
|
119
|
+
* mtimes tie (e.g. multiple files saved in the same millisecond).
|
|
120
|
+
*/
|
|
121
|
+
export function getRunMetricsSummary(cwd: string, limit = 25): RunMetrics[] {
|
|
122
|
+
const dir = metricsDir(cwd);
|
|
123
|
+
let entries: fs.Dirent[] = [];
|
|
124
|
+
try {
|
|
125
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Cap the directory-order candidate set before collecting mtimes. This
|
|
131
|
+
// is a safety valve against unbounded scan cost; the mtime sort below
|
|
132
|
+
// only reorders entries within this bounded candidate window.
|
|
133
|
+
const cap = Math.max(0, limit);
|
|
134
|
+
const sorted = entries
|
|
135
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
136
|
+
.slice(0, MAX_METRIC_FILES_TO_SCAN)
|
|
137
|
+
.map((entry) => {
|
|
138
|
+
const fullPath = path.join(dir, entry.name);
|
|
139
|
+
let mtimeMs = 0;
|
|
140
|
+
try {
|
|
141
|
+
mtimeMs = fs.statSync(fullPath).mtimeMs;
|
|
142
|
+
} catch {
|
|
143
|
+
// stat failure: fall through with mtimeMs=0; the sort still
|
|
144
|
+
// produces a deterministic order (filenames tiebreak).
|
|
145
|
+
}
|
|
146
|
+
return { name: entry.name, mtimeMs };
|
|
147
|
+
})
|
|
148
|
+
.sort((a, b) => {
|
|
149
|
+
const diff = b.mtimeMs - a.mtimeMs;
|
|
150
|
+
if (diff !== 0) return diff;
|
|
151
|
+
// Filename tiebreaker: the run-id prefix is a YYYYMMDDhhmmss
|
|
152
|
+
// timestamp (e.g. team_20260720050617_abc123) so a descending
|
|
153
|
+
// string compare yields newest-first within a tie group.
|
|
154
|
+
return b.name.localeCompare(a.name);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const metrics: RunMetrics[] = [];
|
|
158
|
+
for (const { name } of sorted.slice(0, cap)) {
|
|
159
|
+
const runId = name.replace(/\.json$/, "");
|
|
160
|
+
const m = loadRunMetrics(cwd, runId);
|
|
161
|
+
if (m) metrics.push(m);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return metrics;
|
|
165
|
+
}
|