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,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORE-5 extraction 4 (FINAL): child-process task executor.
|
|
3
|
+
*
|
|
4
|
+
* Lifts the `runtimeKind === "child-process"` branch of `runTeamTask`
|
|
5
|
+
* (model routing + model-fallback attempt loop, `runWorker()` call with
|
|
6
|
+
* onSpawn/onStdoutLine/onJsonEvent/onLifecycleEvent callbacks,
|
|
7
|
+
* timeoutController + externalAbortListener wiring (R3 listener-leak fix),
|
|
8
|
+
* heartbeat persistence, transcript parsing + result-artifact assembly,
|
|
9
|
+
* model-attempt logging) into a single {@link runChildProcessTask} function.
|
|
10
|
+
*
|
|
11
|
+
* Takes the pre-execution context ({@link TaskExecutionContext}) + the
|
|
12
|
+
* stream bridge handle, mutates `ctx.task`/`ctx.tasks` in place (callback
|
|
13
|
+
* closures close over the function's local `task`/`tasks`, then sync back to
|
|
14
|
+
* ctx), and returns the branch output bag ({@link TaskExecutionResult}) for
|
|
15
|
+
* {@link finalizeTaskResult} to consume.
|
|
16
|
+
*
|
|
17
|
+
* Extracted verbatim from `runTeamTask` — no behavioral changes. The
|
|
18
|
+
* characterization tests (task-runner-characterization.test.ts scenarios
|
|
19
|
+
* 2/3/4/11) lock this behavior; char #11 (R3 listener-leak structural)
|
|
20
|
+
* now reads THIS file for the clearTimeout/removeEventListener contract.
|
|
21
|
+
*
|
|
22
|
+
* Also moves three private helpers that were used ONLY in this branch
|
|
23
|
+
* (appendSteeringAsync, appendBackgroundLogAsync,
|
|
24
|
+
* resolveTaskScopeModelsPatterns) and the exported
|
|
25
|
+
* detectRetryableModelFailureFromOutput (re-exported from task-runner.ts
|
|
26
|
+
* for the rate-limit-429-detection test).
|
|
27
|
+
*/
|
|
28
|
+
import * as fs from "node:fs";
|
|
29
|
+
import * as path from "node:path";
|
|
30
|
+
import { loadConfig } from "../../config/config.ts";
|
|
31
|
+
import { errors } from "../../errors.ts";
|
|
32
|
+
import { writeArtifact } from "../../state/artifact-store.ts";
|
|
33
|
+
import { appendEventAsync, appendEventBuffered } from "../../state/event-log.ts";
|
|
34
|
+
import type { ArtifactDescriptor, OperationTerminalEvidence, TeamRunManifest } from "../../state/types.ts";
|
|
35
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
36
|
+
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
37
|
+
import { buildSyntheticTerminalEvidence, cancellationReasonFromSignal } from "../cancellation.ts";
|
|
38
|
+
import type { ChildPiLifecycleEvent } from "../child-pi.ts";
|
|
39
|
+
import {
|
|
40
|
+
appendCrewAgentEvent,
|
|
41
|
+
appendCrewAgentOutput,
|
|
42
|
+
emptyCrewAgentProgress,
|
|
43
|
+
recordFromTask,
|
|
44
|
+
upsertCrewAgent,
|
|
45
|
+
} from "../crew-agent-records.ts";
|
|
46
|
+
import { crewHooks } from "../crew-hooks.ts";
|
|
47
|
+
import { bridgeEventFromJsonEvent } from "../event-stream-bridge.ts";
|
|
48
|
+
import {
|
|
49
|
+
buildConfiguredModelRouting,
|
|
50
|
+
formatModelAttemptNote,
|
|
51
|
+
isRetryableModelFailure,
|
|
52
|
+
type ModelAttemptSummary,
|
|
53
|
+
} from "../model-fallback.ts";
|
|
54
|
+
import { readEnabledModelsPatterns } from "../model-scope.ts";
|
|
55
|
+
import { type ParsedPiJsonOutput, parsePiJsonOutput } from "../pi-json-output.ts";
|
|
56
|
+
import { type ProgressEventSummary, shouldAppendProgressEventUpdate } from "../progress-event-coalescer.ts";
|
|
57
|
+
import { DEFAULT_RETRY_POLICY } from "../retry-executor.ts";
|
|
58
|
+
import { runWorker } from "../run-worker.ts";
|
|
59
|
+
import { parseSessionUsage } from "../session-usage.ts";
|
|
60
|
+
import { parseSupervisorContactFromLine, recordSupervisorContact } from "../supervisor-contact.ts";
|
|
61
|
+
import { createWorkerHeartbeat, touchWorkerHeartbeat } from "../worker-heartbeat.ts";
|
|
62
|
+
import { createStartupEvidence } from "../worker-startup.ts";
|
|
63
|
+
import type { TaskExecutionResult } from "./post-execution.ts";
|
|
64
|
+
import type { StreamBridgeHandle, TaskExecutionContext } from "./pre-execution.ts";
|
|
65
|
+
import { applyAgentProgressEvent, applyUsageToProgress, progressEventSummary, shouldFlushProgressEvent } from "./progress.ts";
|
|
66
|
+
import { cleanResultText, isFinalChildEvent } from "./result-utils.ts";
|
|
67
|
+
import { checkpointTask, persistSingleTaskUpdate, updateTask } from "./state-helpers.ts";
|
|
68
|
+
import { tailReadWithLineSnap } from "./tail-read.ts";
|
|
69
|
+
|
|
70
|
+
/** Async helper for writing steering events — fire-and-forget for non-blocking writes. */
|
|
71
|
+
async function appendSteeringAsync(steeringDir: string, taskId: string, steers: string[]): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
await fs.promises.mkdir(steeringDir, { recursive: true });
|
|
74
|
+
const steeringPath = resolveRealContainedPath(steeringDir, `${taskId}.jsonl`);
|
|
75
|
+
const lines = steers
|
|
76
|
+
.map(
|
|
77
|
+
(msg) =>
|
|
78
|
+
JSON.stringify({
|
|
79
|
+
type: "steer",
|
|
80
|
+
message: msg,
|
|
81
|
+
ts: new Date().toISOString(),
|
|
82
|
+
}) + "\n",
|
|
83
|
+
)
|
|
84
|
+
.join("");
|
|
85
|
+
await fs.promises.appendFile(steeringPath, lines, "utf-8");
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logInternalError("task-runner.steering-write-failed", error as Error, `taskId=${taskId}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Async helper for writing background logs — fire-and-forget for non-blocking writes. */
|
|
92
|
+
async function appendBackgroundLogAsync(bgLogPath: string, eventLine: string): Promise<void> {
|
|
93
|
+
try {
|
|
94
|
+
await fs.promises.appendFile(bgLogPath, `${eventLine}\n`, "utf-8");
|
|
95
|
+
} catch (error) {
|
|
96
|
+
logInternalError("task-runner.background-log-write-failed", error as Error, `path=${bgLogPath}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* F7: resolve the enabledModels allowlist for the child-process spawn path,
|
|
102
|
+
* but only if `runtime.reliability.scopeModels` is ON. Returns [] (no-op)
|
|
103
|
+
* when the toggle is off or the allowlist is empty. Best-effort: any failure
|
|
104
|
+
* to read config or the allowlist silently disables the gate so spawn is
|
|
105
|
+
* never blocked by a misconfiguration.
|
|
106
|
+
*/
|
|
107
|
+
async function resolveTaskScopeModelsPatterns(cwd: string): Promise<string[]> {
|
|
108
|
+
let scopeModels = false;
|
|
109
|
+
try {
|
|
110
|
+
scopeModels = loadConfig(cwd).config.reliability?.scopeModels === true;
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
if (!scopeModels) return [];
|
|
115
|
+
return readEnabledModelsPatterns(cwd);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 429/rate-limit detection (PI_CREW_TOOLING_429_NOTE.md).
|
|
120
|
+
*
|
|
121
|
+
* A worker can exit code 0 with no hard error, yet the transcript is full of
|
|
122
|
+
* `message_end` events carrying `errorMessage: "429 ... overloaded"` (or any
|
|
123
|
+
* retryable model-failure pattern) and empty content arrays. The model never
|
|
124
|
+
* produced a tool call, so the worker "completed" without doing anything.
|
|
125
|
+
*
|
|
126
|
+
* This helper inspects a ParsedPiJsonOutput and, if the run produced only
|
|
127
|
+
* retryable model-failure messages AND no real output text (no finalText, no
|
|
128
|
+
* text events, no patches), returns a surfaced error string so the
|
|
129
|
+
* model-fallback chain (isRetryableModelFailure) can retry on another model.
|
|
130
|
+
* Returns undefined when the run has real output (the 429s were recovered from)
|
|
131
|
+
* or when there are no retryable error messages.
|
|
132
|
+
*/
|
|
133
|
+
export function detectRetryableModelFailureFromOutput(parsed: ParsedPiJsonOutput): string | undefined {
|
|
134
|
+
// Primary signal: pre-extracted `errorMessages` (from pi-json-output parser).
|
|
135
|
+
// The parser already filters to non-empty trimmed strings from message_end
|
|
136
|
+
// events.
|
|
137
|
+
const messages = parsed.errorMessages;
|
|
138
|
+
if (messages && messages.length > 0) {
|
|
139
|
+
// Find the first retryable model-failure message
|
|
140
|
+
// (429 / rate-limit / overloaded / 5xx / ...).
|
|
141
|
+
const retryable = messages.find((m) => isRetryableModelFailure(m));
|
|
142
|
+
if (retryable) {
|
|
143
|
+
// Did the run actually produce real output despite the transient errors?
|
|
144
|
+
// If finalText / textEvents / patches exist, the model recovered and we
|
|
145
|
+
// should NOT mark the run as failed — only flag it when the worker
|
|
146
|
+
// yielded nothing (the 429-only case from the bug report).
|
|
147
|
+
const hasRealOutput =
|
|
148
|
+
(parsed.finalText?.trim().length ?? 0) > 0 ||
|
|
149
|
+
parsed.textEvents.some((t) => t.trim().length > 0) ||
|
|
150
|
+
(parsed.patches?.length ?? 0) > 0;
|
|
151
|
+
if (hasRealOutput) return undefined;
|
|
152
|
+
return `Model returned only retryable errors and no output: ${retryable}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Secondary signal (FIX 3, task packet 01_01-agent): inspect a raw
|
|
156
|
+
// `messageEndEvents` (or `transcript`) array on the parsed output. The
|
|
157
|
+
// ParsedPiJsonOutput type does not currently declare this field, so we
|
|
158
|
+
// read it through a local extension cast. Callers that pass it (tests, a
|
|
159
|
+
// future parser that captures the full event stream) get a second chance
|
|
160
|
+
// to surface retryable failures. Primary path still wins when it matches.
|
|
161
|
+
const raw = parsed as ParsedPiJsonOutput & {
|
|
162
|
+
messageEndEvents?: unknown;
|
|
163
|
+
transcript?: unknown;
|
|
164
|
+
};
|
|
165
|
+
const eventSource = Array.isArray(raw.messageEndEvents)
|
|
166
|
+
? raw.messageEndEvents
|
|
167
|
+
: Array.isArray(raw.transcript)
|
|
168
|
+
? raw.transcript
|
|
169
|
+
: undefined;
|
|
170
|
+
if (!eventSource || eventSource.length === 0) return undefined;
|
|
171
|
+
for (const candidate of eventSource) {
|
|
172
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
173
|
+
const event = candidate as {
|
|
174
|
+
stopReason?: unknown;
|
|
175
|
+
errorMessage?: unknown;
|
|
176
|
+
};
|
|
177
|
+
if (event.stopReason !== "error") continue;
|
|
178
|
+
if (typeof event.errorMessage !== "string" || event.errorMessage.length === 0) continue;
|
|
179
|
+
if (!isRetryableModelFailure(event.errorMessage)) continue;
|
|
180
|
+
// Same real-output gate as the primary signal — don't flag runs that
|
|
181
|
+
// recovered with real final text / patches.
|
|
182
|
+
const hasRealOutput =
|
|
183
|
+
(parsed.finalText?.trim().length ?? 0) > 0 ||
|
|
184
|
+
parsed.textEvents.some((t) => t.trim().length > 0) ||
|
|
185
|
+
(parsed.patches?.length ?? 0) > 0;
|
|
186
|
+
if (hasRealOutput) return undefined;
|
|
187
|
+
return `Model returned only retryable errors and no output: ${event.errorMessage}`;
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Execute the child-process branch of `runTeamTask`: resolve model routing,
|
|
194
|
+
* run the model-fallback attempt loop (spawning a worker per candidate model
|
|
195
|
+
* via {@link runWorker}), wire the wall-clock timeout + external-abort listener
|
|
196
|
+
* (R3 listener-leak cleanup in finally), persist heartbeats/progress, parse
|
|
197
|
+
* the transcript, and assemble the result/log/transcript artifacts.
|
|
198
|
+
*
|
|
199
|
+
* Mutates `ctx.task` and `ctx.tasks` in place (the `runWorker` callbacks
|
|
200
|
+
* close over local `task`/`tasks` and sync back before returning). Returns
|
|
201
|
+
* the branch output bag ({@link TaskExecutionResult}) for
|
|
202
|
+
* {@link finalizeTaskResult} to consume.
|
|
203
|
+
*
|
|
204
|
+
* @param ctx The pre-execution context. `ctx.streamBridge` is the
|
|
205
|
+
* UI event-bus handle (may be undefined).
|
|
206
|
+
* @returns The branch execution result.
|
|
207
|
+
*/
|
|
208
|
+
export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<TaskExecutionResult> {
|
|
209
|
+
const input = ctx.input;
|
|
210
|
+
const manifest: TeamRunManifest = ctx.manifest;
|
|
211
|
+
let task = ctx.task;
|
|
212
|
+
let tasks = ctx.tasks;
|
|
213
|
+
const prompt = ctx.prompt;
|
|
214
|
+
const skillPaths = ctx.skillPaths;
|
|
215
|
+
const collectedJsonEvents = ctx.collectedJsonEvents;
|
|
216
|
+
const streamBridge: StreamBridgeHandle | undefined = ctx.streamBridge;
|
|
217
|
+
|
|
218
|
+
let resultArtifact: ArtifactDescriptor;
|
|
219
|
+
let logArtifact: ArtifactDescriptor | undefined;
|
|
220
|
+
let transcriptArtifact: ArtifactDescriptor | undefined;
|
|
221
|
+
let exitCode: number | null = 0;
|
|
222
|
+
let error: string | undefined;
|
|
223
|
+
let modelAttempts: ModelAttemptSummary[] | undefined;
|
|
224
|
+
let parsedOutput: ParsedPiJsonOutput | undefined;
|
|
225
|
+
let rawFinalText: string | undefined;
|
|
226
|
+
let intermediateFindings: string | undefined;
|
|
227
|
+
let finalStdout = "";
|
|
228
|
+
let transcriptPath: string | undefined;
|
|
229
|
+
let terminalEvidence: OperationTerminalEvidence[] = [];
|
|
230
|
+
let startupEvidence = ctx.startupEvidence;
|
|
231
|
+
|
|
232
|
+
const modelRoutingPlan = buildConfiguredModelRouting({
|
|
233
|
+
overrideModel: input.modelOverride,
|
|
234
|
+
stepModel: input.step.model,
|
|
235
|
+
teamRoleModel: input.teamRoleModel,
|
|
236
|
+
agentModel: input.agent.model,
|
|
237
|
+
fallbackModels: input.agent.fallbackModels,
|
|
238
|
+
parentModel: input.parentModel,
|
|
239
|
+
modelRegistry: input.modelRegistry,
|
|
240
|
+
cwd: task.cwd,
|
|
241
|
+
scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd),
|
|
242
|
+
});
|
|
243
|
+
const candidates = modelRoutingPlan.candidates;
|
|
244
|
+
const attemptModels = candidates.length > 0 ? candidates : [undefined];
|
|
245
|
+
// CORE-3: auto-compute per-task spawn budget on first entry.
|
|
246
|
+
// Budget = attemptModels.length × (maxAttempts + 1) — always one
|
|
247
|
+
// full attempt-worth above the theoretical maximum of
|
|
248
|
+
// maxAttempts × attemptModels.length. Only computes once (max=0 guard).
|
|
249
|
+
if (input.spawnBudget && input.spawnBudget.max === 0) {
|
|
250
|
+
input.spawnBudget.max = attemptModels.length * (DEFAULT_RETRY_POLICY.maxAttempts + 1);
|
|
251
|
+
}
|
|
252
|
+
const logs: string[] = [];
|
|
253
|
+
let finalStderr = "";
|
|
254
|
+
modelAttempts = [];
|
|
255
|
+
let finalCheckpointWritten = false;
|
|
256
|
+
let lastAgentRecordPersistedAt = 0;
|
|
257
|
+
let lastHeartbeatPersistedAt = 0;
|
|
258
|
+
let lastRunProgressPersistedAt = 0;
|
|
259
|
+
let lastTaskProgressPersistedAt = 0;
|
|
260
|
+
let lastRunProgressSummary: ProgressEventSummary | undefined;
|
|
261
|
+
const persistHeartbeat = (force = false): void => {
|
|
262
|
+
const now = Date.now();
|
|
263
|
+
// Skip disk write if throttled (unless forced).
|
|
264
|
+
if (!force && now - lastHeartbeatPersistedAt < 1000) return;
|
|
265
|
+
try {
|
|
266
|
+
// Write to disk first, then update in-memory.
|
|
267
|
+
// Disk state is always <= in-memory state, so a crash never produces
|
|
268
|
+
// a fresher in-memory heartbeat than what's on disk. This prevents the
|
|
269
|
+
// stale reconciler from seeing a live heartbeat paired with stale task state
|
|
270
|
+
// (which could cause false zombie detection).
|
|
271
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
272
|
+
} catch (err) {
|
|
273
|
+
// Run state may have been deleted by prune/forget/cleanup.
|
|
274
|
+
// This is not fatal — the run is gone, no point persisting.
|
|
275
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
// Now update in-memory heartbeat so it is always >= persisted state.
|
|
279
|
+
task = {
|
|
280
|
+
...task,
|
|
281
|
+
heartbeat: touchWorkerHeartbeat(task.heartbeat ?? createWorkerHeartbeat(task.id)),
|
|
282
|
+
};
|
|
283
|
+
lastHeartbeatPersistedAt = now;
|
|
284
|
+
};
|
|
285
|
+
const persistChildProgress = (event: unknown, force = false): void => {
|
|
286
|
+
const now = Date.now();
|
|
287
|
+
if (force || shouldFlushProgressEvent(event) || now - lastAgentRecordPersistedAt >= 500) {
|
|
288
|
+
upsertCrewAgent(manifest, recordFromTask(manifest, task, "child-process"));
|
|
289
|
+
lastAgentRecordPersistedAt = now;
|
|
290
|
+
}
|
|
291
|
+
const summary = progressEventSummary(task, event);
|
|
292
|
+
const decision = shouldAppendProgressEventUpdate({
|
|
293
|
+
previous: lastRunProgressSummary,
|
|
294
|
+
next: summary,
|
|
295
|
+
nowMs: now,
|
|
296
|
+
lastAppendMs: lastRunProgressPersistedAt || undefined,
|
|
297
|
+
minIntervalMs: 1000,
|
|
298
|
+
force,
|
|
299
|
+
});
|
|
300
|
+
if (decision.shouldAppend) {
|
|
301
|
+
// 2.2 caller migration: high-frequency task.progress goes through
|
|
302
|
+
// the buffered path (M7 wire); loss-on-kill is acceptable because progress
|
|
303
|
+
// is informational and re-derivable from per-agent records.
|
|
304
|
+
// appendEventBuffered coalesces into a single lock acquire after bufferMs,
|
|
305
|
+
// reducing producer p95 from ~13µs (serial) to ~0µs (bench M7).
|
|
306
|
+
void appendEventBuffered(manifest.eventsPath, {
|
|
307
|
+
type: "task.progress",
|
|
308
|
+
runId: manifest.runId,
|
|
309
|
+
taskId: task.id,
|
|
310
|
+
data: { ...summary, coalesceReason: decision.reason },
|
|
311
|
+
});
|
|
312
|
+
lastRunProgressSummary = summary;
|
|
313
|
+
lastRunProgressPersistedAt = now;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
for (let i = 0; i < attemptModels.length; i++) {
|
|
317
|
+
// M1 fix: set transcript path per attempt to avoid mixing across fallback attempts.
|
|
318
|
+
transcriptPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${i}.jsonl`;
|
|
319
|
+
// Ensure transcripts/ subdirectory exists before child-pi appends
|
|
320
|
+
// to it. appendTranscript uses O_APPEND (no mkdir) for security,
|
|
321
|
+
// so the caller must create the directory.
|
|
322
|
+
await fs.promises.mkdir(path.join(manifest.artifactsRoot, "transcripts"), {
|
|
323
|
+
recursive: true,
|
|
324
|
+
});
|
|
325
|
+
const model = attemptModels[i];
|
|
326
|
+
// CORE-3: per-task spawn budget cap. Track total runWorker spawns
|
|
327
|
+
// across ALL retry attempts × model fallback iterations. When
|
|
328
|
+
// the budget is exhausted, break the loop using the last error
|
|
329
|
+
// as the final result.
|
|
330
|
+
if (input.spawnBudget) {
|
|
331
|
+
input.spawnBudget.count += 1;
|
|
332
|
+
if (input.spawnBudget.count > input.spawnBudget.max) {
|
|
333
|
+
logs.push(
|
|
334
|
+
`[WARN] CORE-3 spawn budget exhausted (max=${input.spawnBudget.max}) — ` +
|
|
335
|
+
`stopping model fallback after ${modelAttempts.length} attempt(s). ` +
|
|
336
|
+
`Last error: ${error ?? "<none>"}`,
|
|
337
|
+
"",
|
|
338
|
+
);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const attemptStartedAt = new Date();
|
|
343
|
+
const pendingAttempt: ModelAttemptSummary = {
|
|
344
|
+
model: model ?? "default",
|
|
345
|
+
success: false,
|
|
346
|
+
};
|
|
347
|
+
task = {
|
|
348
|
+
...task,
|
|
349
|
+
modelAttempts: [...modelAttempts, pendingAttempt],
|
|
350
|
+
};
|
|
351
|
+
tasks = updateTask(tasks, task);
|
|
352
|
+
crewHooks.emit({
|
|
353
|
+
type: "task_started",
|
|
354
|
+
timestamp: new Date().toISOString(),
|
|
355
|
+
runId: manifest.runId,
|
|
356
|
+
taskId: task.id,
|
|
357
|
+
data: { role: task.role, model: model ?? "default" },
|
|
358
|
+
});
|
|
359
|
+
upsertCrewAgent(manifest, recordFromTask(manifest, task, "child-process"));
|
|
360
|
+
// W2 fix — wall-clock timeout per task. We create our own
|
|
361
|
+
// AbortController, link the caller's signal to it, and abort
|
|
362
|
+
// from a timer. The internal signal is passed to runChildPi so
|
|
363
|
+
// the existing SIGTERM → SIGKILL escalation in child-pi.ts
|
|
364
|
+
// handles cleanup. Prevents runaway agent loops (e.g. 11_build
|
|
365
|
+
// in the oh-my-pi distill run that re-verified completed files
|
|
366
|
+
// 14+ times).
|
|
367
|
+
const taskTimeoutMs = input.runtimeConfig?.taskTimeoutMs ?? 0;
|
|
368
|
+
const timeoutController = new AbortController();
|
|
369
|
+
// W2 fix (memory leak) — store the listener reference so we can
|
|
370
|
+
// removeEventListener() it in the finally block below. { once: true }
|
|
371
|
+
// alone is NOT enough: when the timeout fires first, the listener
|
|
372
|
+
// never fires → { once: true } never auto-removes → listener stays
|
|
373
|
+
// attached to input.signal for the rest of the run (run-level
|
|
374
|
+
// signal = long-lived → leak accumulates per task run).
|
|
375
|
+
let externalAbortListener: (() => void) | undefined;
|
|
376
|
+
if (input.signal) {
|
|
377
|
+
if (input.signal.aborted) {
|
|
378
|
+
timeoutController.abort(input.signal.reason);
|
|
379
|
+
} else {
|
|
380
|
+
externalAbortListener = () => timeoutController.abort(input.signal!.reason);
|
|
381
|
+
input.signal.addEventListener("abort", externalAbortListener, { once: true });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
385
|
+
if (taskTimeoutMs > 0 && !timeoutController.signal.aborted) {
|
|
386
|
+
timeoutHandle = setTimeout(() => {
|
|
387
|
+
if (!timeoutController.signal.aborted) {
|
|
388
|
+
timeoutController.abort(new Error(`Task exceeded wall-clock timeout of ${taskTimeoutMs}ms`));
|
|
389
|
+
}
|
|
390
|
+
}, taskTimeoutMs);
|
|
391
|
+
timeoutHandle.unref?.();
|
|
392
|
+
}
|
|
393
|
+
let childResult;
|
|
394
|
+
try {
|
|
395
|
+
childResult = await runWorker({
|
|
396
|
+
cwd: task.cwd,
|
|
397
|
+
task: prompt,
|
|
398
|
+
agent: input.agent,
|
|
399
|
+
model,
|
|
400
|
+
signal: timeoutController.signal,
|
|
401
|
+
transcriptPath,
|
|
402
|
+
maxDepth: input.limits?.maxTaskDepth,
|
|
403
|
+
skillPaths,
|
|
404
|
+
maxTurns: input.runtimeConfig?.maxTurns,
|
|
405
|
+
graceTurns: input.runtimeConfig?.graceTurns,
|
|
406
|
+
inheritContext: input.runtimeConfig?.inheritContext,
|
|
407
|
+
parentContext: input.parentContext,
|
|
408
|
+
excludeContextBash: input.runtimeConfig?.excludeContextBash,
|
|
409
|
+
sessionId: manifest.sessionId,
|
|
410
|
+
role: task.role,
|
|
411
|
+
runId: manifest.runId,
|
|
412
|
+
agentId: task.id,
|
|
413
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
414
|
+
steeringFile: resolveRealContainedPath(`${manifest.artifactsRoot}/steering`, `${task.id}.jsonl`),
|
|
415
|
+
onSpawn: (pid) => {
|
|
416
|
+
try {
|
|
417
|
+
({ task, tasks } = checkpointTask(manifest, tasks, task, "child-spawned", pid));
|
|
418
|
+
if (task.pendingSteers?.length) {
|
|
419
|
+
const steeringDir = `${manifest.artifactsRoot}/steering`;
|
|
420
|
+
// Fire-and-forget async write for steering events
|
|
421
|
+
void appendSteeringAsync(steeringDir, task.id, task.pendingSteers);
|
|
422
|
+
task.pendingSteers = [];
|
|
423
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
424
|
+
}
|
|
425
|
+
} catch (err) {
|
|
426
|
+
logInternalError("task-runner.on-spawn", err as Error, `pid=${pid}, taskId=${task.id}`);
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
onLifecycleEvent: (event: ChildPiLifecycleEvent) => {
|
|
430
|
+
void appendEventAsync(manifest.eventsPath, {
|
|
431
|
+
type: `worker.${event.type}` as const,
|
|
432
|
+
runId: manifest.runId,
|
|
433
|
+
taskId: task.id,
|
|
434
|
+
message: `Worker lifecycle: ${event.type}${event.error ? ` error=${event.error}` : ""}${event.exitCode != null ? ` exit=${event.exitCode}` : ""}`,
|
|
435
|
+
data: { ...event },
|
|
436
|
+
}).catch((error) => logInternalError("task-runner.lifecycle-event", error, `taskId=${task.id}, type=${event.type}`));
|
|
437
|
+
},
|
|
438
|
+
onStdoutLine: (line) => {
|
|
439
|
+
appendCrewAgentOutput(manifest, task.id, line);
|
|
440
|
+
persistHeartbeat();
|
|
441
|
+
// Check for supervisor contact requests from child Pi
|
|
442
|
+
const contact = parseSupervisorContactFromLine(line);
|
|
443
|
+
if (contact) {
|
|
444
|
+
recordSupervisorContact(manifest, {
|
|
445
|
+
runId: manifest.runId,
|
|
446
|
+
...contact,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
onJsonEvent: (event) => {
|
|
451
|
+
// Top-level error boundary: prevent any single event from crashing the task.
|
|
452
|
+
// Errors are logged but processing continues so subsequent events still update state.
|
|
453
|
+
try {
|
|
454
|
+
appendCrewAgentEvent(manifest, task.id, event);
|
|
455
|
+
if (collectedJsonEvents && event && typeof event === "object" && !Array.isArray(event))
|
|
456
|
+
collectedJsonEvents.push(event as Record<string, unknown>);
|
|
457
|
+
if (collectedJsonEvents && collectedJsonEvents.length > 1000) {
|
|
458
|
+
collectedJsonEvents.splice(0, collectedJsonEvents.length - 1000);
|
|
459
|
+
}
|
|
460
|
+
// Accumulate lifetime usage via message_end events (survives compaction)
|
|
461
|
+
if (event && typeof event === "object" && (event as Record<string, unknown>).type === "message_end") {
|
|
462
|
+
const msg = (event as Record<string, unknown>).message as Record<string, unknown> | undefined;
|
|
463
|
+
if (msg?.role === "assistant") {
|
|
464
|
+
const usage = msg.usage as Record<string, number> | undefined;
|
|
465
|
+
if (usage) {
|
|
466
|
+
task.lifetimeUsage = {
|
|
467
|
+
input: (task.lifetimeUsage?.input ?? 0) + (usage.input ?? 0),
|
|
468
|
+
output: (task.lifetimeUsage?.output ?? 0) + (usage.output ?? 0),
|
|
469
|
+
cacheWrite: (task.lifetimeUsage?.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
persistHeartbeat();
|
|
475
|
+
// Bug #3 fix: Write worker JSON events to background.log for debugging when running in background mode.
|
|
476
|
+
// This supplements the event log so developers can see what the child Pi worker produced.
|
|
477
|
+
if (process.env.PI_CREW_BACKGROUND_MODE === "1" && event) {
|
|
478
|
+
const bgLogPath = `${manifest.stateRoot}/background.log`;
|
|
479
|
+
const eventLine = typeof event === "object" && !Array.isArray(event) ? JSON.stringify(event) : String(event);
|
|
480
|
+
// Fire-and-forget async write for background log
|
|
481
|
+
void appendBackgroundLogAsync(bgLogPath, eventLine);
|
|
482
|
+
}
|
|
483
|
+
// Always keep in-memory agentProgress fresh (cheap) so the UI/events see
|
|
484
|
+
// the latest progress, but THROTTLE the disk persist. Previously this
|
|
485
|
+
// did a full locked read-parse-write of tasks.json on EVERY child JSON
|
|
486
|
+
// event — a 200-event task produced 200 such cycles (Round 15 P1).
|
|
487
|
+
// Final state is force-flushed on task completion (persistHeartbeat(true)).
|
|
488
|
+
const nextProgress = applyAgentProgressEvent(task.agentProgress ?? emptyCrewAgentProgress(), event, task.startedAt);
|
|
489
|
+
task = { ...task, agentProgress: nextProgress };
|
|
490
|
+
tasks = updateTask(tasks, task);
|
|
491
|
+
const progressNow = Date.now();
|
|
492
|
+
if (progressNow - lastTaskProgressPersistedAt >= 500) {
|
|
493
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
494
|
+
lastTaskProgressPersistedAt = progressNow;
|
|
495
|
+
}
|
|
496
|
+
// Bridge event to UI event bus for near-instant updates
|
|
497
|
+
const bridgeEvent = bridgeEventFromJsonEvent(manifest.runId, task.id, event);
|
|
498
|
+
if (bridgeEvent) streamBridge?.handler(bridgeEvent);
|
|
499
|
+
// Feed overflow recovery tracker
|
|
500
|
+
if (input.onJsonEvent) {
|
|
501
|
+
input.onJsonEvent(task.id, manifest.runId, event);
|
|
502
|
+
}
|
|
503
|
+
if (!finalCheckpointWritten && isFinalChildEvent(event)) {
|
|
504
|
+
finalCheckpointWritten = true;
|
|
505
|
+
({ task, tasks } = checkpointTask(manifest, tasks, task, "child-stdout-final"));
|
|
506
|
+
}
|
|
507
|
+
persistChildProgress(event);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
logInternalError("task-runner.on-json-event", err as Error, `taskId=${task.id}`);
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
} finally {
|
|
514
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
515
|
+
// W2 fix — release the listener so it doesn't leak. {once:true}
|
|
516
|
+
// only auto-removes when the listener FIRES; if the timeout
|
|
517
|
+
// fires first, the listener never fires and stays attached
|
|
518
|
+
// to input.signal (run-level signal = long-lived → leak per
|
|
519
|
+
// task). Remove explicitly here.
|
|
520
|
+
if (externalAbortListener && input.signal) {
|
|
521
|
+
input.signal.removeEventListener("abort", externalAbortListener);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const evidenceStatus = childResult.exitStatus?.cancelled
|
|
525
|
+
? "cancelled"
|
|
526
|
+
: childResult.error || (childResult.exitCode && childResult.exitCode !== 0)
|
|
527
|
+
? "failed"
|
|
528
|
+
: "completed";
|
|
529
|
+
terminalEvidence = [
|
|
530
|
+
...terminalEvidence,
|
|
531
|
+
{
|
|
532
|
+
operation: "worker",
|
|
533
|
+
status: evidenceStatus,
|
|
534
|
+
startedAt: attemptStartedAt.toISOString(),
|
|
535
|
+
finishedAt: new Date().toISOString(),
|
|
536
|
+
...(input.signal?.aborted
|
|
537
|
+
? {
|
|
538
|
+
reason: cancellationReasonFromSignal(input.signal),
|
|
539
|
+
}
|
|
540
|
+
: {}),
|
|
541
|
+
...(childResult.exitStatus ? { exitStatus: childResult.exitStatus } : {}),
|
|
542
|
+
},
|
|
543
|
+
];
|
|
544
|
+
if (evidenceStatus === "cancelled") {
|
|
545
|
+
const cancelReason = input.signal?.aborted
|
|
546
|
+
? cancellationReasonFromSignal(input.signal)
|
|
547
|
+
: {
|
|
548
|
+
code: "caller_cancelled" as const,
|
|
549
|
+
message: "Worker cancelled.",
|
|
550
|
+
};
|
|
551
|
+
terminalEvidence.push(buildSyntheticTerminalEvidence("tool", cancelReason, attemptStartedAt.toISOString()));
|
|
552
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
553
|
+
type: "worker.cancelled",
|
|
554
|
+
runId: manifest.runId,
|
|
555
|
+
taskId: task.id,
|
|
556
|
+
message: cancelReason.message,
|
|
557
|
+
data: { terminalEvidence: terminalEvidence.at(-1) },
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
startupEvidence = createStartupEvidence({
|
|
561
|
+
command: "pi",
|
|
562
|
+
startedAt: attemptStartedAt,
|
|
563
|
+
finishedAt: new Date(),
|
|
564
|
+
promptSentAt: attemptStartedAt,
|
|
565
|
+
promptAccepted: childResult.exitCode === 0 && !childResult.error,
|
|
566
|
+
stderr: childResult.stderr,
|
|
567
|
+
error: childResult.error,
|
|
568
|
+
exitCode: childResult.exitCode,
|
|
569
|
+
});
|
|
570
|
+
exitCode = childResult.exitCode;
|
|
571
|
+
finalStdout = childResult.stdout;
|
|
572
|
+
finalStderr = childResult.stderr;
|
|
573
|
+
// Cap transcript read to MAX_TRANSCRIPT_BYTES to avoid OOM on huge transcripts.
|
|
574
|
+
const MAX_TRANSCRIPT_PARSE_BYTES = 5 * 1024 * 1024;
|
|
575
|
+
const transcriptText = tailReadWithLineSnap(transcriptPath, MAX_TRANSCRIPT_PARSE_BYTES, childResult.stdout);
|
|
576
|
+
parsedOutput = parsePiJsonOutput(transcriptText);
|
|
577
|
+
rawFinalText = childResult.rawFinalText;
|
|
578
|
+
intermediateFindings = childResult.intermediateFindings;
|
|
579
|
+
error =
|
|
580
|
+
childResult.error ||
|
|
581
|
+
(childResult.exitCode && childResult.exitCode !== 0
|
|
582
|
+
? childResult.stderr || `Child Pi exited with ${childResult.exitCode}`
|
|
583
|
+
: undefined);
|
|
584
|
+
// E1/E7 (Round 15): when the child timed out, surface a structured
|
|
585
|
+
// CrewError (E007) so users get a code + actionable help hint instead
|
|
586
|
+
// of a bare 'no new output for N ms'. We keep .message as the task error.
|
|
587
|
+
if (childResult.exitStatus?.timedOut) {
|
|
588
|
+
error = errors.childTimeout({
|
|
589
|
+
taskId: task.id,
|
|
590
|
+
stderr: childResult.stderr,
|
|
591
|
+
}).message;
|
|
592
|
+
}
|
|
593
|
+
// 429/rate-limit fix (PI_CREW_TOOLING_429_NOTE.md): a worker can exit
|
|
594
|
+
// code 0 with NO hard error, but the transcript is full of
|
|
595
|
+
// `message_end` events with `errorMessage: "429 ... overloaded"` and
|
|
596
|
+
// empty content. The model never produced a tool call, so the worker
|
|
597
|
+
// "completed" without doing anything. Detect this: if no error was set
|
|
598
|
+
// above AND the parsed output carries a retryable model-failure message
|
|
599
|
+
// AND there is no real output text, surface it as an error so the
|
|
600
|
+
// model-fallback chain can retry on another model.
|
|
601
|
+
if (!error && parsedOutput) {
|
|
602
|
+
const rateLimitErr = detectRetryableModelFailureFromOutput(parsedOutput);
|
|
603
|
+
if (rateLimitErr) error = rateLimitErr;
|
|
604
|
+
}
|
|
605
|
+
persistHeartbeat(true);
|
|
606
|
+
persistChildProgress({ type: "attempt_finished" }, true);
|
|
607
|
+
const attempt: ModelAttemptSummary = {
|
|
608
|
+
model: model ?? "default",
|
|
609
|
+
success: !error,
|
|
610
|
+
exitCode,
|
|
611
|
+
error,
|
|
612
|
+
};
|
|
613
|
+
modelAttempts.push(attempt);
|
|
614
|
+
task = { ...task, modelAttempts: [...modelAttempts] };
|
|
615
|
+
tasks = updateTask(tasks, task);
|
|
616
|
+
logs.push(
|
|
617
|
+
`MODEL ATTEMPT ${i + 1}: ${attempt.model}`,
|
|
618
|
+
`success=${attempt.success}`,
|
|
619
|
+
`exitCode=${attempt.exitCode ?? "null"}`,
|
|
620
|
+
attempt.error ? `error=${attempt.error}` : "",
|
|
621
|
+
"",
|
|
622
|
+
);
|
|
623
|
+
if (!error) break;
|
|
624
|
+
let nextModel = attemptModels[i + 1];
|
|
625
|
+
// FIX 1 (task packet 01_01-agent): when the precomputed attempt
|
|
626
|
+
// chain is exhausted but the failure is retryable, do a one-shot
|
|
627
|
+
// re-resolve via buildConfiguredModelRouting with the failed
|
|
628
|
+
// model as parent. This finds alternative providers/models the
|
|
629
|
+
// original chain missed (e.g. a registry gained new fallbacks
|
|
630
|
+
// after the precompute, or the precompute ran before the parent
|
|
631
|
+
// model was known). If a different candidate is found, use it as
|
|
632
|
+
// nextModel; otherwise fall through to the existing break.
|
|
633
|
+
if (!nextModel && isRetryableModelFailure(error)) {
|
|
634
|
+
const reResolved = buildConfiguredModelRouting({
|
|
635
|
+
overrideModel: undefined,
|
|
636
|
+
stepModel: undefined,
|
|
637
|
+
teamRoleModel: undefined,
|
|
638
|
+
agentModel: undefined,
|
|
639
|
+
fallbackModels: undefined,
|
|
640
|
+
parentModel: attempt.model,
|
|
641
|
+
modelRegistry: input.modelRegistry,
|
|
642
|
+
cwd: task.cwd,
|
|
643
|
+
scopeModelsPatterns: await resolveTaskScopeModelsPatterns(task.cwd),
|
|
644
|
+
});
|
|
645
|
+
const alt = reResolved.candidates.find((c) => c !== attempt.model);
|
|
646
|
+
if (alt) nextModel = alt;
|
|
647
|
+
}
|
|
648
|
+
if (!nextModel || !isRetryableModelFailure(error)) break;
|
|
649
|
+
logs.push(formatModelAttemptNote(attempt, nextModel), "");
|
|
650
|
+
}
|
|
651
|
+
// E2 (Round 15): when the fallback chain was used and STILL failed, surface
|
|
652
|
+
// that explicitly. Without this the task error only shows the last
|
|
653
|
+
// attempt's raw failure, so users can't tell whether to fix an API key,
|
|
654
|
+
// upgrade a plan, or change the model config. Include the chain tried +
|
|
655
|
+
// the final reason.
|
|
656
|
+
if (error && modelAttempts.length > 1) {
|
|
657
|
+
// E2/E1 (Round 15): structured CrewError (E008). Build via the factory so
|
|
658
|
+
// the error carries a code + help hint; keep its .message as the task error.
|
|
659
|
+
error = errors.modelExhausted(
|
|
660
|
+
modelAttempts.map((a) => a.model),
|
|
661
|
+
error,
|
|
662
|
+
).message;
|
|
663
|
+
}
|
|
664
|
+
// NEW-8 fix: register all attempt transcripts as artifacts, not just the used one.
|
|
665
|
+
// Earlier failed attempts' transcripts exist on disk but were invisible to the artifact system.
|
|
666
|
+
const successfulAttemptIndex = modelAttempts.findIndex((attempt) => attempt.success);
|
|
667
|
+
const usedAttempt = successfulAttemptIndex === -1 ? Math.max(0, modelAttempts.length - 1) : successfulAttemptIndex;
|
|
668
|
+
for (let attemptIdx = 0; attemptIdx < modelAttempts.length; attemptIdx++) {
|
|
669
|
+
if (attemptIdx === usedAttempt) continue;
|
|
670
|
+
const tPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${attemptIdx}.jsonl`;
|
|
671
|
+
if (!fs.existsSync(tPath)) continue;
|
|
672
|
+
const MAX_ATTEMPT_TRANSCRIPT = 5 * 1024 * 1024;
|
|
673
|
+
const tContent = tailReadWithLineSnap(tPath, MAX_ATTEMPT_TRANSCRIPT, "");
|
|
674
|
+
if (tContent) {
|
|
675
|
+
writeArtifact(manifest.artifactsRoot, {
|
|
676
|
+
kind: "log",
|
|
677
|
+
relativePath: `transcripts/${task.id}.attempt-${attemptIdx}.jsonl`,
|
|
678
|
+
content: tContent,
|
|
679
|
+
producer: task.id,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
resultArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
684
|
+
kind: "result",
|
|
685
|
+
relativePath: `results/${task.id}.txt`,
|
|
686
|
+
content:
|
|
687
|
+
// Prefer the RAW (uncapped) final assistant text captured before the
|
|
688
|
+
// transcript's 16K compaction — this is the authoritative worker output.
|
|
689
|
+
// Fall back to transcript-derived finalText, then stdout/stderr, so a
|
|
690
|
+
// missing raw capture (mock/error path) never yields empty/garbage.
|
|
691
|
+
cleanResultText(rawFinalText) ??
|
|
692
|
+
cleanResultText(parsedOutput?.finalText) ??
|
|
693
|
+
cleanResultText(finalStdout) ??
|
|
694
|
+
cleanResultText(finalStderr) ??
|
|
695
|
+
// #7 hardening: if all real output paths are empty (worker exhausted
|
|
696
|
+
// budget on tool calls, no assistant text), use intermediate findings.
|
|
697
|
+
// intermediateFindings captures the last N tool-result display lines.
|
|
698
|
+
cleanResultText(intermediateFindings) ??
|
|
699
|
+
"(no output)",
|
|
700
|
+
producer: task.id,
|
|
701
|
+
});
|
|
702
|
+
logArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
703
|
+
kind: "log",
|
|
704
|
+
relativePath: `logs/${task.id}.log`,
|
|
705
|
+
content: [
|
|
706
|
+
...logs,
|
|
707
|
+
`finalExitCode=${exitCode ?? "null"}`,
|
|
708
|
+
`jsonEvents=${parsedOutput?.jsonEvents ?? 0}`,
|
|
709
|
+
parsedOutput?.usage ? `usage=${JSON.stringify(parsedOutput.usage)}` : "",
|
|
710
|
+
"",
|
|
711
|
+
"STDOUT:",
|
|
712
|
+
finalStdout,
|
|
713
|
+
"",
|
|
714
|
+
"STDERR:",
|
|
715
|
+
finalStderr,
|
|
716
|
+
].join("\n"),
|
|
717
|
+
producer: task.id,
|
|
718
|
+
});
|
|
719
|
+
const resolvedModel = modelAttempts[usedAttempt]?.model ?? candidates[0] ?? "default";
|
|
720
|
+
const fallbackReason = usedAttempt > 0 ? modelAttempts[usedAttempt - 1]?.error : undefined;
|
|
721
|
+
task = {
|
|
722
|
+
...task,
|
|
723
|
+
modelRouting: {
|
|
724
|
+
requested: modelRoutingPlan.requested,
|
|
725
|
+
resolved: resolvedModel,
|
|
726
|
+
fallbackChain: candidates,
|
|
727
|
+
reason: fallbackReason ?? modelRoutingPlan.reason,
|
|
728
|
+
usedAttempt,
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
tasks = updateTask(tasks, task);
|
|
732
|
+
// Use the last attempt's transcript for session usage.
|
|
733
|
+
// Safety net: transcriptPath may be undefined in edge cases (e.g., early exit before loop).
|
|
734
|
+
// In practice it is always set inside the for loop above.
|
|
735
|
+
const attemptFallback = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${usedAttempt}.jsonl`;
|
|
736
|
+
const sessionUsage = parseSessionUsage(transcriptPath ?? attemptFallback);
|
|
737
|
+
const effectiveUsage = parsedOutput?.usage ?? sessionUsage;
|
|
738
|
+
if (effectiveUsage) {
|
|
739
|
+
parsedOutput = {
|
|
740
|
+
...(parsedOutput ?? { jsonEvents: 0, textEvents: [] }),
|
|
741
|
+
usage: effectiveUsage,
|
|
742
|
+
};
|
|
743
|
+
task = {
|
|
744
|
+
...task,
|
|
745
|
+
usage: effectiveUsage,
|
|
746
|
+
agentProgress: applyUsageToProgress(task.agentProgress, effectiveUsage),
|
|
747
|
+
};
|
|
748
|
+
tasks = updateTask(tasks, task);
|
|
749
|
+
upsertCrewAgent(manifest, recordFromTask(manifest, task, "child-process"));
|
|
750
|
+
}
|
|
751
|
+
// M2 fix: use attempt-relative path; cap content at MAX_TRANSCRIPT_ARTIFACT_BYTES.
|
|
752
|
+
const MAX_TRANSCRIPT_ARTIFACT_BYTES = 5 * 1024 * 1024; // 5MB cap
|
|
753
|
+
const attemptTranscriptPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${usedAttempt}.jsonl`;
|
|
754
|
+
const transcriptContent = tailReadWithLineSnap(attemptTranscriptPath, MAX_TRANSCRIPT_ARTIFACT_BYTES, "");
|
|
755
|
+
if (transcriptContent) {
|
|
756
|
+
transcriptArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
757
|
+
kind: "log",
|
|
758
|
+
relativePath: `transcripts/${task.id}.attempt-${usedAttempt}.jsonl`,
|
|
759
|
+
content: transcriptContent,
|
|
760
|
+
producer: task.id,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
task = {
|
|
764
|
+
...task,
|
|
765
|
+
resultArtifact,
|
|
766
|
+
...(logArtifact ? { logArtifact } : {}),
|
|
767
|
+
...(transcriptArtifact ? { transcriptArtifact } : {}),
|
|
768
|
+
};
|
|
769
|
+
tasks = updateTask(tasks, task);
|
|
770
|
+
({ task, tasks } = checkpointTask(manifest, tasks, task, "artifact-written"));
|
|
771
|
+
|
|
772
|
+
// Sync the callback-mutated task/tasks back into the context bag so
|
|
773
|
+
// finalizeTaskResult sees the final state.
|
|
774
|
+
ctx.task = task;
|
|
775
|
+
ctx.tasks = tasks;
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
resultArtifact,
|
|
779
|
+
logArtifact,
|
|
780
|
+
transcriptArtifact,
|
|
781
|
+
exitCode,
|
|
782
|
+
error,
|
|
783
|
+
modelAttempts,
|
|
784
|
+
parsedOutput,
|
|
785
|
+
finalStdout,
|
|
786
|
+
transcriptPath,
|
|
787
|
+
terminalEvidence,
|
|
788
|
+
startupEvidence,
|
|
789
|
+
};
|
|
790
|
+
}
|