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,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RetryRunner - Execute tasks with retry support and summary accumulation.
|
|
3
|
+
*
|
|
4
|
+
* Based on pi-boomerang's --rethrow pattern:
|
|
5
|
+
* - Retries failed tasks up to maxAttempts
|
|
6
|
+
* - Generates handoffs between attempts
|
|
7
|
+
* - Accumulates context from previous attempts
|
|
8
|
+
* - Supports exponential backoff
|
|
9
|
+
* - Limits handoff accumulation to prevent memory leaks
|
|
10
|
+
*
|
|
11
|
+
* @see docs/pi-boomerang-integration-plan.md
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { HandoffManager, HandoffSummary, TaskPacket, TaskResult } from "./handoff-manager.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retry configuration.
|
|
18
|
+
*/
|
|
19
|
+
export interface RetryConfig {
|
|
20
|
+
/** Maximum number of retry attempts */
|
|
21
|
+
maxAttempts: number;
|
|
22
|
+
/** Generate summary between attempts for context accumulation */
|
|
23
|
+
summaryBetweenAttempts?: boolean;
|
|
24
|
+
/** Stop retrying if task succeeds */
|
|
25
|
+
stopOnSuccess?: boolean;
|
|
26
|
+
/** Base backoff delay in milliseconds (multiplied by attempt number) */
|
|
27
|
+
backoffMs?: number;
|
|
28
|
+
/** Backoff multiplier (default: 1) */
|
|
29
|
+
backoffMultiplier?: number;
|
|
30
|
+
/** Maximum backoff delay cap in milliseconds */
|
|
31
|
+
maxBackoffMs?: number;
|
|
32
|
+
/** Custom retry condition (return true to retry) */
|
|
33
|
+
retryCondition?: (result: TaskResult, attempt: number) => boolean;
|
|
34
|
+
/** Maximum handoffs to retain (default: 100) - prevents memory leaks */
|
|
35
|
+
maxHandoffs?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Result of a single attempt.
|
|
40
|
+
*/
|
|
41
|
+
export interface AttemptResult {
|
|
42
|
+
attempt: number;
|
|
43
|
+
result: TaskResult;
|
|
44
|
+
summary?: HandoffSummary;
|
|
45
|
+
duration: number;
|
|
46
|
+
error?: string;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Final retry result.
|
|
52
|
+
*/
|
|
53
|
+
export interface RetryResult {
|
|
54
|
+
success: boolean;
|
|
55
|
+
attempts: AttemptResult[];
|
|
56
|
+
finalResult?: TaskResult;
|
|
57
|
+
totalHandoffs: HandoffSummary[];
|
|
58
|
+
totalDuration: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Task runner interface (minimal for retry functionality).
|
|
63
|
+
*/
|
|
64
|
+
export interface TaskRunnerLike {
|
|
65
|
+
runTask(packet: TaskPacket): Promise<TaskResult>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* RetryRunner handles task execution with automatic retry and summary accumulation.
|
|
70
|
+
*/
|
|
71
|
+
export class RetryRunner {
|
|
72
|
+
private _disposed = false;
|
|
73
|
+
|
|
74
|
+
private taskRunner: TaskRunnerLike;
|
|
75
|
+
private handoffManager: HandoffManager;
|
|
76
|
+
private _handoffs: HandoffSummary[] = [];
|
|
77
|
+
|
|
78
|
+
constructor(taskRunner: TaskRunnerLike, handoffManager: HandoffManager) {
|
|
79
|
+
this.taskRunner = taskRunner;
|
|
80
|
+
this.handoffManager = handoffManager;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if this runner has been disposed.
|
|
85
|
+
*/
|
|
86
|
+
get isDisposed(): boolean {
|
|
87
|
+
return this._disposed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Dispose of resources held by this runner.
|
|
92
|
+
* Clears any accumulated state and prevents further operations.
|
|
93
|
+
*/
|
|
94
|
+
dispose(): void {
|
|
95
|
+
this._disposed = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clear accumulated handoffs to free memory.
|
|
100
|
+
* Useful when you want to reset state without disposing.
|
|
101
|
+
*/
|
|
102
|
+
clearHandoffs(): void {
|
|
103
|
+
this._handoffs = [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the effective max handoffs limit from config or default.
|
|
108
|
+
*/
|
|
109
|
+
private getMaxHandoffs(config: RetryConfig): number {
|
|
110
|
+
return config.maxHandoffs ?? 100;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Trim handoffs array to max size, keeping most recent.
|
|
115
|
+
*/
|
|
116
|
+
private trimHandoffs(handoffs: HandoffSummary[], maxSize: number): HandoffSummary[] {
|
|
117
|
+
if (handoffs.length <= maxSize) {
|
|
118
|
+
return handoffs;
|
|
119
|
+
}
|
|
120
|
+
// Keep the most recent handoffs (last maxSize items)
|
|
121
|
+
return handoffs.slice(-maxSize);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Execute task with retry support.
|
|
126
|
+
* Summaries accumulate between attempts for better context.
|
|
127
|
+
*
|
|
128
|
+
* @param packet - Task packet to execute
|
|
129
|
+
* @param config - Retry configuration (stopOnSuccess defaults to true)
|
|
130
|
+
* @returns Final retry result with all attempts
|
|
131
|
+
*/
|
|
132
|
+
async runWithRetry(packet: TaskPacket, config: RetryConfig): Promise<RetryResult> {
|
|
133
|
+
if (this._disposed) {
|
|
134
|
+
throw new Error("RetryRunner has been disposed");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const attempts: AttemptResult[] = [];
|
|
138
|
+
const handoffs: HandoffSummary[] = [];
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
const maxHandoffs = this.getMaxHandoffs(config);
|
|
141
|
+
|
|
142
|
+
// Clear previous handoffs at start of each retry run
|
|
143
|
+
this._handoffs = [];
|
|
144
|
+
|
|
145
|
+
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
|
|
146
|
+
if (this._disposed) {
|
|
147
|
+
throw new Error("RetryRunner was disposed during retry");
|
|
148
|
+
}
|
|
149
|
+
const attemptStart = Date.now();
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Inject accumulated handoffs into context
|
|
153
|
+
const enrichedPacket = this.enrichPacketWithHandoffs(packet, handoffs, attempt);
|
|
154
|
+
|
|
155
|
+
// Execute task
|
|
156
|
+
const result = await this.taskRunner.runTask(enrichedPacket);
|
|
157
|
+
|
|
158
|
+
const attemptResult: AttemptResult = {
|
|
159
|
+
attempt,
|
|
160
|
+
result,
|
|
161
|
+
duration: Date.now() - attemptStart,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Generate summary between attempts
|
|
166
|
+
if (config.summaryBetweenAttempts !== false) {
|
|
167
|
+
const summary = await this.handoffManager.generateSummary(packet, result);
|
|
168
|
+
attemptResult.summary = summary;
|
|
169
|
+
handoffs.push(summary);
|
|
170
|
+
|
|
171
|
+
// Trim handoffs to prevent memory leak
|
|
172
|
+
if (handoffs.length > maxHandoffs) {
|
|
173
|
+
handoffs.splice(0, handoffs.length - maxHandoffs);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
attempts.push(attemptResult);
|
|
178
|
+
|
|
179
|
+
// Stop on success if configured (default to true when undefined)
|
|
180
|
+
if ((config.stopOnSuccess ?? true) && result.outcome === "success") {
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
attempts,
|
|
184
|
+
finalResult: result,
|
|
185
|
+
totalHandoffs: handoffs,
|
|
186
|
+
totalDuration: Date.now() - startTime,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check custom retry condition
|
|
191
|
+
if (config.retryCondition && !config.retryCondition(result, attempt)) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check default retry condition (retry on failure)
|
|
196
|
+
if (result.outcome !== "success" && attempt < config.maxAttempts) {
|
|
197
|
+
// Apply backoff before retry
|
|
198
|
+
const backoffDelay = this.calculateBackoff(
|
|
199
|
+
config.backoffMs ?? 1000,
|
|
200
|
+
attempt,
|
|
201
|
+
config.backoffMultiplier ?? 1,
|
|
202
|
+
config.maxBackoffMs,
|
|
203
|
+
);
|
|
204
|
+
if (backoffDelay > 0) {
|
|
205
|
+
await this.sleep(backoffDelay);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
attempts.push({
|
|
210
|
+
attempt,
|
|
211
|
+
result: {
|
|
212
|
+
outcome: "failure",
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
},
|
|
215
|
+
duration: Date.now() - attemptStart,
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
error: error instanceof Error ? error.message : String(error),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Apply backoff before retry on error
|
|
221
|
+
if (config.backoffMs && attempt < config.maxAttempts) {
|
|
222
|
+
const backoffDelay = this.calculateBackoff(
|
|
223
|
+
config.backoffMs,
|
|
224
|
+
attempt,
|
|
225
|
+
config.backoffMultiplier ?? 1,
|
|
226
|
+
config.maxBackoffMs,
|
|
227
|
+
);
|
|
228
|
+
if (backoffDelay > 0) {
|
|
229
|
+
await this.sleep(backoffDelay);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const finalAttempt = attempts[attempts.length - 1];
|
|
236
|
+
return {
|
|
237
|
+
success: finalAttempt?.result.outcome === "success",
|
|
238
|
+
attempts,
|
|
239
|
+
finalResult: finalAttempt?.result,
|
|
240
|
+
totalHandoffs: this.trimHandoffs(handoffs, maxHandoffs),
|
|
241
|
+
totalDuration: Date.now() - startTime,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Enrich packet with accumulated handoffs from previous attempts.
|
|
247
|
+
*/
|
|
248
|
+
private enrichPacketWithHandoffs(packet: TaskPacket, handoffs: HandoffSummary[], attempt: number): TaskPacket {
|
|
249
|
+
if (handoffs.length === 0) {
|
|
250
|
+
return packet;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Build accumulated context from previous attempts
|
|
254
|
+
const accumulatedContext = handoffs
|
|
255
|
+
.map(
|
|
256
|
+
(h, index) =>
|
|
257
|
+
`## Attempt ${index + 1}: ${h.task}\n` +
|
|
258
|
+
`Outcome: ${h.outcome}\n` +
|
|
259
|
+
`Files: created=${h.filesCreated.join(", ")}, modified=${h.filesModified.join(", ")}\n` +
|
|
260
|
+
`Decisions: ${h.decisions.map((d) => d.rationale).join("; ")}\n` +
|
|
261
|
+
`Blockers: ${h.blockers.join(", ")}\n` +
|
|
262
|
+
`Next Steps: ${h.nextSteps.join(", ")}\n`,
|
|
263
|
+
)
|
|
264
|
+
.join("\n---\n");
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
...packet,
|
|
268
|
+
context: {
|
|
269
|
+
...packet.context,
|
|
270
|
+
__boomerangAttempts: attempt,
|
|
271
|
+
__boomerangHandoffs: handoffs,
|
|
272
|
+
__boomerangContext: `Previous attempts summary:\n${accumulatedContext}`,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Calculate backoff delay with optional capping.
|
|
279
|
+
*/
|
|
280
|
+
private calculateBackoff(baseMs: number, attempt: number, multiplier: number, maxBackoffMs?: number): number {
|
|
281
|
+
let delay = baseMs * multiplier ** (attempt - 1);
|
|
282
|
+
if (maxBackoffMs !== undefined) {
|
|
283
|
+
delay = Math.min(delay, maxBackoffMs);
|
|
284
|
+
}
|
|
285
|
+
return delay;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Sleep helper.
|
|
290
|
+
*/
|
|
291
|
+
private sleep(ms: number): Promise<void> {
|
|
292
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create a RetryRunner with default dependencies.
|
|
298
|
+
*/
|
|
299
|
+
export function createRetryRunner(taskRunner: TaskRunnerLike, handoffManager: HandoffManager): RetryRunner {
|
|
300
|
+
return new RetryRunner(taskRunner, handoffManager);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Default retry config for common scenarios.
|
|
305
|
+
*/
|
|
306
|
+
export const DEFAULT_RETRY_CONFIG: RetryConfig = {
|
|
307
|
+
maxAttempts: 3,
|
|
308
|
+
summaryBetweenAttempts: true,
|
|
309
|
+
stopOnSuccess: true,
|
|
310
|
+
backoffMs: 1000,
|
|
311
|
+
backoffMultiplier: 2,
|
|
312
|
+
maxBackoffMs: 30000,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Retry config for transient failures (quick retries).
|
|
317
|
+
*/
|
|
318
|
+
export const TRANSIENT_FAILURE_RETRY_CONFIG: RetryConfig = {
|
|
319
|
+
maxAttempts: 2,
|
|
320
|
+
summaryBetweenAttempts: false,
|
|
321
|
+
stopOnSuccess: true,
|
|
322
|
+
backoffMs: 500,
|
|
323
|
+
backoffMultiplier: 1,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Retry config for persistent failures (exponential backoff).
|
|
328
|
+
*/
|
|
329
|
+
export const PERSISTENT_FAILURE_RETRY_CONFIG: RetryConfig = {
|
|
330
|
+
maxAttempts: 5,
|
|
331
|
+
summaryBetweenAttempts: true,
|
|
332
|
+
stopOnSuccess: true,
|
|
333
|
+
backoffMs: 2000,
|
|
334
|
+
backoffMultiplier: 2,
|
|
335
|
+
maxBackoffMs: 60000,
|
|
336
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
|
|
2
|
+
|
|
3
|
+
// (FIND-12 R1 review: the AgentPermissionOptions opt-in was dropped — no caller
|
|
4
|
+
// passed `options` and AgentConfig has no `permissions` field, so it was dead
|
|
5
|
+
// code. Custom write-capable roles must instead be added to WRITE_ROLES below.)
|
|
6
|
+
|
|
7
|
+
// Read-only roles: cannot mutate files/source. `verifier` is NOT here — it runs
|
|
8
|
+
// tests (bash + cache writes) so it is a WRITE role (F4). `planner` stays
|
|
9
|
+
// read-only to preserve the plan-approval gate boundary (F3).
|
|
10
|
+
const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
|
|
11
|
+
// Write-capable roles: can mutate files/source within the workspace.
|
|
12
|
+
// FIND-12 (R1 review): this is an EXPLICIT allowlist — every shipped write-capable
|
|
13
|
+
// role must be listed here, otherwise default-deny demotes it to read-only.
|
|
14
|
+
// `agent` is the default direct-agent role (run.ts); `cold-verifier` runs bash/
|
|
15
|
+
// cache writes; `chain-executor` is the chain-workflow executor variant;
|
|
16
|
+
// `worker` is the autonomous goal-loop + dynamic-workflow executor role
|
|
17
|
+
// (goal-loop-runner.ts, run.ts dynamic workflow synthesis).
|
|
18
|
+
const WRITE_ROLES = new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
|
|
19
|
+
export interface PermissionCheckResult {
|
|
20
|
+
allowed: boolean;
|
|
21
|
+
mode: RolePermissionMode;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the permission mode for a given role.
|
|
27
|
+
*
|
|
28
|
+
* FIND-12 (2026-07-20): **default-deny** — unknown/unrecognized roles now
|
|
29
|
+
* receive `"read_only"` instead of the previous permissive `"workspace_write"`.
|
|
30
|
+
* This prevents privilege escalation via typo'd or custom agent names that
|
|
31
|
+
* accidentally inherit write capabilities. Write-capable roles are an EXPLICIT
|
|
32
|
+
* allowlist (`WRITE_ROLES`); to add a custom write-capable role, add it there.
|
|
33
|
+
* (R1 review: a per-agent-config `permissions.workspaceWrite` opt-in was
|
|
34
|
+
* considered but dropped — it was unreachable from all 8 call sites.)
|
|
35
|
+
*
|
|
36
|
+
* @param role the role name (case-sensitive, must match exactly)
|
|
37
|
+
*/
|
|
38
|
+
export function permissionForRole(role: string): RolePermissionMode {
|
|
39
|
+
if (READ_ONLY_ROLES.has(role)) return "read_only";
|
|
40
|
+
if (WRITE_ROLES.has(role)) return "workspace_write";
|
|
41
|
+
// FIND-12: default-deny for unknown/unrecognized roles.
|
|
42
|
+
return "read_only";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
46
|
+
return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
|
|
50
|
+
if (!role) return { allowed: true, mode: "workspace_write" };
|
|
51
|
+
const mode = permissionForRole(role);
|
|
52
|
+
if (mode === "read_only")
|
|
53
|
+
return {
|
|
54
|
+
allowed: false,
|
|
55
|
+
mode,
|
|
56
|
+
reason: `Role '${role}' is read-only and cannot spawn additional subagents.`,
|
|
57
|
+
};
|
|
58
|
+
return { allowed: true, mode };
|
|
59
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
|
+
import { writeArtifact } from "../state/artifact-store.ts";
|
|
3
|
+
import { appendEventAsync } from "../state/event-log.ts";
|
|
4
|
+
import { saveRunTasksAsync, updateRunStatus } from "../state/state-store.ts";
|
|
5
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
6
|
+
import type { WorkflowStep } from "../workflows/workflow-config.ts";
|
|
7
|
+
import { DEFAULT_RETRY_POLICY, executeWithRetry } from "./retry-executor.ts";
|
|
8
|
+
import { permissionForRole } from "./role-permission.ts";
|
|
9
|
+
import { runWorker } from "./run-worker.ts";
|
|
10
|
+
import type { CrewRuntimeMode } from "./runtime-resolver.ts";
|
|
11
|
+
import { sanitizeTaskText } from "./task-packet.ts";
|
|
12
|
+
import { splitCoalescedOutput } from "./task-runner/output-splitter.ts";
|
|
13
|
+
import { mergeArtifacts } from "./team-runner-artifacts.ts";
|
|
14
|
+
import { createWorkerHeartbeat, touchWorkerHeartbeat } from "./worker-heartbeat.ts";
|
|
15
|
+
import { buildWorkspaceTree } from "./workspace-tree.ts";
|
|
16
|
+
|
|
17
|
+
export interface CoalescedTaskGroupInput {
|
|
18
|
+
manifest: TeamRunManifest;
|
|
19
|
+
tasks: TeamTaskState[];
|
|
20
|
+
groupTasks: TeamTaskState[];
|
|
21
|
+
step: WorkflowStep;
|
|
22
|
+
agent: AgentConfig;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
executeWorkers: boolean;
|
|
25
|
+
runtimeKind?: CrewRuntimeMode;
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
28
|
+
teamRole?: unknown;
|
|
29
|
+
perTaskRuntime?: CrewRuntimeMode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CoalescedTaskGroupResult {
|
|
33
|
+
manifest: TeamRunManifest;
|
|
34
|
+
tasks: TeamTaskState[];
|
|
35
|
+
taskIds: string[];
|
|
36
|
+
rawOutput: string;
|
|
37
|
+
success: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runCoalescedTaskGroup(input: CoalescedTaskGroupInput): Promise<CoalescedTaskGroupResult> {
|
|
41
|
+
const { manifest, groupTasks, step, agent, signal, executeWorkers } = input;
|
|
42
|
+
const groupId = groupTasks.map((t) => t.id).join("+");
|
|
43
|
+
const firstTask = groupTasks[0]!;
|
|
44
|
+
const taskIds = groupTasks.map((t) => t.id);
|
|
45
|
+
|
|
46
|
+
// Set ALL N tasks to "running" before spawn so the dashboard reflects
|
|
47
|
+
// the in-flight state.
|
|
48
|
+
let updatedTasks: TeamTaskState[] = input.tasks.map((t) => {
|
|
49
|
+
if (taskIds.includes(t.id) && t.status !== "running") {
|
|
50
|
+
return { ...t, status: "running" as const, startedAt: new Date().toISOString() };
|
|
51
|
+
}
|
|
52
|
+
return t;
|
|
53
|
+
});
|
|
54
|
+
await saveRunTasksAsync(manifest, updatedTasks);
|
|
55
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
56
|
+
type: "task.coalesced_dispatch_start",
|
|
57
|
+
runId: manifest.runId,
|
|
58
|
+
message: `Dispatching ${groupTasks.length} coalesced tasks in 1 worker (role=${firstTask.role}, cwd=${firstTask.cwd})`,
|
|
59
|
+
data: { groupId, role: firstTask.role, cwd: firstTask.cwd, taskIds },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const combinedPrompt = await buildCoalescedPrompt(manifest, step, groupTasks, agent);
|
|
63
|
+
|
|
64
|
+
// FIX (M6): write heartbeats for ALL N tasks in the coalesced group so the
|
|
65
|
+
// background watcher doesn't fire heartbeat_dead against the (single)
|
|
66
|
+
// child pi worker. Previously runCoalescedTaskGroup never wrote heartbeats,
|
|
67
|
+
// so the watcher saw `heartbeat.lastSeenAt: undefined` within 2 seconds of
|
|
68
|
+
// spawn and emitted heartbeat_dead even though the worker continued to
|
|
69
|
+
// completion (false-positive stuck-worker alarms). The singleton path
|
|
70
|
+
// (runTeamTask) writes heartbeats via persistHeartbeat — M6 needs the
|
|
71
|
+
// equivalent to avoid the false-positive.
|
|
72
|
+
updatedTasks = updatedTasks.map((t) => {
|
|
73
|
+
if (!taskIds.includes(t.id)) return t;
|
|
74
|
+
return {
|
|
75
|
+
...t,
|
|
76
|
+
heartbeat: t.heartbeat ?? createWorkerHeartbeat(t.id),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
await saveRunTasksAsync(manifest, updatedTasks);
|
|
80
|
+
|
|
81
|
+
let rawOutput = "";
|
|
82
|
+
let success = false;
|
|
83
|
+
// FIND-06: serialize heartbeat saves and retain the active save so terminal
|
|
84
|
+
// results can drain it before their final write.
|
|
85
|
+
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
86
|
+
let heartbeatInFlight = false;
|
|
87
|
+
let heartbeatPromise: Promise<void> | null = null;
|
|
88
|
+
// FIND-06 P1 fix (R1 review): a heartbeat save that exceeds the 5s drain
|
|
89
|
+
// timeout continues in the background and can rename its temp file AFTER
|
|
90
|
+
// the terminal write, clobbering terminal state with the pre-terminal
|
|
91
|
+
// snapshot. finalWriteStarted lets the IIFE repair that by re-writing the
|
|
92
|
+
// (now-terminal) updatedTasks AFTER its own save resolves, so the terminal
|
|
93
|
+
// state always lands last regardless of disk timing.
|
|
94
|
+
let finalWriteStarted = false;
|
|
95
|
+
if (!executeWorkers) {
|
|
96
|
+
rawOutput = buildScaffoldOutput(groupTasks);
|
|
97
|
+
success = true;
|
|
98
|
+
} else {
|
|
99
|
+
// Heartbeat refresher: touch every task's heartbeat every 15s while the
|
|
100
|
+
// worker is alive. Set `alive: true` explicitly so post-completion
|
|
101
|
+
// staleness checks immediately recognize liveness.
|
|
102
|
+
heartbeatTimer = setInterval(() => {
|
|
103
|
+
// FIND-06: setInterval does not await async callbacks. Do not start a
|
|
104
|
+
// second read/modify/write while the preceding heartbeat save is active.
|
|
105
|
+
if (heartbeatInFlight) return;
|
|
106
|
+
heartbeatInFlight = true;
|
|
107
|
+
heartbeatPromise = (async () => {
|
|
108
|
+
try {
|
|
109
|
+
updatedTasks = updatedTasks.map((t) => {
|
|
110
|
+
if (!taskIds.includes(t.id)) return t;
|
|
111
|
+
// FIND-06 belt-and-suspenders: never replace terminal state or its
|
|
112
|
+
// resultArtifact with a heartbeat-only snapshot.
|
|
113
|
+
if (t.status === "completed" || t.status === "failed") return t;
|
|
114
|
+
return {
|
|
115
|
+
...t,
|
|
116
|
+
heartbeat: touchWorkerHeartbeat(t.heartbeat ?? createWorkerHeartbeat(t.id), { alive: true }),
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
await saveRunTasksAsync(manifest, updatedTasks);
|
|
120
|
+
} catch {
|
|
121
|
+
// Run may have been pruned mid-dispatch — best-effort only.
|
|
122
|
+
} finally {
|
|
123
|
+
heartbeatInFlight = false;
|
|
124
|
+
// FIND-06 P1 fix: if the terminal write started while our heartbeat
|
|
125
|
+
// save was in flight, re-write the (now-terminal) updatedTasks so
|
|
126
|
+
// our late snapshot cannot leave stale pre-terminal state on disk.
|
|
127
|
+
// Runs after our own save resolves, so it always lands last.
|
|
128
|
+
if (finalWriteStarted) {
|
|
129
|
+
try {
|
|
130
|
+
await saveRunTasksAsync(manifest, updatedTasks);
|
|
131
|
+
} catch {
|
|
132
|
+
// best-effort repair — same swallow policy as the heartbeat.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
}, 15_000);
|
|
138
|
+
try {
|
|
139
|
+
const result = await executeWithRetry(
|
|
140
|
+
async () => {
|
|
141
|
+
return await runWorker({
|
|
142
|
+
cwd: firstTask.cwd,
|
|
143
|
+
task: combinedPrompt,
|
|
144
|
+
agent,
|
|
145
|
+
signal,
|
|
146
|
+
excludeContextBash: true,
|
|
147
|
+
maxTurns: 5,
|
|
148
|
+
onJsonEvent: (e) => input.onJsonEvent?.(firstTask.id, manifest.runId, e),
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
DEFAULT_RETRY_POLICY,
|
|
152
|
+
{ signal },
|
|
153
|
+
);
|
|
154
|
+
rawOutput = result.rawFinalText ?? result.stdout ?? "";
|
|
155
|
+
success = result.exitStatus?.exitCode === 0;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
rawOutput = `Worker dispatch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
158
|
+
success = false;
|
|
159
|
+
} finally {
|
|
160
|
+
if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const split = splitCoalescedOutput(rawOutput, taskIds);
|
|
165
|
+
|
|
166
|
+
const finishedAt = new Date().toISOString();
|
|
167
|
+
const newArtifacts: TeamRunManifest["artifacts"] = [];
|
|
168
|
+
updatedTasks = updatedTasks.map((t) => {
|
|
169
|
+
if (!taskIds.includes(t.id)) return t;
|
|
170
|
+
const entry = split.find((s) => s.taskId === t.id);
|
|
171
|
+
const ok = success && Boolean(entry?.text);
|
|
172
|
+
const text = entry?.text ?? rawOutput;
|
|
173
|
+
// BUGFIX (M6 real dispatch): write to artifactsRoot via writeArtifact
|
|
174
|
+
// so task.resultArtifact is set and aggregateTaskOutputs can read
|
|
175
|
+
// the per-task text. Previously the coalesced path used a raw
|
|
176
|
+
// writeFile to stateRoot/results/<id>.txt which aggregated task
|
|
177
|
+
// outputs could not locate — they only consult task.resultArtifact.
|
|
178
|
+
// Result: tasks reported "EMPTY OUTPUT" in the batch summary even
|
|
179
|
+
// though on-disk results were correct.
|
|
180
|
+
const resultArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
181
|
+
kind: "result",
|
|
182
|
+
relativePath: `results/${t.id}.txt`,
|
|
183
|
+
content: text,
|
|
184
|
+
producer: t.id,
|
|
185
|
+
});
|
|
186
|
+
newArtifacts.push(resultArtifact);
|
|
187
|
+
return {
|
|
188
|
+
...t,
|
|
189
|
+
status: ok ? ("completed" as const) : ("failed" as const),
|
|
190
|
+
finishedAt,
|
|
191
|
+
result: {
|
|
192
|
+
text,
|
|
193
|
+
producer: groupId,
|
|
194
|
+
strategy: entry?.strategy ?? "broadcast",
|
|
195
|
+
},
|
|
196
|
+
resultArtifact,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// FIND-06: stop ticks and drain the snapshot captured by any active
|
|
201
|
+
// heartbeat before persisting terminal results. Bound the wait so a stuck
|
|
202
|
+
// filesystem operation cannot block dispatch completion indefinitely.
|
|
203
|
+
// P1 fix (R1 review): set finalWriteStarted BEFORE the drain so any pending
|
|
204
|
+
// heartbeat IIFE observes it and re-writes the terminal state after its
|
|
205
|
+
// own (possibly late) save resolves — preventing a late heartbeat snapshot
|
|
206
|
+
// from clobbering the terminal write.
|
|
207
|
+
finalWriteStarted = true;
|
|
208
|
+
if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
|
|
209
|
+
const pendingHeartbeat = heartbeatPromise;
|
|
210
|
+
if (pendingHeartbeat) {
|
|
211
|
+
let drainTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
212
|
+
try {
|
|
213
|
+
await Promise.race([
|
|
214
|
+
pendingHeartbeat,
|
|
215
|
+
new Promise<void>((resolve) => {
|
|
216
|
+
drainTimeout = setTimeout(resolve, 5_000);
|
|
217
|
+
}),
|
|
218
|
+
]);
|
|
219
|
+
} finally {
|
|
220
|
+
if (drainTimeout !== undefined) clearTimeout(drainTimeout);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
await saveRunTasksAsync(manifest, updatedTasks);
|
|
224
|
+
let updatedManifest: TeamRunManifest = {
|
|
225
|
+
...manifest,
|
|
226
|
+
artifacts: mergeArtifacts([...manifest.artifacts, ...newArtifacts]),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (success) {
|
|
230
|
+
updatedManifest = updateRunStatus(updatedManifest, "running");
|
|
231
|
+
}
|
|
232
|
+
await appendEventAsync(updatedManifest.eventsPath, {
|
|
233
|
+
type: "task.coalesced_dispatch_end",
|
|
234
|
+
runId: manifest.runId,
|
|
235
|
+
message: `Coalesced dispatch ${success ? "completed" : "failed"} (${taskIds.length} tasks, ${split[0]?.strategy ?? "broadcast"} split)`,
|
|
236
|
+
data: { groupId, taskIds, success, strategy: split[0]?.strategy },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return { manifest: updatedManifest, tasks: updatedTasks, taskIds, rawOutput, success };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function buildCoalescedPrompt(
|
|
243
|
+
manifest: TeamRunManifest,
|
|
244
|
+
step: WorkflowStep,
|
|
245
|
+
groupTasks: TeamTaskState[],
|
|
246
|
+
agent: AgentConfig,
|
|
247
|
+
): Promise<string> {
|
|
248
|
+
const tree = await buildWorkspaceTree(groupTasks[0]!.cwd);
|
|
249
|
+
const treeBlock = tree.rendered ? `# Workspace Structure\n${tree.rendered}` : "";
|
|
250
|
+
const roleInstructions =
|
|
251
|
+
permissionForRole(groupTasks[0]!.role) === "read_only"
|
|
252
|
+
? `You are running in READ-ONLY mode. Do not create, modify, delete, or move files. Emit your findings as TEXT in your final output.`
|
|
253
|
+
: "";
|
|
254
|
+
|
|
255
|
+
const taskBlocks = groupTasks
|
|
256
|
+
.map((task, idx) => {
|
|
257
|
+
return [
|
|
258
|
+
`### Task ${idx + 1} of ${groupTasks.length} (id: ${task.id})`,
|
|
259
|
+
`Step: ${step.id}`,
|
|
260
|
+
`Role: ${step.role}`,
|
|
261
|
+
`Task: ${sanitizeTaskText(step.task.replaceAll("{goal}", manifest.goal))}`,
|
|
262
|
+
].join("\n");
|
|
263
|
+
})
|
|
264
|
+
.join("\n\n---\n\n");
|
|
265
|
+
|
|
266
|
+
const outputInstructions = groupTasks.map((task) => `<<<TASK_RESULT:${task.id}>>>`).join(" ... ");
|
|
267
|
+
|
|
268
|
+
return [
|
|
269
|
+
"# pi-crew Coalesced Worker Prompt",
|
|
270
|
+
`Run ID: ${manifest.runId}`,
|
|
271
|
+
`Team: ${manifest.team}`,
|
|
272
|
+
`Workflow: ${manifest.workflow ?? "(none)"}`,
|
|
273
|
+
`Goal: ${manifest.goal}`,
|
|
274
|
+
`Tasks in this batch: ${groupTasks.length}`,
|
|
275
|
+
``,
|
|
276
|
+
roleInstructions,
|
|
277
|
+
``,
|
|
278
|
+
treeBlock,
|
|
279
|
+
``,
|
|
280
|
+
`# Your Tasks`,
|
|
281
|
+
`Complete ALL ${groupTasks.length} tasks below. For each, structure your final output using the delimiters shown.`,
|
|
282
|
+
``,
|
|
283
|
+
taskBlocks,
|
|
284
|
+
``,
|
|
285
|
+
`# Output Format (CRITICAL)`,
|
|
286
|
+
`After completing all tasks, structure your final output using these delimiters:`,
|
|
287
|
+
``,
|
|
288
|
+
outputInstructions,
|
|
289
|
+
``,
|
|
290
|
+
`Wrap each task's result between the start and end delimiters:`,
|
|
291
|
+
`<<<TASK_RESULT:{taskId}>>>`,
|
|
292
|
+
`...your result for this task...`,
|
|
293
|
+
`<<<END_TASK_RESULT>>>`,
|
|
294
|
+
``,
|
|
295
|
+
`If delimiters don't fit your workflow, use \`### Task N of M\` headings and we'll parse those instead.`,
|
|
296
|
+
]
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.join("\n");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function buildScaffoldOutput(groupTasks: TeamTaskState[]): string {
|
|
302
|
+
return groupTasks
|
|
303
|
+
.map(
|
|
304
|
+
(task, idx) =>
|
|
305
|
+
`<<<TASK_RESULT:${task.id}>>>\nScaffold result for task ${idx + 1} of ${groupTasks.length}: ${task.id}\n<<<END_TASK_RESULT>>>`,
|
|
306
|
+
)
|
|
307
|
+
.join("\n\n");
|
|
308
|
+
}
|