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,561 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { DEFAULT_PATHS, DEFAULT_SUBAGENT } from "../config/defaults.ts";
|
|
4
|
+
import type { PiTeamsToolResult } from "../extension/tool-result.ts";
|
|
5
|
+
import { loadRunManifestById } from "../state/state-store.ts";
|
|
6
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
7
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
8
|
+
import { redactSecrets } from "../utils/redaction.ts";
|
|
9
|
+
|
|
10
|
+
export type SubagentStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "error" | "blocked" | "stopped";
|
|
11
|
+
|
|
12
|
+
export interface SubagentSpawnOptions {
|
|
13
|
+
cwd: string;
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
prompt: string;
|
|
17
|
+
background: boolean;
|
|
18
|
+
model?: string;
|
|
19
|
+
skill?: string | string[] | false;
|
|
20
|
+
maxTurns?: number;
|
|
21
|
+
ownerSessionGeneration?: number;
|
|
22
|
+
/** Optional batch grouping id (Rule 1). Agents sharing a batchId coalesce
|
|
23
|
+
* completion notifications into one. undefined => individual (default). */
|
|
24
|
+
batchId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SubagentRecord {
|
|
28
|
+
id: string;
|
|
29
|
+
runId?: string;
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
prompt: string;
|
|
33
|
+
status: SubagentStatus;
|
|
34
|
+
startedAt: number;
|
|
35
|
+
completedAt?: number;
|
|
36
|
+
result?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
resultConsumed?: boolean;
|
|
39
|
+
model?: string;
|
|
40
|
+
skill?: string | string[] | false;
|
|
41
|
+
background: boolean;
|
|
42
|
+
ownerSessionGeneration?: number;
|
|
43
|
+
/** Batch grouping id (Rule 1). undefined => individual notification. */
|
|
44
|
+
batchId?: string;
|
|
45
|
+
stuckNotified?: boolean;
|
|
46
|
+
blockedAt?: number;
|
|
47
|
+
promise?: Promise<void>;
|
|
48
|
+
// Phase 1.6: Telemetry baseline fields
|
|
49
|
+
turnCount?: number;
|
|
50
|
+
terminated?: boolean;
|
|
51
|
+
durationMs?: number;
|
|
52
|
+
/** Lifetime token usage accumulated via message_end events. Survives compaction. */
|
|
53
|
+
lifetimeUsage?: { input: number; output: number; cacheWrite: number };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type SpawnRunner = (options: SubagentSpawnOptions, signal?: AbortSignal) => Promise<PiTeamsToolResult>;
|
|
57
|
+
type Notify = (record: SubagentRecord) => void;
|
|
58
|
+
type NotifyEvent = (type: string, data: Record<string, unknown>) => void;
|
|
59
|
+
|
|
60
|
+
interface QueuedSpawn {
|
|
61
|
+
record: SubagentRecord;
|
|
62
|
+
options: SubagentSpawnOptions;
|
|
63
|
+
runner: SpawnRunner;
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isValidSubagentId(id: string): boolean {
|
|
68
|
+
return /^[a-z0-9_]+$/i.test(id) && id.length <= 128;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function persistedSubagentPath(cwd: string, id: string): string {
|
|
72
|
+
if (!isValidSubagentId(id)) throw new Error(`Invalid subagent id: ${id}`);
|
|
73
|
+
return path.join(projectCrewRoot(cwd), DEFAULT_PATHS.state.subagentsSubdir, `${id}.json`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function serializableRecord(record: SubagentRecord): SubagentRecord {
|
|
77
|
+
const { promise: _promise, ...rest } = record;
|
|
78
|
+
return rest;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function savePersistedSubagentRecord(cwd: string, record: SubagentRecord): void {
|
|
82
|
+
try {
|
|
83
|
+
const filePath = persistedSubagentPath(cwd, record.id);
|
|
84
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
85
|
+
fs.writeFileSync(filePath, `${JSON.stringify(redactSecrets(serializableRecord(record)), null, 2)}\n`, "utf-8");
|
|
86
|
+
// SECURITY: Restrict permissions to owner-only (rw-------).
|
|
87
|
+
// On multi-user systems, other users must not read task prompts,
|
|
88
|
+
// agent descriptions, and run IDs from subagent record files.
|
|
89
|
+
fs.chmodSync(filePath, 0o600);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logInternalError("subagent-manager.save", error, `id=${record.id}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete the persisted subagent record from disk.
|
|
97
|
+
* Used when a subagent is cancelled or terminated — the user wants no trace.
|
|
98
|
+
* Safe-fail: if the file does not exist (already deleted), this is a no-op.
|
|
99
|
+
* Any other I/O error is logged but not propagated.
|
|
100
|
+
*/
|
|
101
|
+
export function removePersistedSubagentRecord(cwd: string, id: string): boolean {
|
|
102
|
+
try {
|
|
103
|
+
const filePath = persistedSubagentPath(cwd, id);
|
|
104
|
+
fs.unlinkSync(filePath);
|
|
105
|
+
return true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const code = (error as NodeJS.ErrnoException)?.code;
|
|
108
|
+
if (code === "ENOENT") return false;
|
|
109
|
+
logInternalError("subagent-manager.remove", error, `id=${id}`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Predicate: should this record's persisted file be deleted on terminal status?
|
|
116
|
+
* Only abnormal terminations (cancelled / stopped / terminated-flag) are wiped.
|
|
117
|
+
* Successful runs (`completed`) and agent errors (`failed`, `error`) keep their audit trail.
|
|
118
|
+
*/
|
|
119
|
+
export function shouldDeleteOnTerminalStatus(record: SubagentRecord): boolean {
|
|
120
|
+
if (record.terminated === true) return true;
|
|
121
|
+
const s = record.status;
|
|
122
|
+
return s === "cancelled" || s === "stopped";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ALLOWED_RECORD_FIELDS = new Set([
|
|
126
|
+
"id",
|
|
127
|
+
"agentId",
|
|
128
|
+
"agentName",
|
|
129
|
+
"subagentType",
|
|
130
|
+
"type",
|
|
131
|
+
"description",
|
|
132
|
+
"prompt",
|
|
133
|
+
"status",
|
|
134
|
+
"startedAt",
|
|
135
|
+
"completedAt",
|
|
136
|
+
"spawnedAt",
|
|
137
|
+
"model",
|
|
138
|
+
"runId",
|
|
139
|
+
"cwd",
|
|
140
|
+
"taskId",
|
|
141
|
+
"result",
|
|
142
|
+
"error",
|
|
143
|
+
"resultConsumed",
|
|
144
|
+
"background",
|
|
145
|
+
"ownerSessionGeneration",
|
|
146
|
+
"stuckNotified",
|
|
147
|
+
"blockedAt",
|
|
148
|
+
"turnCount",
|
|
149
|
+
"terminated",
|
|
150
|
+
"durationMs",
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
function sanitizePersistedRecord(raw: unknown): SubagentRecord | undefined {
|
|
154
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
155
|
+
const obj = raw as Record<string, unknown>;
|
|
156
|
+
// Accept either `id` (public SubagentRecord field) or `agentId` (legacy).
|
|
157
|
+
// The saved JSON uses `id` (from serializableRecord → JSON.stringify of record).
|
|
158
|
+
const idValue = obj.id ?? obj.agentId;
|
|
159
|
+
if (typeof idValue !== "string" || !idValue) return undefined;
|
|
160
|
+
const clean: Record<string, unknown> = { id: idValue };
|
|
161
|
+
if (obj.agentId && typeof obj.agentId === "string") {
|
|
162
|
+
clean.agentId = obj.agentId;
|
|
163
|
+
}
|
|
164
|
+
for (const key of Object.keys(obj)) {
|
|
165
|
+
if (
|
|
166
|
+
ALLOWED_RECORD_FIELDS.has(key) &&
|
|
167
|
+
(typeof obj[key] === "string" || typeof obj[key] === "number" || typeof obj[key] === "boolean")
|
|
168
|
+
) {
|
|
169
|
+
clean[key] = obj[key];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Re-validate `id` to prevent prototype/constructor smuggling
|
|
173
|
+
if (typeof clean.id !== "string" || clean.id.length === 0) return undefined;
|
|
174
|
+
return clean as unknown as SubagentRecord;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function readPersistedSubagentRecord(cwd: string, id: string): SubagentRecord | undefined {
|
|
178
|
+
try {
|
|
179
|
+
const raw = JSON.parse(fs.readFileSync(persistedSubagentPath(cwd, id), "utf-8"));
|
|
180
|
+
return sanitizePersistedRecord(raw);
|
|
181
|
+
} catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resultText(result: PiTeamsToolResult): string {
|
|
187
|
+
return (
|
|
188
|
+
result.content
|
|
189
|
+
?.map((item) => (item.type === "text" ? item.text : ""))
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join("\n") ?? ""
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function detailsRunId(result: PiTeamsToolResult): string | undefined {
|
|
196
|
+
const details = result.details as { runId?: unknown } | undefined;
|
|
197
|
+
return typeof details?.runId === "string" ? details.runId : undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function totalRunTurns(cwd: string, runId: string | undefined): number | undefined {
|
|
201
|
+
if (!runId) return undefined;
|
|
202
|
+
const loaded = loadRunManifestById(cwd, runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
203
|
+
if (!loaded) return undefined;
|
|
204
|
+
let total = 0;
|
|
205
|
+
let hasTurns = false;
|
|
206
|
+
for (const task of loaded.tasks) {
|
|
207
|
+
const turns = task.usage?.turns ?? task.agentProgress?.turns;
|
|
208
|
+
if (typeof turns === "number" && Number.isFinite(turns)) {
|
|
209
|
+
total += turns;
|
|
210
|
+
hasTurns = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return hasTurns ? total : undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class SubagentManager {
|
|
217
|
+
private readonly records = new Map<string, SubagentRecord>();
|
|
218
|
+
private readonly cwdByRecord = new Map<string, string>();
|
|
219
|
+
private readonly controllers = new Map<string, AbortController>();
|
|
220
|
+
private readonly controllerCleanup = new Map<string, () => void>();
|
|
221
|
+
private queue: QueuedSpawn[] = [];
|
|
222
|
+
private runningBackground = 0;
|
|
223
|
+
private counter = 0;
|
|
224
|
+
private maxConcurrent: number;
|
|
225
|
+
private readonly onComplete?: Notify;
|
|
226
|
+
private readonly onEvent?: NotifyEvent;
|
|
227
|
+
private readonly pollIntervalMs: number;
|
|
228
|
+
|
|
229
|
+
constructor(maxConcurrent = 4, onComplete?: Notify, pollIntervalMs = 1000, onEvent?: NotifyEvent) {
|
|
230
|
+
this.maxConcurrent = maxConcurrent;
|
|
231
|
+
this.onComplete = onComplete;
|
|
232
|
+
this.onEvent = onEvent;
|
|
233
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
spawn(options: SubagentSpawnOptions, runner: SpawnRunner, signal?: AbortSignal): SubagentRecord {
|
|
237
|
+
const record: SubagentRecord = {
|
|
238
|
+
id: `agent_${Date.now().toString(36)}_${(++this.counter).toString(36)}`,
|
|
239
|
+
type: options.type,
|
|
240
|
+
description: options.description,
|
|
241
|
+
prompt: options.prompt,
|
|
242
|
+
status: options.background && this.runningBackground >= this.maxConcurrent ? "queued" : "running",
|
|
243
|
+
startedAt: Date.now(),
|
|
244
|
+
model: options.model,
|
|
245
|
+
skill: options.skill,
|
|
246
|
+
background: options.background,
|
|
247
|
+
ownerSessionGeneration: options.ownerSessionGeneration,
|
|
248
|
+
batchId: options.batchId,
|
|
249
|
+
};
|
|
250
|
+
this.records.set(record.id, record);
|
|
251
|
+
this.cwdByRecord.set(record.id, options.cwd);
|
|
252
|
+
savePersistedSubagentRecord(options.cwd, record);
|
|
253
|
+
if (record.status === "queued") {
|
|
254
|
+
this.queue.push({ record, options, runner, signal });
|
|
255
|
+
return record;
|
|
256
|
+
}
|
|
257
|
+
this.start(record, options, runner, signal);
|
|
258
|
+
return record;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
getRecord(id: string): SubagentRecord | undefined {
|
|
262
|
+
return this.records.get(id);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
listAgents(): SubagentRecord[] {
|
|
266
|
+
return [...this.records.values()].sort((a, b) => b.startedAt - a.startedAt);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
abort(id: string, reason?: string): boolean {
|
|
270
|
+
const record = this.records.get(id);
|
|
271
|
+
if (!record) return false;
|
|
272
|
+
if (record.status === "queued") {
|
|
273
|
+
this.queue = this.queue.filter((entry) => entry.record.id !== id);
|
|
274
|
+
this.markStopped(record, reason ?? "Aborted by caller.");
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
if (record.status !== "running" && record.status !== "blocked") return false;
|
|
278
|
+
this.controllers.get(id)?.abort();
|
|
279
|
+
this.markStopped(record, reason ?? "Aborted by caller.");
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
abortAll(reason?: string): number {
|
|
284
|
+
let count = 0;
|
|
285
|
+
const stopReason = reason ?? "Aborted (session switch or shutdown).";
|
|
286
|
+
for (const entry of this.queue) {
|
|
287
|
+
this.markStopped(entry.record, stopReason);
|
|
288
|
+
count++;
|
|
289
|
+
}
|
|
290
|
+
this.queue = [];
|
|
291
|
+
for (const record of this.records.values()) {
|
|
292
|
+
if (record.status === "running" || record.status === "blocked") {
|
|
293
|
+
this.controllers.get(record.id)?.abort();
|
|
294
|
+
this.markStopped(record, stopReason);
|
|
295
|
+
count++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return count;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async waitForAll(): Promise<void> {
|
|
302
|
+
while (true) {
|
|
303
|
+
this.drainQueue();
|
|
304
|
+
const pending = this.listAgents()
|
|
305
|
+
.filter((record) => record.status === "running" || record.status === "queued")
|
|
306
|
+
.map((record) => record.promise)
|
|
307
|
+
.filter((promise): promise is Promise<void> => Boolean(promise));
|
|
308
|
+
if (!pending.length) break;
|
|
309
|
+
await Promise.allSettled(pending);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async waitForRecord(id: string): Promise<SubagentRecord | undefined> {
|
|
314
|
+
while (true) {
|
|
315
|
+
const record = this.records.get(id);
|
|
316
|
+
if (!record) return undefined;
|
|
317
|
+
if (record.status !== "running" && record.status !== "queued") return record;
|
|
318
|
+
if (record.promise)
|
|
319
|
+
await record.promise.catch((error) => {
|
|
320
|
+
logInternalError("subagent-manager.waitForRecord", error, `id=${id}`);
|
|
321
|
+
});
|
|
322
|
+
else await new Promise((resolve) => setTimeout(resolve, 100));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setMaxConcurrent(value: number): void {
|
|
327
|
+
this.maxConcurrent = Math.max(1, Math.floor(value));
|
|
328
|
+
this.drainQueue();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private start(record: SubagentRecord, options: SubagentSpawnOptions, runner: SpawnRunner, signal?: AbortSignal): void {
|
|
332
|
+
if (options.background) this.runningBackground++;
|
|
333
|
+
record.status = "running";
|
|
334
|
+
record.startedAt = Date.now();
|
|
335
|
+
record.completedAt = undefined;
|
|
336
|
+
const runSignal = this.createRunSignal(record.id, signal);
|
|
337
|
+
savePersistedSubagentRecord(options.cwd, record);
|
|
338
|
+
record.promise = (async () => {
|
|
339
|
+
try {
|
|
340
|
+
const result = await runner(options, runSignal);
|
|
341
|
+
if (record.status === "stopped") return;
|
|
342
|
+
record.runId = detailsRunId(result);
|
|
343
|
+
record.result = resultText(result);
|
|
344
|
+
savePersistedSubagentRecord(options.cwd, record);
|
|
345
|
+
if (result.isError) {
|
|
346
|
+
record.status = "error";
|
|
347
|
+
record.error = record.result;
|
|
348
|
+
throw new Error(record.error);
|
|
349
|
+
}
|
|
350
|
+
if (record.runId) await this.pollRunToTerminal(options.cwd, record);
|
|
351
|
+
else record.status = "completed";
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (record.status === "stopped" || runSignal.aborted) {
|
|
354
|
+
const abortReason = runSignal.aborted
|
|
355
|
+
? "Signal aborted — agent cancelled by parent (session switch, user cancel, or tool timeout)."
|
|
356
|
+
: undefined;
|
|
357
|
+
record.status = "stopped";
|
|
358
|
+
if (!record.error) record.error = abortReason ?? (error instanceof Error ? error.message : String(error));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
record.status = "error";
|
|
362
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
363
|
+
throw error; // H4: Propagate rejection so callers awaiting record.promise see the error
|
|
364
|
+
} finally {
|
|
365
|
+
this.cleanupRunSignal(record.id);
|
|
366
|
+
if (options.background) this.runningBackground = Math.max(0, this.runningBackground - 1);
|
|
367
|
+
if (record.status !== "blocked") record.completedAt = record.completedAt ?? Date.now();
|
|
368
|
+
savePersistedSubagentRecord(options.cwd, record);
|
|
369
|
+
if (
|
|
370
|
+
record.status === "completed" ||
|
|
371
|
+
record.status === "failed" ||
|
|
372
|
+
record.status === "cancelled" ||
|
|
373
|
+
record.status === "error" ||
|
|
374
|
+
record.status === "stopped"
|
|
375
|
+
) {
|
|
376
|
+
// Phase 1.6: Populate telemetry fields
|
|
377
|
+
record.turnCount = record.turnCount ?? totalRunTurns(options.cwd, record.runId);
|
|
378
|
+
record.durationMs = record.completedAt ? Math.max(0, record.completedAt - record.startedAt) : undefined;
|
|
379
|
+
savePersistedSubagentRecord(options.cwd, record);
|
|
380
|
+
// User policy (v0.9.16): cancelled / stopped subagents leave NO trace.
|
|
381
|
+
// Successful runs (`completed`) and agent errors (`failed`, `error`)
|
|
382
|
+
// keep their audit trail. See shouldDeleteOnTerminalStatus().
|
|
383
|
+
if (shouldDeleteOnTerminalStatus(record)) {
|
|
384
|
+
removePersistedSubagentRecord(options.cwd, record.id);
|
|
385
|
+
}
|
|
386
|
+
this.onComplete?.(record);
|
|
387
|
+
}
|
|
388
|
+
this.drainQueue();
|
|
389
|
+
}
|
|
390
|
+
})();
|
|
391
|
+
// Defense in depth (issue #29): a subagent failure should never crash
|
|
392
|
+
// the host pi process. The IIFE above can reject (e.g. when a run
|
|
393
|
+
// lookup fails) and the re-throw at line 281 propagates to
|
|
394
|
+
// `record.promise`. If no caller awaits the promise, that rejection
|
|
395
|
+
// would become `unhandledRejection` → `uncaughtException` → pi exits.
|
|
396
|
+
// Attaching a no-op catch here keeps the contract for callers that
|
|
397
|
+
// DO await (they still see the rejection) while preventing the
|
|
398
|
+
// harness-killing failure mode for callers that don't.
|
|
399
|
+
record.promise.catch((error) => {
|
|
400
|
+
logInternalError("subagent-manager.start.unhandled", error, `id=${record.id}`);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private markStopped(record: SubagentRecord, reason?: string): void {
|
|
405
|
+
record.status = "stopped";
|
|
406
|
+
record.terminated = true;
|
|
407
|
+
record.completedAt = Date.now();
|
|
408
|
+
if (reason && !record.error) record.error = reason;
|
|
409
|
+
const cwd = this.cwdByRecord.get(record.id);
|
|
410
|
+
// User policy (v0.9.16): stopped subagents leave NO trace on disk.
|
|
411
|
+
// Save first (so any concurrent reader sees the final status), then
|
|
412
|
+
// unlink. The race window is microseconds and the file is gone before
|
|
413
|
+
// the next widget/dashboard tick.
|
|
414
|
+
if (cwd) savePersistedSubagentRecord(cwd, record);
|
|
415
|
+
if (cwd) removePersistedSubagentRecord(cwd, record.id);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private createRunSignal(id: string, signal?: AbortSignal): AbortSignal {
|
|
419
|
+
const controller = new AbortController();
|
|
420
|
+
this.controllers.set(id, controller);
|
|
421
|
+
if (signal?.aborted) {
|
|
422
|
+
controller.abort();
|
|
423
|
+
return controller.signal;
|
|
424
|
+
}
|
|
425
|
+
if (signal) {
|
|
426
|
+
const abort = (): void => controller.abort();
|
|
427
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
428
|
+
this.controllerCleanup.set(id, () => signal.removeEventListener("abort", abort));
|
|
429
|
+
}
|
|
430
|
+
return controller.signal;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private cleanupRunSignal(id: string): void {
|
|
434
|
+
this.controllerCleanup.get(id)?.();
|
|
435
|
+
this.controllerCleanup.delete(id);
|
|
436
|
+
this.controllers.delete(id);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private drainQueue(): void {
|
|
440
|
+
while (this.queue.length > 0 && this.runningBackground < this.maxConcurrent) {
|
|
441
|
+
const next = this.queue.shift();
|
|
442
|
+
if (next?.record.status !== "queued") continue;
|
|
443
|
+
this.start(next.record, next.options, next.runner, next.signal);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private async pollRunToTerminal(cwd: string, record: SubagentRecord): Promise<void> {
|
|
448
|
+
// Safety: max 30 minutes (1800 polls at 1s interval) to prevent infinite
|
|
449
|
+
// polling if manifest file is deleted or run state becomes unrecoverable.
|
|
450
|
+
const MAX_POLL_COUNT = 1800;
|
|
451
|
+
let pollCount = 0;
|
|
452
|
+
while (record.runId && (record.status === "running" || record.status === "blocked")) {
|
|
453
|
+
if (++pollCount > MAX_POLL_COUNT) {
|
|
454
|
+
logInternalError(
|
|
455
|
+
"subagent-manager.poll-timeout",
|
|
456
|
+
new Error(`pollRunToTerminal exceeded ${MAX_POLL_COUNT} polls for runId=${record.runId}`),
|
|
457
|
+
`id=${record.id}`,
|
|
458
|
+
);
|
|
459
|
+
record.status = "error";
|
|
460
|
+
record.error = `Poll timeout: run did not reach terminal state after ${MAX_POLL_COUNT} seconds`;
|
|
461
|
+
record.completedAt = Date.now();
|
|
462
|
+
savePersistedSubagentRecord(cwd, record);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const loaded = loadRunManifestById(cwd, record.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
466
|
+
if (!loaded) {
|
|
467
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (loaded.manifest.status === "completed") {
|
|
471
|
+
record.status = "completed";
|
|
472
|
+
record.error = undefined;
|
|
473
|
+
record.turnCount = record.turnCount ?? totalRunTurns(cwd, record.runId);
|
|
474
|
+
record.completedAt = Date.now();
|
|
475
|
+
savePersistedSubagentRecord(cwd, record);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (loaded.manifest.status === "failed" || loaded.manifest.status === "cancelled") {
|
|
479
|
+
record.status = loaded.manifest.status;
|
|
480
|
+
record.error = loaded.manifest.summary;
|
|
481
|
+
record.turnCount = record.turnCount ?? totalRunTurns(cwd, record.runId);
|
|
482
|
+
record.completedAt = Date.now();
|
|
483
|
+
savePersistedSubagentRecord(cwd, record);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (loaded.manifest.status === "blocked") {
|
|
487
|
+
record.status = "blocked";
|
|
488
|
+
record.error = undefined;
|
|
489
|
+
if (!record.blockedAt) {
|
|
490
|
+
record.blockedAt = Date.now();
|
|
491
|
+
record.stuckNotified = false;
|
|
492
|
+
record.completedAt = undefined;
|
|
493
|
+
this.onComplete?.(record);
|
|
494
|
+
this.scheduleStuckBlockedNotify(cwd, record);
|
|
495
|
+
this.scheduleBlockedTerminalPoll(cwd, record);
|
|
496
|
+
}
|
|
497
|
+
savePersistedSubagentRecord(cwd, record);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private scheduleBlockedTerminalPoll(cwd: string, record: SubagentRecord): void {
|
|
505
|
+
const poll = (): void => {
|
|
506
|
+
const current = this.records.get(record.id);
|
|
507
|
+
if (current?.status !== "blocked" || !current.runId) return;
|
|
508
|
+
const loaded = loadRunManifestById(cwd, current.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
509
|
+
if (
|
|
510
|
+
!loaded ||
|
|
511
|
+
loaded.manifest.status === "blocked" ||
|
|
512
|
+
loaded.manifest.status === "running" ||
|
|
513
|
+
loaded.manifest.status === "planning" ||
|
|
514
|
+
loaded.manifest.status === "queued"
|
|
515
|
+
) {
|
|
516
|
+
const timer = setTimeout(poll, this.pollIntervalMs);
|
|
517
|
+
timer.unref();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const persisted = readPersistedSubagentRecord(cwd, current.id);
|
|
521
|
+
current.resultConsumed = current.resultConsumed || persisted?.resultConsumed;
|
|
522
|
+
if (loaded.manifest.status === "completed") {
|
|
523
|
+
current.status = "completed";
|
|
524
|
+
current.error = undefined;
|
|
525
|
+
} else if (loaded.manifest.status === "failed" || loaded.manifest.status === "cancelled") {
|
|
526
|
+
current.status = loaded.manifest.status;
|
|
527
|
+
current.error = loaded.manifest.summary;
|
|
528
|
+
} else return;
|
|
529
|
+
current.completedAt = Date.now();
|
|
530
|
+
current.turnCount = current.turnCount ?? totalRunTurns(cwd, current.runId);
|
|
531
|
+
current.durationMs = Math.max(0, current.completedAt - current.startedAt);
|
|
532
|
+
savePersistedSubagentRecord(cwd, current);
|
|
533
|
+
this.onComplete?.(current);
|
|
534
|
+
};
|
|
535
|
+
const timer = setTimeout(poll, this.pollIntervalMs);
|
|
536
|
+
timer.unref();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private scheduleStuckBlockedNotify(cwd: string, record: SubagentRecord): void {
|
|
540
|
+
const threshold = DEFAULT_SUBAGENT.stuckBlockedNotifyMs;
|
|
541
|
+
const fire = (): void => {
|
|
542
|
+
const current = this.records.get(record.id);
|
|
543
|
+
if (current?.status !== "blocked" || !current.blockedAt || current.stuckNotified) return;
|
|
544
|
+
current.stuckNotified = true;
|
|
545
|
+
this.onEvent?.("subagent.stuck-blocked", {
|
|
546
|
+
event: "subagent.stuck-blocked",
|
|
547
|
+
id: current.id,
|
|
548
|
+
runId: current.runId,
|
|
549
|
+
durationMs: Math.max(0, Date.now() - current.blockedAt),
|
|
550
|
+
ownerSessionGeneration: current.ownerSessionGeneration,
|
|
551
|
+
});
|
|
552
|
+
savePersistedSubagentRecord(cwd, current);
|
|
553
|
+
};
|
|
554
|
+
if (threshold <= 0) {
|
|
555
|
+
fire();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const timer = setTimeout(fire, threshold);
|
|
559
|
+
timer.unref();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface SubprocessToolEvent {
|
|
2
|
+
toolName: string;
|
|
3
|
+
toolCallId: string;
|
|
4
|
+
args?: Record<string, unknown>;
|
|
5
|
+
result?: {
|
|
6
|
+
content: Array<{ type: string; text?: string }>;
|
|
7
|
+
details?: unknown;
|
|
8
|
+
};
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SubprocessToolHandler<TData = unknown> {
|
|
13
|
+
extractData?: (event: SubprocessToolEvent) => TData | undefined;
|
|
14
|
+
shouldTerminate?: (event: SubprocessToolEvent) => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SubprocessToolRegistry {
|
|
18
|
+
register<T>(toolName: string, handler: SubprocessToolHandler<T>): void;
|
|
19
|
+
getHandler(toolName: string): SubprocessToolHandler | undefined;
|
|
20
|
+
hasHandler(toolName: string): boolean;
|
|
21
|
+
getRegisteredTools(): string[];
|
|
22
|
+
extractAll(event: SubprocessToolEvent): Record<string, unknown>;
|
|
23
|
+
/** H3: Clear all registered handlers (for test isolation). */
|
|
24
|
+
clear(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class SubprocessToolRegistryImpl implements SubprocessToolRegistry {
|
|
28
|
+
private readonly handlers = new Map<string, SubprocessToolHandler>();
|
|
29
|
+
|
|
30
|
+
register<T>(toolName: string, handler: SubprocessToolHandler<T>): void {
|
|
31
|
+
this.handlers.set(toolName, handler as SubprocessToolHandler);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getHandler(toolName: string): SubprocessToolHandler | undefined {
|
|
35
|
+
return this.handlers.get(toolName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
hasHandler(toolName: string): boolean {
|
|
39
|
+
return this.handlers.has(toolName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getRegisteredTools(): string[] {
|
|
43
|
+
return [...this.handlers.keys()];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
extractAll(event: SubprocessToolEvent): Record<string, unknown> {
|
|
47
|
+
const extracted: Record<string, unknown> = {};
|
|
48
|
+
for (const [toolName, handler] of this.handlers) {
|
|
49
|
+
if (handler.extractData) {
|
|
50
|
+
const data = handler.extractData(event);
|
|
51
|
+
if (data !== undefined) {
|
|
52
|
+
extracted[toolName] = data;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return extracted;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** H3: Clear all registered handlers (for test isolation). */
|
|
60
|
+
clear(): void {
|
|
61
|
+
this.handlers.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const subprocessToolRegistry: SubprocessToolRegistry = new SubprocessToolRegistryImpl();
|
|
66
|
+
|
|
67
|
+
/** @internal Reset the global singleton registry (for test isolation). */
|
|
68
|
+
function resetSubprocessToolRegistry(): void {
|
|
69
|
+
subprocessToolRegistry.clear();
|
|
70
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { appendEvent } from "../state/event-log.ts";
|
|
2
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
3
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
4
|
+
|
|
5
|
+
export interface SupervisorContactPayload {
|
|
6
|
+
runId: string;
|
|
7
|
+
taskId: string;
|
|
8
|
+
reason: "decision_needed" | "clarification" | "approval" | "error_escalation" | "custom";
|
|
9
|
+
message: string;
|
|
10
|
+
data?: Record<string, unknown>;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Record a supervisor contact event from a child task.
|
|
16
|
+
* This represents a child→parent communication where the child needs
|
|
17
|
+
* a decision, clarification, or approval to continue.
|
|
18
|
+
*/
|
|
19
|
+
export function recordSupervisorContact(manifest: TeamRunManifest, payload: Omit<SupervisorContactPayload, "timestamp">): void {
|
|
20
|
+
const fullPayload: SupervisorContactPayload = {
|
|
21
|
+
...payload,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
appendEvent(manifest.eventsPath, {
|
|
26
|
+
type: "supervisor.contact",
|
|
27
|
+
runId: manifest.runId,
|
|
28
|
+
taskId: payload.taskId,
|
|
29
|
+
data: fullPayload as unknown as Record<string, unknown>,
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logInternalError("supervisor-contact.record", error, `runId=${manifest.runId} taskId=${payload.taskId}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse a supervisor contact request from child Pi stdout.
|
|
38
|
+
* Detects structured JSON lines with type "supervisor_contact".
|
|
39
|
+
*/
|
|
40
|
+
export function parseSupervisorContactFromLine(line: string): Omit<SupervisorContactPayload, "timestamp" | "runId"> | undefined {
|
|
41
|
+
if (!line.trim()) return undefined;
|
|
42
|
+
let parsed: unknown;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(line);
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
49
|
+
const record = parsed as Record<string, unknown>;
|
|
50
|
+
if (record.type !== "supervisor_contact" && record.type !== "crew_supervisor_contact") return undefined;
|
|
51
|
+
return {
|
|
52
|
+
taskId: typeof record.taskId === "string" ? record.taskId : "",
|
|
53
|
+
reason:
|
|
54
|
+
typeof record.reason === "string" &&
|
|
55
|
+
["decision_needed", "clarification", "approval", "error_escalation", "custom"].includes(record.reason)
|
|
56
|
+
? (record.reason as SupervisorContactPayload["reason"])
|
|
57
|
+
: "custom",
|
|
58
|
+
message: typeof record.message === "string" ? record.message : String(record.message ?? ""),
|
|
59
|
+
data:
|
|
60
|
+
record.data && typeof record.data === "object" && !Array.isArray(record.data)
|
|
61
|
+
? (record.data as Record<string, unknown>)
|
|
62
|
+
: undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|