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,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReDoS-resistant pattern matching for secret detection.
|
|
3
|
+
* Uses linear-time scan instead of complex regex to prevent catastrophic backtracking.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Pattern for PEM private keys.
|
|
7
|
+
// F-05 fix: the inner quantifier is bounded to {0,8192}? so a flood of BEGIN
|
|
8
|
+
// markers with no matching END cannot trigger the catastrophic O(n^2) scaling
|
|
9
|
+
// of the old unbounded [\s\S]+? (252KB→205ms, 1024KB→3360ms, 2048KB→13749ms).
|
|
10
|
+
// 8192 (8KB) is the tightest bound that still covers EVERY real PEM private
|
|
11
|
+
// key — an 8192-bit RSA key (the largest practical size) has a ~6.6KB body.
|
|
12
|
+
// NOTE: a 65536 bound was considered but is empirically ~4× SLOWER than the
|
|
13
|
+
// original (V8 regex per-step overhead on large bounded lazy quantifiers), so
|
|
14
|
+
// 8192 is used. A length short-circuit in redactSecretString() additionally
|
|
15
|
+
// skips the regex entirely on inputs > 2MB.
|
|
16
|
+
export const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]{0,8192}?-----END [A-Z ]+PRIVATE KEY-----/g;
|
|
17
|
+
|
|
18
|
+
// --- P1f (RFC §P1f / §6 STRIDE) — additional anchored, ReDoS-SAFE secret patterns. ---
|
|
19
|
+
// All patterns below are LINEAR-TIME: each uses a single bounded quantifier on a
|
|
20
|
+
// character class (fixed {N} or a plain +) with NO nested quantifiers and NO
|
|
21
|
+
// overlapping alternation. Boundaries are zero-width lookarounds on simple char
|
|
22
|
+
// classes, which are also linear. Do NOT introduce (a+)+-style nesting here.
|
|
23
|
+
//
|
|
24
|
+
// RESIDUAL (documented, Med-High per RFC §6): regex redaction is BEST-EFFORT
|
|
25
|
+
// against an *adversarial* worker that can encode/split/transform secrets
|
|
26
|
+
// (base64, line splits, novel formats, non-pattern env vars). This catches the
|
|
27
|
+
// common/accidental leak; it is NOT a boundary against a determined exfiltrator.
|
|
28
|
+
// Full mitigation ladder: (1) redaction here + at artifact-write; (2) Phase 1.5
|
|
29
|
+
// sanitized-env verification; (3) sandbox (deferred).
|
|
30
|
+
|
|
31
|
+
// Exclusion list: LLM usage-count key names that contain "token" but are
|
|
32
|
+
// observable metrics, NOT credentials. These must NEVER be redacted so that
|
|
33
|
+
// token-usage observability in events.jsonl is preserved. The isSecretKey()
|
|
34
|
+
// keyword-scan matches '_token' in 'prompt_tokens' (underscore + "token"),
|
|
35
|
+
// falsely classifying usage counts as secrets. See performance/quality
|
|
36
|
+
// assessment fix #5.
|
|
37
|
+
const TOKEN_COUNT_KEYS = new Set([
|
|
38
|
+
"prompt_tokens",
|
|
39
|
+
"completion_tokens",
|
|
40
|
+
"total_tokens",
|
|
41
|
+
"cached_tokens",
|
|
42
|
+
"reasoning_tokens",
|
|
43
|
+
"cached_read_tokens",
|
|
44
|
+
"cached_write_tokens",
|
|
45
|
+
"input_tokens",
|
|
46
|
+
"output_tokens",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// JWT — three base64url segments separated by dots, distinctive "eyJ" headers.
|
|
50
|
+
// Linear: single + on [A-Za-z0-9_-] per segment, no nesting.
|
|
51
|
+
export const JWT_PATTERN = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
52
|
+
|
|
53
|
+
// GitHub PAT (classic + fine-grained prefixes) — fixed 36-char base62 tail.
|
|
54
|
+
// Linear: fixed {36} count on a char class (constant time per match position).
|
|
55
|
+
export const GITHUB_PAT_PATTERN = /(?<![A-Za-z0-9_])gh[pousr]_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g;
|
|
56
|
+
|
|
57
|
+
// AWS access key id — fixed 16-char uppercase-alphanumeric tail.
|
|
58
|
+
// Linear: fixed {16} count on a char class.
|
|
59
|
+
export const AWS_ACCESS_KEY_PATTERN = /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![0-9A-Z])/g;
|
|
60
|
+
|
|
61
|
+
// Optional extras (RFC OQ13) — same ReDoS-safe shape (fixed counts / single +).
|
|
62
|
+
export const SLACK_TOKEN_PATTERN = /(?<![A-Za-z0-9_-])xox[baprs]-[A-Za-z0-9-]{10,}/g;
|
|
63
|
+
export const GOOGLE_API_KEY_PATTERN = /(?<![A-Za-z0-9_-])AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])/g;
|
|
64
|
+
export const STRIPE_KEY_PATTERN = /(?<![A-Za-z0-9_])sk_live_[0-9a-zA-Z]{24}(?![0-9a-zA-Z])/g;
|
|
65
|
+
|
|
66
|
+
// Linear-time secret key detection
|
|
67
|
+
// IMPORTANT: This function must maintain linear-time guarantees.
|
|
68
|
+
// The fast-path regex uses simple string alternatives with anchors only (no quantifiers),
|
|
69
|
+
// and the linear scan iterates through characters once. If either path is replaced with
|
|
70
|
+
// a more complex regex, catastrophic backtracking (ReDoS) could result.
|
|
71
|
+
// Any modifications must preserve O(n) complexity where n = keyName.length.
|
|
72
|
+
export function isSecretKey(keyName: string): boolean {
|
|
73
|
+
// Fast path: known token-count keys are never secrets.
|
|
74
|
+
if (TOKEN_COUNT_KEYS.has(keyName.toLowerCase())) return false;
|
|
75
|
+
// Fast path: common secret key names (safe anchored regex, no backtracking)
|
|
76
|
+
const lower = keyName.toLowerCase();
|
|
77
|
+
if (/^(token|apikey|api_key|password|secret|credential|authorization|privatekey|private_key)$/.test(lower)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
// Linear scan for prefix characters followed by keywords.
|
|
81
|
+
// FIX (cold-review #1 of Phase 1): use `lower.startsWith(kw, i+1)` + `lower.charAt(...)` to
|
|
82
|
+
// avoid allocating substring+toLowerCase inside the O(n) loop (was O(n^2) — adversarial
|
|
83
|
+
// worker emitting `_`×N+`=` stalled 200KB→29s, 500KB→216s). Now O(n).
|
|
84
|
+
const prefixes = "_.-";
|
|
85
|
+
const keywords = ["token", "api", "key", "password", "passwd", "secret", "credential", "authorization", "private"];
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < keyName.length; i++) {
|
|
88
|
+
if (prefixes.includes(keyName[i])) {
|
|
89
|
+
for (const kw of keywords) {
|
|
90
|
+
if (lower.startsWith(kw, i + 1)) {
|
|
91
|
+
const afterCh = lower.charAt(i + 1 + kw.length);
|
|
92
|
+
if (afterCh === "" || prefixes.includes(afterCh) || /[a-zA-Z0-9]/.test(afterCh)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// FIX (P1f, surfaced by notification-sink test): also match camelCase
|
|
100
|
+
// boundaries (e.g. `apiToken`, `clientSecret`, `authKey`) — the separator
|
|
101
|
+
// scan above requires `_-.` between prefix and keyword and MISSES the very
|
|
102
|
+
// common camelCase pattern. Scan: a keyword matches if it appears with a
|
|
103
|
+
// word boundary (start of string, end of string, camelCase lowercase->upper
|
|
104
|
+
// transition, or one of `_-.` separators). Linear: one forward pass.
|
|
105
|
+
for (const kw of keywords) {
|
|
106
|
+
let from = 0;
|
|
107
|
+
while (true) {
|
|
108
|
+
const idx = lower.indexOf(kw, from);
|
|
109
|
+
if (idx === -1) break;
|
|
110
|
+
const before = idx === 0 ? "" : lower.charAt(idx - 1);
|
|
111
|
+
const afterIdx = idx + kw.length;
|
|
112
|
+
const afterCh = afterIdx >= lower.length ? "" : lower.charAt(afterIdx);
|
|
113
|
+
const atStart = idx === 0;
|
|
114
|
+
const atEnd = afterIdx === lower.length;
|
|
115
|
+
const camelBoundary = /[A-Z]/.test(keyName.charAt(afterIdx)); // lowercase->uppercase in original
|
|
116
|
+
const sepBoundary = prefixes.includes(before) || prefixes.includes(afterCh);
|
|
117
|
+
if (atStart || atEnd || camelBoundary || sepBoundary) {
|
|
118
|
+
// Require non-empty chars before/after to avoid matching `api` inside `capitalize`
|
|
119
|
+
const hasBefore = idx > 0;
|
|
120
|
+
const hasAfter = afterIdx < lower.length;
|
|
121
|
+
if (hasBefore || hasAfter) return true;
|
|
122
|
+
}
|
|
123
|
+
from = idx + 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Boundary chars that may precede an "authorization:" or "Bearer " keyword.
|
|
130
|
+
// Includes '-' so prefixed headers (Proxy-Authorization, X-Authorization) and
|
|
131
|
+
// '\t' so tab-indented headers are recognized. See security review L5.
|
|
132
|
+
const AUTH_HEADER_BOUNDARY_CHARS = new Set([" ", ",", "{", "[", '"', "\r", "\n", "-", "\t"]);
|
|
133
|
+
function isAuthHeaderBoundary(ch: string | undefined): boolean {
|
|
134
|
+
return ch !== undefined && AUTH_HEADER_BOUNDARY_CHARS.has(ch);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Linear-time Authorization header redaction.
|
|
138
|
+
// L3 fix: scan ALL occurrences (previously first-only via indexOf, so a second
|
|
139
|
+
// "authorization:" on a later line leaked). L5 fix: boundary set includes '-'
|
|
140
|
+
// and '\t' so Proxy-Authorization / X-Authorization / tab-indented headers are
|
|
141
|
+
// redacted. Bearer values are left for redactBearerTokens.
|
|
142
|
+
export function redactAuthHeader(line: string): string {
|
|
143
|
+
const lower = line.toLowerCase();
|
|
144
|
+
let result = "";
|
|
145
|
+
let i = 0; // emit cursor into the original `line`
|
|
146
|
+
let searchFrom = 0; // cursor for the next indexOf scan
|
|
147
|
+
for (;;) {
|
|
148
|
+
const authIdx = lower.indexOf("authorization:", searchFrom);
|
|
149
|
+
if (authIdx === -1) {
|
|
150
|
+
result += line.substring(i);
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
// Emit the unchanged span up to this occurrence.
|
|
154
|
+
result += line.substring(i, authIdx);
|
|
155
|
+
const isBoundary = authIdx === 0 || isAuthHeaderBoundary(line[authIdx - 1]);
|
|
156
|
+
const afterAuth = lower.substring(authIdx + 14).trimStart();
|
|
157
|
+
if (isBoundary && !afterAuth.startsWith("bearer ")) {
|
|
158
|
+
// Regular Authorization header — blank the credential value (the rest
|
|
159
|
+
// of the line is the credential). Appending only a marker would leave
|
|
160
|
+
// the secret bytes visible; replace them with "***". Bearer values are
|
|
161
|
+
// left intact here for redactBearerTokens. See security review L3/L5.
|
|
162
|
+
let end = authIdx + 14;
|
|
163
|
+
while (end < line.length && line[end] !== "\r" && line[end] !== "\n") {
|
|
164
|
+
end++;
|
|
165
|
+
}
|
|
166
|
+
result += line.substring(authIdx, authIdx + 14) + " ***";
|
|
167
|
+
i = end;
|
|
168
|
+
searchFrom = end; // entire line consumed; resume after the line break
|
|
169
|
+
} else {
|
|
170
|
+
// Bearer token (handled by redactBearerTokens) OR not a boundary —
|
|
171
|
+
// keep the "authorization:" literal and continue scanning.
|
|
172
|
+
result += line.substring(authIdx, authIdx + 14);
|
|
173
|
+
i = authIdx + 14;
|
|
174
|
+
searchFrom = authIdx + 14;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Linear-time Bearer token redaction
|
|
180
|
+
export function redactBearerTokens(line: string): string {
|
|
181
|
+
const upper = line.toUpperCase();
|
|
182
|
+
const result: string[] = [];
|
|
183
|
+
let i = 0;
|
|
184
|
+
|
|
185
|
+
while (i < line.length) {
|
|
186
|
+
if (upper.startsWith("BEARER ", i)) {
|
|
187
|
+
// Check word boundary: start-of-string or a boundary char. Includes '-'
|
|
188
|
+
// and '\t' (L5) so "Proxy-Authorization: Bearer ..." is redacted.
|
|
189
|
+
if (i > 0 && !isAuthHeaderBoundary(line[i - 1])) {
|
|
190
|
+
result.push(line[i]);
|
|
191
|
+
i++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Found "Bearer " - now find the token
|
|
196
|
+
const bearerPrefix = line.substring(i, i + 7); // "Bearer "
|
|
197
|
+
let j = i + 7;
|
|
198
|
+
let tokenLen = 0;
|
|
199
|
+
while (j < line.length && tokenLen < 200 && /[A-Za-z0-9._~+/-]/.test(line[j])) {
|
|
200
|
+
j++;
|
|
201
|
+
tokenLen++;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (tokenLen >= 8) {
|
|
205
|
+
// Replace with Bearer + *** (redact the token)
|
|
206
|
+
result.push(bearerPrefix + "***");
|
|
207
|
+
i = j;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
result.push(line[i]);
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result.join("");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
219
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
220
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set)
|
|
221
|
+
return false;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function redactSecretString(value: string): string {
|
|
226
|
+
let result = value;
|
|
227
|
+
|
|
228
|
+
// Replace PEM private keys. Skip the regex entirely on very large inputs —
|
|
229
|
+
// no real PEM key exceeds ~7KB, and inputs > 2MB are too large to be a real
|
|
230
|
+
// key. Combined with the bounded {0,8192}? quantifier this keeps the replace
|
|
231
|
+
// from blowing up quadratically on adversarial BEGIN-marker flooding (F-05):
|
|
232
|
+
// unbounded 2MB=13.7s; bounded 2MB≈2.3s; >2MB skipped entirely.
|
|
233
|
+
if (result.length <= 2_000_000) {
|
|
234
|
+
// F-05 hardening: cap total BEGIN markers. No legitimate input contains
|
|
235
|
+
// many PEM keys, and each marker can cost up to 8192 regex steps — without
|
|
236
|
+
// this cap, ~64K adversarial markers in a <2MB input still block the event
|
|
237
|
+
// loop for ~2.5s. Capping at 100 (far above any real multi-key document)
|
|
238
|
+
// bounds worst-case to ~50ms while preserving redaction for normal inputs.
|
|
239
|
+
const beginMarkerCount = result.split("-----BEGIN").length - 1;
|
|
240
|
+
if (beginMarkerCount <= 100) {
|
|
241
|
+
result = result.replace(PEM_PRIVATE_KEY_PATTERN, "***");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Replace Authorization headers (non-Bearer format)
|
|
246
|
+
result = redactAuthHeader(result);
|
|
247
|
+
|
|
248
|
+
// Replace Bearer tokens (run before structured-token patterns so a
|
|
249
|
+
// "Bearer <jwt>" pair is collapsed first; bare tokens are caught below).
|
|
250
|
+
result = redactBearerTokens(result);
|
|
251
|
+
|
|
252
|
+
// P1f: structured secret tokens (JWT / GitHub PAT / AWS keys + optional
|
|
253
|
+
// Slack/Google/Stripe). Best-effort vs adversarial workers (see note above).
|
|
254
|
+
result = result
|
|
255
|
+
.replace(JWT_PATTERN, "***")
|
|
256
|
+
.replace(GITHUB_PAT_PATTERN, "***")
|
|
257
|
+
.replace(AWS_ACCESS_KEY_PATTERN, "***")
|
|
258
|
+
.replace(SLACK_TOKEN_PATTERN, "***")
|
|
259
|
+
.replace(GOOGLE_API_KEY_PATTERN, "***")
|
|
260
|
+
.replace(STRIPE_KEY_PATTERN, "***");
|
|
261
|
+
|
|
262
|
+
// Replace inline secrets: key=value or key:value patterns
|
|
263
|
+
result = redactInlineSecrets(result);
|
|
264
|
+
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Linear-time inline secret redaction: token=xxx, api_key=xxx, etc.
|
|
269
|
+
// FIX (P1f): previously O(n^2) — after a non-secret alphanumeric run, the loop did
|
|
270
|
+
// i++ (advance 1 char) and re-scanned from i+1, so a long run was rescanned O(n)
|
|
271
|
+
// times = O(n^2). The P1f ReDoS test (300KB no-dot input) surfaced this pre-existing
|
|
272
|
+
// bug. Now advances past the whole run when it isn't a redactable secret -> O(n).
|
|
273
|
+
// FIX (BG2 follow-up): the inner-loop regex test /[a-zA-Z0-9_-]/.test(value[j])
|
|
274
|
+
// is O(1) per call but with measurable regex-engine overhead — for a 100KB
|
|
275
|
+
// underscore run that's 100K regex calls, well over the 200ms budget the
|
|
276
|
+
// P1f regression test allows. Replaced with a charCodeAt check (~5x faster
|
|
277
|
+
// in practice, O(1) per call with no regex allocation/eval cost).
|
|
278
|
+
function isKeyChar(c: string): boolean {
|
|
279
|
+
const code = c.charCodeAt(0);
|
|
280
|
+
return (
|
|
281
|
+
(code >= 48 && code <= 57) || // 0-9
|
|
282
|
+
(code >= 65 && code <= 90) || // A-Z
|
|
283
|
+
(code >= 97 && code <= 122) || // a-z
|
|
284
|
+
code === 95 || // _
|
|
285
|
+
code === 45 // -
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function redactInlineSecrets(value: string): string {
|
|
290
|
+
const result: string[] = [];
|
|
291
|
+
let i = 0;
|
|
292
|
+
|
|
293
|
+
while (i < value.length) {
|
|
294
|
+
// Collect a run of key characters (alphanumeric, underscore, hyphen).
|
|
295
|
+
let j = i;
|
|
296
|
+
while (j < value.length && isKeyChar(value[j])) {
|
|
297
|
+
j++;
|
|
298
|
+
}
|
|
299
|
+
const keyLen = j - i;
|
|
300
|
+
|
|
301
|
+
let redacted = false;
|
|
302
|
+
if (keyLen > 0 && j < value.length && (value[j] === "=" || value[j] === ":")) {
|
|
303
|
+
const key = value.substring(i, j);
|
|
304
|
+
|
|
305
|
+
// Check if this is a secret key
|
|
306
|
+
if (isSecretKey(key)) {
|
|
307
|
+
// Find the value (everything after = or : until space, comma, or end)
|
|
308
|
+
const sep = value[j];
|
|
309
|
+
let k = j + 1;
|
|
310
|
+
let valLen = 0;
|
|
311
|
+
while (
|
|
312
|
+
k < value.length &&
|
|
313
|
+
valLen < 500 &&
|
|
314
|
+
value[k] !== " " &&
|
|
315
|
+
value[k] !== "," &&
|
|
316
|
+
value[k] !== ";" &&
|
|
317
|
+
value[k] !== '"' &&
|
|
318
|
+
value[k] !== "\r" &&
|
|
319
|
+
value[k] !== "\n"
|
|
320
|
+
) {
|
|
321
|
+
k++;
|
|
322
|
+
valLen++;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Only redact if there's actual content
|
|
326
|
+
if (valLen > 0) {
|
|
327
|
+
result.push(key);
|
|
328
|
+
result.push(sep);
|
|
329
|
+
result.push("***");
|
|
330
|
+
i = k;
|
|
331
|
+
redacted = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!redacted) {
|
|
337
|
+
if (keyLen > 0) {
|
|
338
|
+
// Not a redactable secret — push the WHOLE run and advance past it (O(n)).
|
|
339
|
+
result.push(value.substring(i, j));
|
|
340
|
+
i = j;
|
|
341
|
+
} else {
|
|
342
|
+
// Single non-key character (space, punctuation, etc.)
|
|
343
|
+
result.push(value[i]);
|
|
344
|
+
i++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return result.join("");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function redactSecrets(value: unknown, keyName = ""): unknown {
|
|
353
|
+
if (keyName && isSecretKey(keyName)) return "***";
|
|
354
|
+
if (typeof value === "string") return redactSecretString(value);
|
|
355
|
+
if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
|
|
356
|
+
if (isRecord(value)) {
|
|
357
|
+
const output: Record<string, unknown> = {};
|
|
358
|
+
for (const [key, entry] of Object.entries(value)) output[key] = redactSecrets(entry, key);
|
|
359
|
+
return output;
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function redactJsonLine(line: string): string {
|
|
365
|
+
try {
|
|
366
|
+
return JSON.stringify(redactSecrets(JSON.parse(line) as unknown));
|
|
367
|
+
} catch {
|
|
368
|
+
return redactSecretString(line);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the bash executable path, with Windows fallbacks for Git Bash.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveBashCmd(): string {
|
|
7
|
+
if (process.platform !== "win32") return "bash";
|
|
8
|
+
const candidates = [process.env.SHELL, "C:\\Program Files\\Git\\bin\\bash.exe", "C:\\Program Files (x86)\\Git\\bin\\bash.exe"];
|
|
9
|
+
for (const cand of candidates) {
|
|
10
|
+
if (cand && fs.existsSync(cand)) return cand;
|
|
11
|
+
}
|
|
12
|
+
return "bash";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Choose the right shell command and arguments for a script path.
|
|
17
|
+
* On Windows, powershell (.ps1) and batch (.cmd/.bat) run natively.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveShellForScript(scriptPath: string): {
|
|
20
|
+
command: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
} {
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
if (scriptPath.endsWith(".ps1")) {
|
|
25
|
+
return { command: "powershell", args: ["-File", scriptPath] };
|
|
26
|
+
}
|
|
27
|
+
if (scriptPath.endsWith(".cmd") || scriptPath.endsWith(".bat")) {
|
|
28
|
+
// Node >= 20 blocks direct spawn of .bat/.cmd without shell (CVE-2024-27980)
|
|
29
|
+
return {
|
|
30
|
+
command: process.env.ComSpec ?? "cmd.exe",
|
|
31
|
+
args: ["/d", "/s", "/c", scriptPath],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { command: resolveBashCmd(), args: [scriptPath] };
|
|
36
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunWatcherRegistry — bounded per-run filesystem watcher registry.
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM (pts/2 interactive-session hang, /home/bom/pts2-hang-investigation-2026-06-16.md):
|
|
5
|
+
* `watchCrewState` used `fs.watch(<crewRoot>/state, { recursive: true })`. On
|
|
6
|
+
* Linux, Node implements "recursive" by creating ONE inotify watch PER
|
|
7
|
+
* SUBDIRECTORY. With many historical runs under `.crew/state/runs/`, this
|
|
8
|
+
* ballooned to hundreds of watches (109→339 observed) — one per run dir ever —
|
|
9
|
+
* and the resulting event volume + render amplification produced a permanent
|
|
10
|
+
* busy-loop (71% CPU, 400KB/s read) even with no active work.
|
|
11
|
+
*
|
|
12
|
+
* FIX: instead of recursively watching the whole history, watch a SINGLE
|
|
13
|
+
* non-recursive watcher on the `runs/` root (to detect new run dirs appearing)
|
|
14
|
+
* PLUS one non-recursive watcher PER ACTIVE RUN. Total inotify cost is now
|
|
15
|
+
* O(active runs) — typically 1–5 — not O(total history). Completed runs stop
|
|
16
|
+
* being watched as soon as they leave the active set (reconciled by buildFrame,
|
|
17
|
+
* which reads manifest statuses each preload tick).
|
|
18
|
+
*
|
|
19
|
+
* The registry is intentionally small and directly unit-testable (a Map of
|
|
20
|
+
* watchers with add/remove/reconcile/close semantics).
|
|
21
|
+
*/
|
|
22
|
+
import type { FSWatcher } from "node:fs";
|
|
23
|
+
import { closeWatcher, watchWithErrorHandler } from "./fs-watch.ts";
|
|
24
|
+
|
|
25
|
+
export interface ReconcileResult {
|
|
26
|
+
added: string[];
|
|
27
|
+
removed: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ActiveRun {
|
|
31
|
+
runId: string;
|
|
32
|
+
runDir: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type RunChangeCallback = (runId: string) => void;
|
|
36
|
+
export type ErrorCallback = (error: unknown) => void;
|
|
37
|
+
|
|
38
|
+
export class RunWatcherRegistry {
|
|
39
|
+
private readonly runWatchers = new Map<string, FSWatcher>();
|
|
40
|
+
private rootWatcher: FSWatcher | undefined;
|
|
41
|
+
private closed = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Watch the `runs/` root directory (non-recursive) and invoke `onNewRun`
|
|
45
|
+
* whenever a new run subdirectory appears. This is the only way to detect a
|
|
46
|
+
* brand-new run, because `crew.run.created` is never emitted by the runtime
|
|
47
|
+
* (confirmed: only `crew.run.completed/failed/cancelled` are emitted).
|
|
48
|
+
*/
|
|
49
|
+
setRootWatcher(runsDir: string, onNewRun: RunChangeCallback, onError?: ErrorCallback): void {
|
|
50
|
+
if (this.closed) return;
|
|
51
|
+
// Replace any prior root watcher.
|
|
52
|
+
closeWatcher(this.rootWatcher);
|
|
53
|
+
this.rootWatcher =
|
|
54
|
+
watchWithErrorHandler(
|
|
55
|
+
runsDir,
|
|
56
|
+
(_eventType, filename) => {
|
|
57
|
+
if (typeof filename !== "string" || filename.length === 0) return;
|
|
58
|
+
// fs.watch reports directory entries as bare names (no slash on Linux).
|
|
59
|
+
// A new run dir appears as `runs/<runId>` → filename = "<runId>".
|
|
60
|
+
// Filter obviously-not-run-id noise (files, temp, etc.) defensively.
|
|
61
|
+
const candidate = filename.replace(/\\/g, "/").split("/")[0];
|
|
62
|
+
if (candidate.length === 0) return;
|
|
63
|
+
onNewRun(candidate);
|
|
64
|
+
},
|
|
65
|
+
(error) => {
|
|
66
|
+
if (onError) onError(error);
|
|
67
|
+
},
|
|
68
|
+
) ?? undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Add a NON-RECURSIVE watcher on a single run directory. Costs exactly ONE
|
|
73
|
+
* inotify watch. If a watcher for this runId already exists, close + replace.
|
|
74
|
+
* Returns true if a watcher is now active for this runId.
|
|
75
|
+
*/
|
|
76
|
+
addRunWatcher(runId: string, runDir: string, onChange: RunChangeCallback, onError?: ErrorCallback): boolean {
|
|
77
|
+
if (this.closed) return false;
|
|
78
|
+
const existing = this.runWatchers.get(runId);
|
|
79
|
+
if (existing) closeWatcher(existing);
|
|
80
|
+
const watcher = watchWithErrorHandler(
|
|
81
|
+
runDir,
|
|
82
|
+
() => onChange(runId),
|
|
83
|
+
(error) => {
|
|
84
|
+
if (onError) onError(error);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
if (watcher) {
|
|
88
|
+
this.runWatchers.set(runId, watcher);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
// watchWithErrorHandler returned null (fs.watch unsupported / dir missing).
|
|
92
|
+
// Remove any stale entry so hasWatcher() stays honest.
|
|
93
|
+
this.runWatchers.delete(runId);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Remove and close a specific run's watcher. No-op if not watched. */
|
|
98
|
+
removeRunWatcher(runId: string): void {
|
|
99
|
+
const watcher = this.runWatchers.get(runId);
|
|
100
|
+
if (watcher) {
|
|
101
|
+
closeWatcher(watcher);
|
|
102
|
+
this.runWatchers.delete(runId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Is a run currently being watched? */
|
|
107
|
+
hasWatcher(runId: string): boolean {
|
|
108
|
+
return this.runWatchers.has(runId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reconcile against the current active-run set: add watchers for active runs
|
|
113
|
+
* not yet watched, remove watchers for runs that left the active set. Returns
|
|
114
|
+
* which runIds were added / removed (useful for logging + tests).
|
|
115
|
+
*/
|
|
116
|
+
reconcile(activeRuns: ActiveRun[], onChange: RunChangeCallback, onError?: ErrorCallback): ReconcileResult {
|
|
117
|
+
if (this.closed) return { added: [], removed: [] };
|
|
118
|
+
const activeIds = new Set(activeRuns.map((r) => r.runId));
|
|
119
|
+
const added: string[] = [];
|
|
120
|
+
const removed: string[] = [];
|
|
121
|
+
// Remove watchers for runs no longer active.
|
|
122
|
+
for (const runId of [...this.runWatchers.keys()]) {
|
|
123
|
+
if (!activeIds.has(runId)) {
|
|
124
|
+
this.removeRunWatcher(runId);
|
|
125
|
+
removed.push(runId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Add watchers for newly-active runs.
|
|
129
|
+
for (const { runId, runDir } of activeRuns) {
|
|
130
|
+
if (!this.runWatchers.has(runId)) {
|
|
131
|
+
if (this.addRunWatcher(runId, runDir, onChange, onError)) {
|
|
132
|
+
added.push(runId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { added, removed };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Close ALL watchers (per-run + root). Safe to call multiple times. */
|
|
140
|
+
closeAll(): void {
|
|
141
|
+
this.closed = true;
|
|
142
|
+
for (const watcher of this.runWatchers.values()) closeWatcher(watcher);
|
|
143
|
+
this.runWatchers.clear();
|
|
144
|
+
closeWatcher(this.rootWatcher);
|
|
145
|
+
this.rootWatcher = undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Number of active PER-RUN watchers (excludes the root watcher). */
|
|
149
|
+
get size(): number {
|
|
150
|
+
return this.runWatchers.size;
|
|
151
|
+
}
|
|
152
|
+
}
|