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,291 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
4
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
5
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
6
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
7
|
+
|
|
8
|
+
export interface Checkpoint {
|
|
9
|
+
runId: string;
|
|
10
|
+
taskId: string;
|
|
11
|
+
step: number;
|
|
12
|
+
context: string;
|
|
13
|
+
progress: string;
|
|
14
|
+
savedAt: number;
|
|
15
|
+
agentId: string;
|
|
16
|
+
agentModel?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CheckpointStore {
|
|
20
|
+
save(checkpoint: Checkpoint): void;
|
|
21
|
+
load(runId: string, taskId: string): Checkpoint | null;
|
|
22
|
+
delete(runId: string, taskId: string): void;
|
|
23
|
+
list(runId: string): Checkpoint[];
|
|
24
|
+
hasCheckpoint(runId: string, taskId: string): boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CheckpointEntry {
|
|
28
|
+
checkpoints: Record<string, Checkpoint>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* File-based checkpoint store.
|
|
33
|
+
* Saves checkpoints as JSON files in .crew/state/runs/<runId>/checkpoints/
|
|
34
|
+
*/
|
|
35
|
+
export class FileCheckpointStore implements CheckpointStore {
|
|
36
|
+
private readonly stateRoot: string;
|
|
37
|
+
|
|
38
|
+
constructor(stateRoot: string) {
|
|
39
|
+
this.stateRoot = stateRoot;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private checkpointDir(): string {
|
|
43
|
+
return path.join(this.stateRoot, "checkpoints");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private checkpointPath(taskId: string): string {
|
|
47
|
+
return path.join(this.checkpointDir(), `${taskId}.json`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private ensureDir(): void {
|
|
51
|
+
const dir = this.checkpointDir();
|
|
52
|
+
if (!fs.existsSync(dir)) {
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
save(checkpoint: Checkpoint): void {
|
|
58
|
+
// Validate taskId to prevent path traversal: the taskId is used to
|
|
59
|
+
// build a file path under this.checkpointDir(). Without validation, a
|
|
60
|
+
// malicious or buggy taskId like "../../../etc/passwd" could escape
|
|
61
|
+
// the checkpoints directory.
|
|
62
|
+
assertSafePathId("taskId", checkpoint.taskId);
|
|
63
|
+
this.ensureDir();
|
|
64
|
+
const p = this.checkpointPath(checkpoint.taskId);
|
|
65
|
+
// Atomic write: write to temp file first, then rename, then fsync parent.
|
|
66
|
+
// This guarantees either the old file or the new file, never a partial
|
|
67
|
+
// write, even on network filesystems or certain journal modes.
|
|
68
|
+
//
|
|
69
|
+
// Round 22 (BUG 1): the temp filename MUST be unique per save call.
|
|
70
|
+
// Previously a fixed '.tmp.checkpoint' was shared across ALL concurrent
|
|
71
|
+
// saves; pi-crew's multi-process architecture (main + detached background
|
|
72
|
+
// workers each checkpointing their own tasks) made this realistic: two
|
|
73
|
+
// processes writing '.tmp.checkpoint' at once → one's rename picks up the
|
|
74
|
+
// other's data (silent corruption) and the second rename hits ENOENT
|
|
75
|
+
// (silent data loss). Including taskId + pid + timestamp guarantees
|
|
76
|
+
// uniqueness across processes and across tasks.
|
|
77
|
+
// atomicWriteFile handles temp + write + fsync(data) + rename + fsync(parent)
|
|
78
|
+
// with a UUID-based unique temp name (cross-process safe). The previous
|
|
79
|
+
// hand-rolled version missed data-fsync (ST-5), risking empty/stale content
|
|
80
|
+
// after power loss.
|
|
81
|
+
atomicWriteFile(p, JSON.stringify(checkpoint, null, 2));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
load(runId: string, taskId: string): Checkpoint | null {
|
|
85
|
+
assertSafePathId("taskId", taskId);
|
|
86
|
+
const p = this.checkpointPath(taskId);
|
|
87
|
+
if (!fs.existsSync(p)) return null;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8")) as Checkpoint;
|
|
91
|
+
// Verify it's for the correct run
|
|
92
|
+
if (data.runId !== runId) return null;
|
|
93
|
+
return data;
|
|
94
|
+
} catch {
|
|
95
|
+
// File existed but JSON was corrupt — log and rename for later inspection
|
|
96
|
+
logInternalError("checkpoint-load", new Error("JSON parse failed"), `file=${p}`);
|
|
97
|
+
try {
|
|
98
|
+
fs.renameSync(p, `${p}.corrupt.${Date.now()}`);
|
|
99
|
+
} catch {
|
|
100
|
+
// Best effort — ignore rename failure
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
delete(runId: string, taskId: string): void {
|
|
107
|
+
assertSafePathId("taskId", taskId);
|
|
108
|
+
const p = this.checkpointPath(taskId);
|
|
109
|
+
if (fs.existsSync(p)) {
|
|
110
|
+
try {
|
|
111
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8")) as Checkpoint;
|
|
112
|
+
if (data.runId === runId) {
|
|
113
|
+
fs.unlinkSync(p);
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// File existed but couldn't read — delete it anyway
|
|
117
|
+
try {
|
|
118
|
+
fs.unlinkSync(p);
|
|
119
|
+
} catch {
|
|
120
|
+
/* ignore */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
list(runId: string): Checkpoint[] {
|
|
127
|
+
const dir = this.checkpointDir();
|
|
128
|
+
if (!fs.existsSync(dir)) return [];
|
|
129
|
+
|
|
130
|
+
return fs
|
|
131
|
+
.readdirSync(dir)
|
|
132
|
+
.filter((f) => f.endsWith(".json"))
|
|
133
|
+
.map((f) => {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(fs.readFileSync(path.join(dir, f), "utf-8")) as Checkpoint;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.filter((c): c is Checkpoint => c !== null && c.runId === runId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
hasCheckpoint(runId: string, taskId: string): boolean {
|
|
144
|
+
return this.load(runId, taskId) !== null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const MAX_STORES = 100;
|
|
149
|
+
const _stores = new Map<string, FileCheckpointStore>();
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get checkpoint store for a run's state root.
|
|
153
|
+
* Uses LRU eviction when the store exceeds MAX_STORES entries.
|
|
154
|
+
*/
|
|
155
|
+
export function getCheckpointStore(stateRoot: string): CheckpointStore {
|
|
156
|
+
if (!_stores.has(stateRoot)) {
|
|
157
|
+
if (_stores.size >= MAX_STORES) {
|
|
158
|
+
// Evict the oldest entry (first in insertion order)
|
|
159
|
+
const oldestKey = _stores.keys().next().value;
|
|
160
|
+
if (oldestKey !== undefined) {
|
|
161
|
+
_stores.delete(oldestKey);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
_stores.set(stateRoot, new FileCheckpointStore(stateRoot));
|
|
165
|
+
}
|
|
166
|
+
return _stores.get(stateRoot)!;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Clear all checkpoint stores (for testing).
|
|
171
|
+
*/
|
|
172
|
+
export function clearCheckpointStores(): void {
|
|
173
|
+
_stores.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Save a checkpoint during agent execution.
|
|
178
|
+
*/
|
|
179
|
+
export function saveCheckpoint(
|
|
180
|
+
runId: string,
|
|
181
|
+
taskId: string,
|
|
182
|
+
step: number,
|
|
183
|
+
context: string,
|
|
184
|
+
progress: string,
|
|
185
|
+
agentId: string,
|
|
186
|
+
agentModel?: string,
|
|
187
|
+
cwd?: string,
|
|
188
|
+
): void {
|
|
189
|
+
// Validate both runId and taskId to prevent path traversal: these are
|
|
190
|
+
// used to build the file path under <crewRoot>/state/runs/<runId>/checkpoints/<taskId>.json.
|
|
191
|
+
assertSafePathId("runId", runId);
|
|
192
|
+
assertSafePathId("taskId", taskId);
|
|
193
|
+
const checkpoint: Checkpoint = {
|
|
194
|
+
runId,
|
|
195
|
+
taskId,
|
|
196
|
+
step,
|
|
197
|
+
context,
|
|
198
|
+
progress,
|
|
199
|
+
savedAt: Date.now(),
|
|
200
|
+
agentId,
|
|
201
|
+
agentModel,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// State root is parent of checkpoints dir. Use projectCrewRoot() so the
|
|
205
|
+
// path lands in .pi/teams/state/runs/ for .pi-based projects (issue #29).
|
|
206
|
+
const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
207
|
+
const store = getCheckpointStore(stateRoot);
|
|
208
|
+
store.save(checkpoint);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Load a checkpoint for resuming.
|
|
213
|
+
*/
|
|
214
|
+
export function loadCheckpoint(runId: string, taskId: string, cwd?: string): Checkpoint | null {
|
|
215
|
+
assertSafePathId("runId", runId);
|
|
216
|
+
assertSafePathId("taskId", taskId);
|
|
217
|
+
const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
218
|
+
const store = getCheckpointStore(stateRoot);
|
|
219
|
+
return store.load(runId, taskId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Delete a checkpoint after successful completion.
|
|
224
|
+
*/
|
|
225
|
+
export function clearCheckpoint(runId: string, taskId: string, cwd?: string): void {
|
|
226
|
+
assertSafePathId("runId", runId);
|
|
227
|
+
assertSafePathId("taskId", taskId);
|
|
228
|
+
const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
229
|
+
const store = getCheckpointStore(stateRoot);
|
|
230
|
+
store.delete(runId, taskId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if a checkpoint exists for a task.
|
|
235
|
+
*/
|
|
236
|
+
export function hasCheckpoint(runId: string, taskId: string, cwd?: string): boolean {
|
|
237
|
+
assertSafePathId("runId", runId);
|
|
238
|
+
assertSafePathId("taskId", taskId);
|
|
239
|
+
const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
240
|
+
const store = getCheckpointStore(stateRoot);
|
|
241
|
+
return store.hasCheckpoint(runId, taskId);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* List all checkpoints for a run.
|
|
246
|
+
*/
|
|
247
|
+
export function listCheckpoints(runId: string, cwd?: string): Checkpoint[] {
|
|
248
|
+
assertSafePathId("runId", runId);
|
|
249
|
+
const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
250
|
+
const store = getCheckpointStore(stateRoot);
|
|
251
|
+
return store.list(runId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Format a checkpoint for display.
|
|
256
|
+
*/
|
|
257
|
+
export function formatCheckpoint(checkpoint: Checkpoint): string {
|
|
258
|
+
return [
|
|
259
|
+
`## Checkpoint: ${checkpoint.taskId}`,
|
|
260
|
+
"",
|
|
261
|
+
`**Agent:** ${checkpoint.agentId}`,
|
|
262
|
+
checkpoint.agentModel ? `**Model:** ${checkpoint.agentModel}` : "",
|
|
263
|
+
"",
|
|
264
|
+
`**Progress:** ${checkpoint.progress}`,
|
|
265
|
+
"",
|
|
266
|
+
`**Step:** ${checkpoint.step}`,
|
|
267
|
+
`**Saved:** ${new Date(checkpoint.savedAt).toISOString()}`,
|
|
268
|
+
"",
|
|
269
|
+
`**Context:** ${checkpoint.context.slice(0, 300)}${checkpoint.context.length > 300 ? "..." : ""}`,
|
|
270
|
+
]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join("\n");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Format all checkpoints for a run.
|
|
277
|
+
*/
|
|
278
|
+
export function formatAllCheckpoints(runId: string, cwd?: string): string {
|
|
279
|
+
const checkpoints = listCheckpoints(runId, cwd);
|
|
280
|
+
if (checkpoints.length === 0) {
|
|
281
|
+
return `No checkpoints found for run ${runId}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return [
|
|
285
|
+
`# Checkpoints: ${runId}`,
|
|
286
|
+
"",
|
|
287
|
+
...checkpoints.map((cp, i) => `${i + 1}. **${cp.taskId}** — ${cp.progress} (${new Date(cp.savedAt).toLocaleString()})`),
|
|
288
|
+
"",
|
|
289
|
+
`Use \`team action='resume' runId=${runId} taskId=<taskId>\` to resume.`,
|
|
290
|
+
].join("\n");
|
|
291
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* child-pi-constants.ts — Shared timing and capture constants for child-pi runtime.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from child-pi.ts (H-7 decomposition, step 3). Zero behavior change.
|
|
5
|
+
*
|
|
6
|
+
* These constants are shared between runChildPi (in child-pi.ts) and the kill
|
|
7
|
+
* helpers (in child-pi-kill.ts). Centralizing them here avoids circular imports
|
|
8
|
+
* and makes the timing budget configurable from one place.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
|
|
12
|
+
|
|
13
|
+
/** Post-exit window during which stdio is guarded against late writes. */
|
|
14
|
+
export const POST_EXIT_STDIO_GUARD_MS = DEFAULT_CHILD_PI.postExitStdioGuardMs;
|
|
15
|
+
|
|
16
|
+
/** Maximum time to wait for a final assistant event after the last stdout byte. */
|
|
17
|
+
export const FINAL_DRAIN_MS = DEFAULT_CHILD_PI.finalDrainMs;
|
|
18
|
+
|
|
19
|
+
/** Time after SIGTERM to escalate to SIGKILL. */
|
|
20
|
+
export const HARD_KILL_MS = DEFAULT_CHILD_PI.hardKillMs;
|
|
21
|
+
|
|
22
|
+
/** Maximum time with no output before the child is considered unresponsive. */
|
|
23
|
+
export const RESPONSE_TIMEOUT_MS = DEFAULT_CHILD_PI.responseTimeoutMs;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum size (bytes) for the ChildPiLineObserver's line accumulation buffer.
|
|
27
|
+
* When exceeded, the buffer is force-flushed to prevent unbounded memory growth
|
|
28
|
+
* from chatty child processes that produce output without newlines.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_LINE_BUFFER_BYTES = 1024 * 1024; // 1 MB
|
|
31
|
+
|
|
32
|
+
/** Maximum characters for assistant text fragments in compacted events. */
|
|
33
|
+
export const MAX_ASSISTANT_TEXT_CHARS = DEFAULT_CHILD_PI.maxAssistantTextChars;
|
|
34
|
+
|
|
35
|
+
/** Maximum characters for tool-result fragments in compacted events. */
|
|
36
|
+
export const MAX_TOOL_RESULT_CHARS = DEFAULT_CHILD_PI.maxToolResultChars;
|
|
37
|
+
|
|
38
|
+
/** Maximum characters for tool-input fragments in compacted events. */
|
|
39
|
+
export const MAX_TOOL_INPUT_CHARS = DEFAULT_CHILD_PI.maxToolInputChars;
|
|
40
|
+
|
|
41
|
+
/** Maximum characters for general compactable content (used by TruncationStage). */
|
|
42
|
+
export const MAX_COMPACT_CONTENT_CHARS = DEFAULT_CHILD_PI.maxCompactContentChars;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* child-pi-kill.ts — Kill/escape path for child Pi worker processes.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from child-pi.ts (H-7 decomposition, step 2). Zero behavior change.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* - appendBoundedTail(): bounded concatenation with truncation marker.
|
|
8
|
+
* - killProcessPid(): send SIGTERM → SIGKILL escalation, with Windows taskkill fallback.
|
|
9
|
+
* - killProcessTree(): log+kill a child by pid, optionally attach exit listener to clear timer.
|
|
10
|
+
* - terminateActiveChildPiProcesses(): kill all known active children.
|
|
11
|
+
* - registerActiveChild()/unregisterActiveChild(): book-keeping for the active-children Map.
|
|
12
|
+
* - Periodic zombie reaper (setInterval) that drops dead entries.
|
|
13
|
+
*
|
|
14
|
+
* The activeChildrenMap and hardKillTimers Map are kept here (not re-exported)
|
|
15
|
+
* because they are tightly coupled to the kill lifecycle.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ChildProcess } from "node:child_process";
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
|
+
import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
|
|
21
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
22
|
+
import { HARD_KILL_MS } from "./child-pi-constants.ts";
|
|
23
|
+
import { TailCaptureStage } from "./compact-stages/tail-capture-stage.ts";
|
|
24
|
+
|
|
25
|
+
const MAX_CAPTURE_BYTES = DEFAULT_CHILD_PI.maxCaptureBytes;
|
|
26
|
+
|
|
27
|
+
// Active children bookkeeping. Mutated by registerActiveChild/unregisterActiveChild.
|
|
28
|
+
const activeChildProcesses = new Map<number, ChildProcess>();
|
|
29
|
+
const childHardKillTimers = new Map<number, NodeJS.Timeout>();
|
|
30
|
+
|
|
31
|
+
// Periodic cleanup of dead child process entries to prevent memory leaks.
|
|
32
|
+
// If a child process never emits exit/close (zombie), the entry would leak.
|
|
33
|
+
setInterval(() => {
|
|
34
|
+
for (const [pid, child] of activeChildProcesses) {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid, 0); // Throws ESRCH if dead
|
|
37
|
+
} catch {
|
|
38
|
+
activeChildProcesses.delete(pid);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, 60_000).unref();
|
|
42
|
+
|
|
43
|
+
/** Register a newly-spawned child so it can be tracked + killed on shutdown. */
|
|
44
|
+
export function registerActiveChild(pid: number, child: ChildProcess): void {
|
|
45
|
+
activeChildProcesses.set(pid, child);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Remove a child from the active set once it has exited (or before kill). */
|
|
49
|
+
export function unregisterActiveChild(pid: number): void {
|
|
50
|
+
activeChildProcesses.delete(pid);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Clear the SIGKILL escalation timer for a pid (called on early exit). */
|
|
54
|
+
export function clearHardKillTimer(pid: number | undefined): void {
|
|
55
|
+
if (pid === undefined) return;
|
|
56
|
+
const timer = childHardKillTimers.get(pid);
|
|
57
|
+
if (timer) {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
childHardKillTimers.delete(pid);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Append `chunk` to `current`, keeping the result within `maxBytes`. When the
|
|
65
|
+
* cap is exceeded, returns the tail (most recent bytes) prefixed with a marker
|
|
66
|
+
* showing how much was dropped. Delegates to TailCaptureStage so the logic
|
|
67
|
+
* stays consistent with the rest of the compaction pipeline.
|
|
68
|
+
*/
|
|
69
|
+
export function appendBoundedTail(current: string, chunk: string, maxBytes = MAX_CAPTURE_BYTES): string {
|
|
70
|
+
return new TailCaptureStage({
|
|
71
|
+
maxBytes,
|
|
72
|
+
marker: `[pi-crew captured output truncated to last ${Math.round(maxBytes / 1024)} KiB]`,
|
|
73
|
+
}).apply(current + chunk);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function spawnTaskkillSafe(pid: number): void {
|
|
77
|
+
// B6: spawn taskkill and attach an 'error' listener. spawn() emits ENOENT/EACCES
|
|
78
|
+
// via the 'error' event — unhandled these would become uncaughtException and
|
|
79
|
+
// can crash the parent as an uncaught exception. taskkill is a standard Windows
|
|
80
|
+
// utility and rarely fails to spawn, but be defensive.
|
|
81
|
+
try {
|
|
82
|
+
const taskkillChild = spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
83
|
+
stdio: "ignore",
|
|
84
|
+
detached: false,
|
|
85
|
+
});
|
|
86
|
+
taskkillChild.on("error", (err) => {
|
|
87
|
+
logInternalError("child-pi.taskkill-spawn-error", err instanceof Error ? err : new Error(String(err)), `pid=${pid}`);
|
|
88
|
+
});
|
|
89
|
+
taskkillChild.unref();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logInternalError("child-pi.taskkill-sync-error", error instanceof Error ? error : new Error(String(error)), `pid=${pid}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function killProcessPid(pid: number): void {
|
|
96
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
97
|
+
try {
|
|
98
|
+
if (process.platform === "win32") {
|
|
99
|
+
// 3.8: Windows path uses taskkill /T /F (force kill the entire tree).
|
|
100
|
+
// taskkill itself can silently fail (PID gone, permission denied, etc.)
|
|
101
|
+
// so verify after 2s and log a warning if the process is still alive.
|
|
102
|
+
spawnTaskkillSafe(pid);
|
|
103
|
+
const verifyTimer = setTimeout(() => {
|
|
104
|
+
try {
|
|
105
|
+
process.kill(pid, 0); // throws ESRCH when dead
|
|
106
|
+
// Still alive — log and retry once.
|
|
107
|
+
logInternalError(
|
|
108
|
+
"child-pi.taskkill-stuck",
|
|
109
|
+
new Error(`process ${pid} still alive 2s after taskkill /T /F; retrying`),
|
|
110
|
+
`pid=${pid}`,
|
|
111
|
+
"error",
|
|
112
|
+
);
|
|
113
|
+
try {
|
|
114
|
+
spawnTaskkillSafe(pid);
|
|
115
|
+
} catch {
|
|
116
|
+
/* best-effort */
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// ESRCH or EPERM — process is gone. OK.
|
|
120
|
+
}
|
|
121
|
+
}, 2000);
|
|
122
|
+
verifyTimer.unref();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
process.kill(-pid, "SIGTERM");
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logInternalError("child-pi.sigterm", error, `pid=${pid}`);
|
|
129
|
+
try {
|
|
130
|
+
process.kill(pid, "SIGTERM");
|
|
131
|
+
} catch (fallbackError) {
|
|
132
|
+
logInternalError("child-pi.sigterm-absolute", fallbackError, `pid=${pid}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
clearHardKillTimer(pid);
|
|
136
|
+
const hardKillTimer = setTimeout(() => {
|
|
137
|
+
try {
|
|
138
|
+
process.kill(-pid, "SIGKILL");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logInternalError("child-pi.sigkill", error, `pid=${pid}`);
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, "SIGKILL");
|
|
143
|
+
} catch (fallbackError) {
|
|
144
|
+
logInternalError("child-pi.sigkill-absolute", fallbackError, `pid=${pid}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
childHardKillTimers.delete(pid);
|
|
148
|
+
}, HARD_KILL_MS);
|
|
149
|
+
hardKillTimer.unref();
|
|
150
|
+
childHardKillTimers.set(pid, hardKillTimer);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logInternalError("child-pi.kill-process-pid", error, `pid=${pid}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function killProcessTree(pid: number | undefined, child?: ChildProcess): void {
|
|
157
|
+
// Phase-0 diagnostic (HB-003a): capture who invoked killProcessTree so the
|
|
158
|
+
// exit-null race has a provenance trail. .stack is best-effort (may be undefined
|
|
159
|
+
// under deep async), so we take a snapshot lazily.
|
|
160
|
+
try {
|
|
161
|
+
const callerStack = new Error("killProcessTree caller").stack ?? "(no stack)";
|
|
162
|
+
logInternalError(
|
|
163
|
+
"child-pi.kill-process-tree-invoked",
|
|
164
|
+
new Error(`pid=${pid} called from:\n${callerStack.split("\n").slice(0, 8).join("\n")}`),
|
|
165
|
+
`pid=${pid}`,
|
|
166
|
+
);
|
|
167
|
+
} catch {
|
|
168
|
+
/* diagnostic best-effort */
|
|
169
|
+
}
|
|
170
|
+
if (!pid || !Number.isInteger(pid) || pid <= 0) return;
|
|
171
|
+
if (child && child.exitCode !== null) return;
|
|
172
|
+
killProcessPid(pid);
|
|
173
|
+
child?.once("exit", () => clearHardKillTimer(pid));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function terminateActiveChildPiProcesses(): number {
|
|
177
|
+
const entries = [...activeChildProcesses.entries()];
|
|
178
|
+
for (const [pid, child] of entries) killProcessTree(pid, child);
|
|
179
|
+
return entries.length;
|
|
180
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// 2.6 — Child-pi warm pool skeleton (ADR 0008 Proposed).
|
|
2
|
+
//
|
|
3
|
+
// This module exposes the pool interface that child-pi.ts will consume
|
|
4
|
+
// once Pi gains the wait-for-prompt handshake protocol. Until the Pi-side
|
|
5
|
+
// support lands, the pool is permanently disabled (size=0): callers always
|
|
6
|
+
// receive null from `acquirePooledChild` and fall through to the regular
|
|
7
|
+
// spawn path. This keeps the integration point ready without altering
|
|
8
|
+
// production behaviour.
|
|
9
|
+
//
|
|
10
|
+
// Enabling a real pool requires three pieces in order:
|
|
11
|
+
//
|
|
12
|
+
// 1. Pi runtime: respond to a `PI_CREW_POOL_HEALTH=1` ping on stdin and
|
|
13
|
+
// block in a "wait-for-prompt" state until the parent writes a real
|
|
14
|
+
// prompt. Pi does not currently implement this.
|
|
15
|
+
// 2. This module: replace the `acquirePooledChild` returning null with
|
|
16
|
+
// an actual pool that spawn-and-park N processes.
|
|
17
|
+
// 3. child-pi.ts: prefer pooled children on `runChildPi` entry; fall
|
|
18
|
+
// back to fresh spawn on miss.
|
|
19
|
+
//
|
|
20
|
+
// Disabled by default; opt-in via `runtime.warmPool.size > 0` config or
|
|
21
|
+
// the `PI_CREW_WARM_POOL_SIZE` env var.
|
|
22
|
+
import type { ChildProcess } from "node:child_process";
|
|
23
|
+
|
|
24
|
+
export interface WarmPoolOptions {
|
|
25
|
+
/** Number of warm processes to maintain. 0 disables the pool. */
|
|
26
|
+
size: number;
|
|
27
|
+
/** Drop pooled processes that have been idle longer than this. */
|
|
28
|
+
maxIdleMs: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_WARM_POOL_OPTIONS: WarmPoolOptions = {
|
|
32
|
+
size: 0,
|
|
33
|
+
maxIdleMs: 5 * 60_000,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Resolve the effective pool size from env / config / defaults. */
|
|
37
|
+
export function resolveWarmPoolSize(env: NodeJS.ProcessEnv = process.env, configured?: number): number {
|
|
38
|
+
const fromEnv = Number.parseInt(env.PI_CREW_WARM_POOL_SIZE ?? "", 10);
|
|
39
|
+
if (Number.isFinite(fromEnv) && fromEnv >= 0) return fromEnv;
|
|
40
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured >= 0) return configured;
|
|
41
|
+
return DEFAULT_WARM_POOL_OPTIONS.size;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Try to acquire a parked child from the pool. Returns null when the pool
|
|
46
|
+
* is disabled or empty; caller should spawn a fresh child instead.
|
|
47
|
+
*
|
|
48
|
+
* Skeleton — currently always returns null. See module docstring.
|
|
49
|
+
*/
|
|
50
|
+
export function acquirePooledChild(_options: Partial<WarmPoolOptions> = {}): ChildProcess | null {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Mark a pooled child as done. Pool processes are single-use: this terminates
|
|
56
|
+
* the child rather than returning it to the pool, because state contamination
|
|
57
|
+
* across runs would be unsafe (file handles, env mutations, mounted FDs).
|
|
58
|
+
*
|
|
59
|
+
* Skeleton — currently a no-op since acquirePooledChild never returns a child.
|
|
60
|
+
*/
|
|
61
|
+
export function releasePooledChild(_child: ChildProcess | null | undefined): void {
|
|
62
|
+
// no-op while the pool is disabled
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Drain and terminate every parked child. Call on cleanupRuntime. */
|
|
66
|
+
export function disposeWarmPool(): void {
|
|
67
|
+
// no-op while the pool is disabled
|
|
68
|
+
}
|