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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Important-Line Classifier (P0-B) — scan middle slice of a truncated value
|
|
3
|
+
* for diagnostic lines worth preserving between head and tail.
|
|
4
|
+
*
|
|
5
|
+
* Ported from Hypa's `ImportantLineClassifier.cs` (5 regexes) and the
|
|
6
|
+
* middle-scanning portion of `Stages/TruncationStage.cs:24-46`, adapted to TS
|
|
7
|
+
* (no `[GeneratedRegex]` AOT) and to pi-crew's head(75%)/tail(25%) split.
|
|
8
|
+
*
|
|
9
|
+
* Design rationale:
|
|
10
|
+
* - Patterns are intentionally OVER-INCLUSIVE. False positives preserve
|
|
11
|
+
* harmless lines; false negatives drop critical diagnostics, which is
|
|
12
|
+
* unacceptable (the whole point of this module). Hypa uses the same
|
|
13
|
+
* over-inclusive design.
|
|
14
|
+
* - Patterns are evaluated against a WHOLE line, not against the raw
|
|
15
|
+
* truncated slice, so a match at a line boundary is reliable.
|
|
16
|
+
* - The `splitWithImportantLines` helper performs the head/tail split AND
|
|
17
|
+
* greedily picks whole important lines from the middle that fit inside
|
|
18
|
+
* `slackFactor * maxChars` (default 15% slack). Callers compose their own
|
|
19
|
+
* marker using the returned parts — keeping `compactString` (marker
|
|
20
|
+
* "compacted ... chars, head+tail preserved") and `readIfSmall` (marker
|
|
21
|
+
* "truncated ... bytes, head+tail preserved") backward-compatible when no
|
|
22
|
+
* important lines are present.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Diagnostic patterns. Anchored where safe to avoid matching noise. */
|
|
26
|
+
export const IMPORTANT_LINE_PATTERNS: readonly RegExp[] = [
|
|
27
|
+
// error keywords — NOTE: "warning" is intentionally excluded here; it has
|
|
28
|
+
// its own case-sensitive pattern below so that the common prose word
|
|
29
|
+
// "warning" does not over-match. (Hypa does the same split.)
|
|
30
|
+
/\b(error|failed|exception|fatal|panic)\b/i,
|
|
31
|
+
// file:line diagnostic — `child-pi.ts:383:`, `App.tsx:42:`
|
|
32
|
+
/\w+\.\w+:\d+:/,
|
|
33
|
+
// HTTP 4xx / 5xx — bounded so it does not match phone numbers etc.
|
|
34
|
+
/\b[45]\d{2}\b/,
|
|
35
|
+
// k8s / linter "Warning" event (case-sensitive so prose is not matched)
|
|
36
|
+
/\bWarning\b/,
|
|
37
|
+
// compiler / linter diagnostic id — `TS2304`, `CS0246`, `ES1234`
|
|
38
|
+
/\b[A-Z]{2,4}\d{3,5}\b/,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** True iff `line` matches at least one important-line pattern. */
|
|
42
|
+
export function isImportantLine(line: string): boolean {
|
|
43
|
+
if (!line) return false;
|
|
44
|
+
for (const pattern of IMPORTANT_LINE_PATTERNS) {
|
|
45
|
+
if (pattern.test(line)) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract up to `maxLines` important lines from `text`. Lines are split on
|
|
52
|
+
* `\n` (also handles `\r\n`). Order preserved; duplicates kept (callers may
|
|
53
|
+
* want to see the same diagnostic twice if it appears twice — that often
|
|
54
|
+
* signals a recurring failure).
|
|
55
|
+
*/
|
|
56
|
+
export function extractImportantLines(text: string, maxLines = 30): string[] {
|
|
57
|
+
if (!text || maxLines <= 0) return [];
|
|
58
|
+
const out: string[] = [];
|
|
59
|
+
for (const line of text.split(/\r?\n/)) {
|
|
60
|
+
if (out.length >= maxLines) break;
|
|
61
|
+
if (isImportantLine(line)) out.push(line);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TruncationSplit {
|
|
67
|
+
/** The first 75% of the value (by char count), verbatim. */
|
|
68
|
+
head: string;
|
|
69
|
+
/** The last 25% of the value (by char count), verbatim. */
|
|
70
|
+
tail: string;
|
|
71
|
+
/**
|
|
72
|
+
* Important lines from the middle slice, greedily picked (whole lines) so
|
|
73
|
+
* the joined length fits inside `slackFactor * maxChars`. Empty when
|
|
74
|
+
* `preserveImportant` is false OR no important lines are present OR none
|
|
75
|
+
* fit the slack budget.
|
|
76
|
+
*/
|
|
77
|
+
importantLines: string[];
|
|
78
|
+
/** `value.length - maxChars` — chars dropped if no important lines preserved. */
|
|
79
|
+
baseDropped: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SplitOptions {
|
|
83
|
+
/** When false, important-line scanning is skipped (assistant-text mode). */
|
|
84
|
+
preserveImportant?: boolean;
|
|
85
|
+
/** Hard cap on candidate lines before slack-budget selection. Default 30. */
|
|
86
|
+
maxImportantLines?: number;
|
|
87
|
+
/** Fraction of `maxChars` available for important-line content. Default 0.15. */
|
|
88
|
+
slackFactor?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Split `value` into head + important-middle + tail, returning the parts.
|
|
93
|
+
* The caller is responsible for composing the final result (marker + glue)
|
|
94
|
+
* because the marker wording differs between `compactString` and
|
|
95
|
+
* `readIfSmall`.
|
|
96
|
+
*
|
|
97
|
+
* When no important lines are picked, the returned `importantLines` is `[]`
|
|
98
|
+
* and the marker wording stays bit-identical to the pre-P0-B format.
|
|
99
|
+
*/
|
|
100
|
+
export function splitWithImportantLines(value: string, maxChars: number, opts: SplitOptions = {}): TruncationSplit {
|
|
101
|
+
if (value.length <= maxChars) {
|
|
102
|
+
return { head: value, tail: "", importantLines: [], baseDropped: 0 };
|
|
103
|
+
}
|
|
104
|
+
const headLen = Math.floor(maxChars * 0.75);
|
|
105
|
+
const tailLen = maxChars - headLen;
|
|
106
|
+
const head = value.slice(0, headLen);
|
|
107
|
+
const tail = value.slice(value.length - tailLen);
|
|
108
|
+
|
|
109
|
+
if (opts.preserveImportant === false) {
|
|
110
|
+
return {
|
|
111
|
+
head,
|
|
112
|
+
tail,
|
|
113
|
+
importantLines: [],
|
|
114
|
+
baseDropped: value.length - maxChars,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const slackFactor = opts.slackFactor ?? 0.15;
|
|
119
|
+
const slackChars = Math.max(0, Math.floor(maxChars * slackFactor));
|
|
120
|
+
const maxCandidates = opts.maxImportantLines ?? 30;
|
|
121
|
+
const middle = value.slice(headLen, value.length - tailLen);
|
|
122
|
+
const candidates = extractImportantLines(middle, maxCandidates);
|
|
123
|
+
|
|
124
|
+
// Greedily pick whole lines that fit in the slack budget.
|
|
125
|
+
const chosen: string[] = [];
|
|
126
|
+
let used = 0;
|
|
127
|
+
for (const line of candidates) {
|
|
128
|
+
const addLen = (chosen.length > 0 ? 1 : 0) + line.length; // '\n' separator
|
|
129
|
+
if (used + addLen > slackChars) break;
|
|
130
|
+
chosen.push(line);
|
|
131
|
+
used += addLen;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
head,
|
|
136
|
+
tail,
|
|
137
|
+
importantLines: chosen,
|
|
138
|
+
baseDropped: value.length - maxChars,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intercom bridge — workers can escalate questions to the orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Pattern origin: pi-subagents/src/intercom-bridge.ts — contact_supervisor tool
|
|
5
|
+
* for child agents to escalate decisions, report progress, or ask questions.
|
|
6
|
+
*
|
|
7
|
+
* This module provides the message queue and correlation logic.
|
|
8
|
+
* The actual tool registration happens in task-runner.ts.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ── Types ────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type IntercomUrgency = "low" | "medium" | "high" | "critical";
|
|
14
|
+
export type IntercomType = "question" | "escalation" | "progress" | "block";
|
|
15
|
+
|
|
16
|
+
export interface IntercomMessage {
|
|
17
|
+
type: IntercomType;
|
|
18
|
+
taskStepId: string;
|
|
19
|
+
content: string;
|
|
20
|
+
urgency: IntercomUrgency;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
timeout?: number; // ms to wait for response
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IntercomResponse {
|
|
26
|
+
answer: string;
|
|
27
|
+
source: "orchestrator" | "human" | "timeout";
|
|
28
|
+
timestamp: number;
|
|
29
|
+
messageId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Message Queue ────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
interface PendingMessage {
|
|
35
|
+
message: IntercomMessage;
|
|
36
|
+
id: string;
|
|
37
|
+
resolve: (response: IntercomResponse) => void;
|
|
38
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MAX_QUEUE_SIZE = 100;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* In-process intercom queue for worker→orchestrator communication.
|
|
45
|
+
*
|
|
46
|
+
* Each message gets a unique ID. Callers await a response via a Promise.
|
|
47
|
+
* If no response arrives within the timeout, resolves with source="timeout".
|
|
48
|
+
*/
|
|
49
|
+
export class IntercomQueue {
|
|
50
|
+
private pending = new Map<string, PendingMessage>();
|
|
51
|
+
private queue: IntercomMessage[] = [];
|
|
52
|
+
/** Monotonic sequence counter — guarantees unique IDs even when many
|
|
53
|
+
* messages enqueue within the same millisecond (avoids Date.now()
|
|
54
|
+
* collision + weak Math.random birthday-paradox flakes under load). */
|
|
55
|
+
private seq = 0;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Enqueue a message and return a promise that resolves when the
|
|
59
|
+
* orchestrator responds (or times out).
|
|
60
|
+
*/
|
|
61
|
+
enqueue(message: IntercomMessage): Promise<IntercomResponse> {
|
|
62
|
+
if (this.pending.size >= MAX_QUEUE_SIZE) {
|
|
63
|
+
// Evict oldest
|
|
64
|
+
const firstKey = this.pending.keys().next().value;
|
|
65
|
+
if (firstKey) this.evict(firstKey, "queue_full");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const id = `icm-${Date.now().toString(36)}-${(this.seq++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
69
|
+
|
|
70
|
+
return new Promise<IntercomResponse>((resolve) => {
|
|
71
|
+
const entry: PendingMessage = { message, id, resolve };
|
|
72
|
+
|
|
73
|
+
// Set timeout if specified
|
|
74
|
+
if (message.timeout && message.timeout > 0) {
|
|
75
|
+
entry.timer = setTimeout(() => {
|
|
76
|
+
resolve({
|
|
77
|
+
answer: "No response received within timeout",
|
|
78
|
+
source: "timeout",
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
messageId: id,
|
|
81
|
+
});
|
|
82
|
+
this.pending.delete(id);
|
|
83
|
+
}, message.timeout);
|
|
84
|
+
// Defense in depth: never let a pending-message timer block
|
|
85
|
+
// process exit. The timer is cleared via respond()/evict() in
|
|
86
|
+
// the normal case; .unref() ensures shutdown isn't blocked if
|
|
87
|
+
// the queue is abandoned.
|
|
88
|
+
if (entry.timer && typeof entry.timer.unref === "function") {
|
|
89
|
+
entry.timer.unref();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.pending.set(id, entry);
|
|
94
|
+
this.queue.push({ ...message });
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Respond to a pending message by ID.
|
|
100
|
+
*/
|
|
101
|
+
respond(messageId: string, answer: string, source: "orchestrator" | "human" = "orchestrator"): boolean {
|
|
102
|
+
const entry = this.pending.get(messageId);
|
|
103
|
+
if (!entry) return false;
|
|
104
|
+
|
|
105
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
106
|
+
|
|
107
|
+
entry.resolve({
|
|
108
|
+
answer,
|
|
109
|
+
source,
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
messageId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.pending.delete(messageId);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get all pending messages (for orchestrator to process).
|
|
120
|
+
*/
|
|
121
|
+
getPending(): Array<IntercomMessage & { id: string }> {
|
|
122
|
+
return [...this.pending.entries()].map(([id, entry]) => ({
|
|
123
|
+
...entry.message,
|
|
124
|
+
id,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Number of pending messages awaiting response.
|
|
130
|
+
*/
|
|
131
|
+
get pendingCount(): number {
|
|
132
|
+
return this.pending.size;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clean up all pending messages (e.g., on run completion).
|
|
137
|
+
*/
|
|
138
|
+
clear(): void {
|
|
139
|
+
for (const [id, entry] of this.pending) {
|
|
140
|
+
this.evict(id, "run_complete");
|
|
141
|
+
}
|
|
142
|
+
this.queue = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private evict(id: string, reason: string): void {
|
|
146
|
+
const entry = this.pending.get(id);
|
|
147
|
+
if (!entry) return;
|
|
148
|
+
|
|
149
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
150
|
+
|
|
151
|
+
entry.resolve({
|
|
152
|
+
answer: `Message evicted: ${reason}`,
|
|
153
|
+
source: "timeout",
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
messageId: id,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.pending.delete(id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Singleton per run ────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
const queues = new Map<string, IntercomQueue>();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get or create an intercom queue for a run.
|
|
168
|
+
*/
|
|
169
|
+
export function getIntercomQueue(runId: string): IntercomQueue {
|
|
170
|
+
let queue = queues.get(runId);
|
|
171
|
+
if (!queue) {
|
|
172
|
+
queue = new IntercomQueue();
|
|
173
|
+
queues.set(runId, queue);
|
|
174
|
+
}
|
|
175
|
+
return queue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Clean up intercom queue for a completed run.
|
|
180
|
+
*/
|
|
181
|
+
export function cleanupIntercomQueue(runId: string): void {
|
|
182
|
+
const queue = queues.get(runId);
|
|
183
|
+
if (queue) {
|
|
184
|
+
queue.clear();
|
|
185
|
+
queues.delete(runId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transparent iteration hooks — runs user-supplied before/after task scripts
|
|
3
|
+
* with structured JSON payload on stdin.
|
|
4
|
+
*
|
|
5
|
+
* Distilled from pi-autoresearch's iteration hook pattern.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
11
|
+
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
12
|
+
import { resolveShellForScript } from "../utils/resolve-shell.ts";
|
|
13
|
+
import { HeadSnapStage } from "./compact-stages/index.ts";
|
|
14
|
+
import { DENIED_METRIC_NAMES } from "./metric-parser.ts";
|
|
15
|
+
|
|
16
|
+
/** Hook execution stage. */
|
|
17
|
+
export type HookStage = "before" | "after";
|
|
18
|
+
|
|
19
|
+
/** Payload sent to the hook script via stdin as JSON. */
|
|
20
|
+
export interface HookPayload {
|
|
21
|
+
event: HookStage;
|
|
22
|
+
cwd: string;
|
|
23
|
+
taskId: string;
|
|
24
|
+
runId: string;
|
|
25
|
+
taskRole: string;
|
|
26
|
+
lastResult?: {
|
|
27
|
+
status: string;
|
|
28
|
+
description: string;
|
|
29
|
+
diagnostics?: Record<string, unknown>;
|
|
30
|
+
} | null;
|
|
31
|
+
session: {
|
|
32
|
+
teamName: string;
|
|
33
|
+
workflowName: string;
|
|
34
|
+
goal: string;
|
|
35
|
+
completedTasks: number;
|
|
36
|
+
totalTasks: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Result of executing an iteration hook. */
|
|
41
|
+
export interface HookResult {
|
|
42
|
+
/** Whether the hook script was actually executed. */
|
|
43
|
+
fired: boolean;
|
|
44
|
+
/** Captured stdout (truncated to 8KB). */
|
|
45
|
+
stdout: string;
|
|
46
|
+
/** Captured stderr. */
|
|
47
|
+
stderr: string;
|
|
48
|
+
/** Exit code of the hook process. */
|
|
49
|
+
exitCode: number | null;
|
|
50
|
+
/** Whether the hook timed out. */
|
|
51
|
+
timedOut: boolean;
|
|
52
|
+
/** Wall-clock duration in milliseconds. */
|
|
53
|
+
durationMs: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Maximum stdout capture size in bytes (8 KB). */
|
|
57
|
+
const MAX_STDOUT_BYTES = 8192;
|
|
58
|
+
|
|
59
|
+
/** Hook execution timeout in milliseconds (30 seconds). */
|
|
60
|
+
const HOOK_TIMEOUT_MS = 30_000;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validates that a hook script path is within an allowed directory.
|
|
64
|
+
* Allowed paths:
|
|
65
|
+
* - Relative paths starting with ".hooks/" (case-sensitive)
|
|
66
|
+
* - Absolute paths under $HOME/.pi/hooks/
|
|
67
|
+
* All other paths are rejected to prevent arbitrary script execution.
|
|
68
|
+
* @param hookPath - The hook script path to validate
|
|
69
|
+
* @returns true if the path is allowed, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
export function isAllowedHookPath(hookPath: string): boolean {
|
|
72
|
+
if (!hookPath || hookPath.trim().length === 0) return false;
|
|
73
|
+
if (!path.isAbsolute(hookPath)) {
|
|
74
|
+
// Use path.posix.normalize to ensure forward-slash normalization on all platforms.
|
|
75
|
+
// On Windows, path.normalize converts .hooks/hook.sh to .hooks\hook.sh (backslash),
|
|
76
|
+
// breaking the startsWith(".hooks/") check. path.posix.normalize always uses /.
|
|
77
|
+
const normalized = path.posix.normalize(hookPath);
|
|
78
|
+
return normalized === ".hooks" || normalized.startsWith(".hooks/");
|
|
79
|
+
}
|
|
80
|
+
// Normalize to forward slashes for consistent cross-platform comparison.
|
|
81
|
+
// e.g., "C:\\Users\\runner\\.pi\\hooks\\hook.sh" matches
|
|
82
|
+
// "C:\\Users\\runner\\.pi\\hooks/hook.sh" from path.join.
|
|
83
|
+
const normalizedHookPath = hookPath.replace(/\\/g, "/");
|
|
84
|
+
const homeHooksNormalized = (process.env.HOME ?? "").replace(/\\/g, "/") + "/.pi/hooks";
|
|
85
|
+
return normalizedHookPath === homeHooksNormalized || normalizedHookPath.startsWith(homeHooksNormalized + "/");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a not-fired result for when the hook script is absent or not executable.
|
|
90
|
+
*/
|
|
91
|
+
function notFiredResult(): HookResult {
|
|
92
|
+
return {
|
|
93
|
+
fired: false,
|
|
94
|
+
stdout: "",
|
|
95
|
+
stderr: "",
|
|
96
|
+
exitCode: null,
|
|
97
|
+
timedOut: false,
|
|
98
|
+
durationMs: 0,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a script path exists and is executable.
|
|
104
|
+
*/
|
|
105
|
+
function isScriptRunnable(scriptPath: string): boolean {
|
|
106
|
+
try {
|
|
107
|
+
if (!fs.existsSync(scriptPath)) return false;
|
|
108
|
+
|
|
109
|
+
// On Windows, X_OK is unreliable — just check F_OK (file exists).
|
|
110
|
+
// On Unix, check both F_OK and X_OK.
|
|
111
|
+
if (process.platform === "win32") {
|
|
112
|
+
fs.accessSync(scriptPath, fs.constants.F_OK);
|
|
113
|
+
} else {
|
|
114
|
+
fs.accessSync(scriptPath, fs.constants.F_OK | fs.constants.X_OK);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Run an iteration hook script with JSON payload on stdin.
|
|
124
|
+
*
|
|
125
|
+
* Spawns `bash <script>` with the hook payload as JSON on stdin.
|
|
126
|
+
* Captures stdout (capped at 8KB) and stderr. Enforces a 30-second timeout.
|
|
127
|
+
*
|
|
128
|
+
* **Security note:** Hook paths are restricted to `.hooks/` relative paths
|
|
129
|
+
* or `$HOME/.pi/hooks/` absolute paths. All other paths are rejected before
|
|
130
|
+
* execution.
|
|
131
|
+
*
|
|
132
|
+
* @param payload - Structured hook payload
|
|
133
|
+
* @param hookScriptPath - Absolute or relative path to the hook script
|
|
134
|
+
* @returns HookResult indicating whether the hook fired and its output
|
|
135
|
+
*/
|
|
136
|
+
export async function runIterationHook(
|
|
137
|
+
payload: HookPayload,
|
|
138
|
+
hookScriptPath: string,
|
|
139
|
+
options?: { timeoutMs?: number },
|
|
140
|
+
): Promise<HookResult> {
|
|
141
|
+
if (!isAllowedHookPath(hookScriptPath)) {
|
|
142
|
+
return {
|
|
143
|
+
fired: false,
|
|
144
|
+
stdout: "",
|
|
145
|
+
stderr: "hook path not allowed: " + hookScriptPath,
|
|
146
|
+
exitCode: null,
|
|
147
|
+
timedOut: false,
|
|
148
|
+
durationMs: 0,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Resolve relative paths relative to cwd
|
|
152
|
+
const resolvedScript = path.isAbsolute(hookScriptPath) ? hookScriptPath : path.join(payload.cwd, hookScriptPath);
|
|
153
|
+
if (!isScriptRunnable(resolvedScript)) {
|
|
154
|
+
return notFiredResult();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const startTime = Date.now();
|
|
158
|
+
const stdinJson = JSON.stringify(payload);
|
|
159
|
+
const stdoutChunks: Buffer[] = [];
|
|
160
|
+
const stderrChunks: Buffer[] = [];
|
|
161
|
+
|
|
162
|
+
return new Promise<HookResult>((resolve) => {
|
|
163
|
+
const { command, args } = resolveShellForScript(resolvedScript);
|
|
164
|
+
const child = spawn(command, args, {
|
|
165
|
+
cwd: payload.cwd,
|
|
166
|
+
env: {
|
|
167
|
+
...sanitizeEnvSecrets(process.env, {
|
|
168
|
+
allowList: ["PATH", "HOME", "USER", ...WINDOWS_ESSENTIAL_ENV_VARS, "TMPDIR", "LANG", "LC_ALL", "PI_CREW_*"],
|
|
169
|
+
}),
|
|
170
|
+
PI_CREW_HOOK: "1",
|
|
171
|
+
},
|
|
172
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
let killed = false;
|
|
176
|
+
const timeoutMs = options?.timeoutMs ?? HOOK_TIMEOUT_MS;
|
|
177
|
+
const timeout = setTimeout(() => {
|
|
178
|
+
killed = true;
|
|
179
|
+
child.kill("SIGKILL");
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
|
|
182
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
183
|
+
stdoutChunks.push(chunk);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
187
|
+
stderrChunks.push(chunk);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
child.on("close", (code: number | null) => {
|
|
191
|
+
clearTimeout(timeout);
|
|
192
|
+
const durationMs = Date.now() - startTime;
|
|
193
|
+
|
|
194
|
+
const rawStdout = Buffer.concat(stdoutChunks);
|
|
195
|
+
// Sprint 5: refactored onto HeadSnapStage. Convert to UTF-8 string once,
|
|
196
|
+
// then apply the byte-cap stage with newline-snap so partial lines
|
|
197
|
+
// never appear in the captured preview. HeadSnapStage is byte-cap-safe
|
|
198
|
+
// (walks back partial UTF-8 sequences at the cut boundary).
|
|
199
|
+
const stdoutText = new HeadSnapStage({
|
|
200
|
+
maxBytes: MAX_STDOUT_BYTES,
|
|
201
|
+
}).apply(rawStdout.toString("utf-8"));
|
|
202
|
+
|
|
203
|
+
const rawStderr = Buffer.concat(stderrChunks);
|
|
204
|
+
|
|
205
|
+
resolve({
|
|
206
|
+
fired: true,
|
|
207
|
+
stdout: stdoutText,
|
|
208
|
+
stderr: rawStderr.toString("utf-8"),
|
|
209
|
+
exitCode: code,
|
|
210
|
+
timedOut: killed,
|
|
211
|
+
durationMs,
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
child.on("error", (err: Error) => {
|
|
216
|
+
clearTimeout(timeout);
|
|
217
|
+
const durationMs = Date.now() - startTime;
|
|
218
|
+
resolve({
|
|
219
|
+
fired: true,
|
|
220
|
+
stdout: "",
|
|
221
|
+
stderr: err.message,
|
|
222
|
+
exitCode: null,
|
|
223
|
+
timedOut: false,
|
|
224
|
+
durationMs,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Write payload to stdin and close it.
|
|
229
|
+
// Handle EPIPE errors gracefully (occurs if the hook script exits before
|
|
230
|
+
// reading all of stdin, which is normal for some hook scripts on certain OS).
|
|
231
|
+
child.stdin.on("error", () => {
|
|
232
|
+
/* ignore EPIPE — hook exited early */
|
|
233
|
+
});
|
|
234
|
+
try {
|
|
235
|
+
child.stdin.write(stdinJson, "utf-8");
|
|
236
|
+
child.stdin.end();
|
|
237
|
+
} catch {
|
|
238
|
+
// ignore
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Derive a steer message from the hook result.
|
|
245
|
+
*
|
|
246
|
+
* - Non-zero exit → error steer message
|
|
247
|
+
* - Timeout → timeout steer message
|
|
248
|
+
* - Empty stdout → null (no steer)
|
|
249
|
+
* - Otherwise → trimmed stdout content
|
|
250
|
+
*/
|
|
251
|
+
export function steerMessageFromHook(stage: HookStage, result: HookResult): string | null {
|
|
252
|
+
if (!result.fired) return null;
|
|
253
|
+
|
|
254
|
+
if (result.timedOut) {
|
|
255
|
+
return `[${stage}-hook] Hook timed out after ${result.durationMs}ms`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (result.exitCode !== null && result.exitCode !== 0) {
|
|
259
|
+
const stderrSnippet = result.stderr.trim().slice(0, 200);
|
|
260
|
+
return `[${stage}-hook] Hook exited with code ${result.exitCode}${stderrSnippet ? `: ${stderrSnippet}` : ""}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const trimmed = result.stdout.trim();
|
|
264
|
+
if (trimmed.length === 0) return null;
|
|
265
|
+
|
|
266
|
+
// Filter out prototype-polluting metric names from hook output
|
|
267
|
+
const lines = trimmed.split("\n");
|
|
268
|
+
const safeLines = lines.filter((line) => {
|
|
269
|
+
const match = /^CREW_METRIC\s+(\w+)=/.exec(line);
|
|
270
|
+
if (match) {
|
|
271
|
+
const name = match[1];
|
|
272
|
+
return !DENIED_METRIC_NAMES.has(name);
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return safeLines.join("\n");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Build a log entry for recording hook execution in events.jsonl.
|
|
282
|
+
*/
|
|
283
|
+
export function hookLogEntry(stage: HookStage, result: HookResult): Record<string, unknown> {
|
|
284
|
+
const entry: Record<string, unknown> = {
|
|
285
|
+
type: "iteration-hook",
|
|
286
|
+
stage,
|
|
287
|
+
fired: result.fired,
|
|
288
|
+
durationMs: result.durationMs,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
if (result.fired) {
|
|
292
|
+
entry.exitCode = result.exitCode;
|
|
293
|
+
entry.timedOut = result.timedOut;
|
|
294
|
+
|
|
295
|
+
// Include truncated stdout/stderr for diagnostics
|
|
296
|
+
if (result.stdout.length > 0) {
|
|
297
|
+
entry.stdoutPreview = result.stdout.slice(0, 512);
|
|
298
|
+
}
|
|
299
|
+
if (result.stderr.length > 0) {
|
|
300
|
+
entry.stderrPreview = result.stderr.slice(0, 512);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return entry;
|
|
305
|
+
}
|