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,239 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { NotificationDescriptor } from "../extension/notification-router.ts";
|
|
4
|
+
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
5
|
+
import { appendEvent } from "../state/event-log.ts";
|
|
6
|
+
import { loadRunManifestById } from "../state/state-store.ts";
|
|
7
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
8
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
9
|
+
import { DEFAULT_GRADIENT_THRESHOLDS, type GradientThresholds, type HeartbeatLevel, heartbeatAgeMs } from "./heartbeat-gradient.ts";
|
|
10
|
+
import type { ManifestCache } from "./manifest-cache.ts";
|
|
11
|
+
|
|
12
|
+
export interface HeartbeatWatcherRouter {
|
|
13
|
+
enqueue(notification: NotificationDescriptor): boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface HeartbeatWatcherOptions {
|
|
17
|
+
cwd: string;
|
|
18
|
+
pollIntervalMs?: number;
|
|
19
|
+
thresholds?: GradientThresholds;
|
|
20
|
+
manifestCache: ManifestCache;
|
|
21
|
+
registry: MetricRegistry;
|
|
22
|
+
router: HeartbeatWatcherRouter;
|
|
23
|
+
deadletterTickThreshold?: number;
|
|
24
|
+
/**
|
|
25
|
+
* 3.6 — minimum interval between repeated deadletter triggers for the same
|
|
26
|
+
* runId+taskId. Without this, a flaky worker (dead → alive → dead) can
|
|
27
|
+
* fire deadletter entries faster than the operator can respond. Default
|
|
28
|
+
* 60_000 ms.
|
|
29
|
+
*/
|
|
30
|
+
deadletterCooldownMs?: number;
|
|
31
|
+
onDead?: (runId: string, taskId: string, elapsed: number) => void;
|
|
32
|
+
onDeadletterTrigger?: (manifest: TeamRunManifest, taskId: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Polls running runs for heartbeat staleness.
|
|
37
|
+
*
|
|
38
|
+
* Uses recursive setTimeout to avoid timer storms.
|
|
39
|
+
* Cleanup is done in the same pass — no second scan over manifests.
|
|
40
|
+
* Keys for runs that disappear from the cache are cleaned via staleness-age policy
|
|
41
|
+
* rather than being leaked forever.
|
|
42
|
+
*/
|
|
43
|
+
export class HeartbeatWatcher {
|
|
44
|
+
private timer?: ReturnType<typeof setTimeout>;
|
|
45
|
+
private lastLevel = new Map<string, HeartbeatLevel>();
|
|
46
|
+
private consecutiveDead = new Map<string, number>();
|
|
47
|
+
private lastSeen = new Map<string, number>(); // key → last time it was active
|
|
48
|
+
private lastDeadletterTriggerAt = new Map<string, number>(); // 3.6 cooldown gate
|
|
49
|
+
/** Max age (ms) to retain a stale key before garbage-collecting it. */
|
|
50
|
+
private readonly maxKeyAgeMs = 600_000; // 10 minutes
|
|
51
|
+
private readonly opts: HeartbeatWatcherOptions;
|
|
52
|
+
|
|
53
|
+
constructor(opts: HeartbeatWatcherOptions) {
|
|
54
|
+
this.opts = opts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
start(): void {
|
|
58
|
+
this.dispose();
|
|
59
|
+
this.scheduleTick();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private scheduleTick(): void {
|
|
63
|
+
// 3.2 — when at least one run has a dead-streak in progress, poll faster
|
|
64
|
+
// (1s) so operators get notified quickly. Healthy state stays at the
|
|
65
|
+
// configured interval (default 5s) to keep idle CPU near zero.
|
|
66
|
+
const baseInterval = this.opts.pollIntervalMs ?? 5000;
|
|
67
|
+
const interval = this.consecutiveDead.size > 0 ? Math.min(1000, baseInterval) : baseInterval;
|
|
68
|
+
this.timer = setTimeout(() => this.tick(), interval);
|
|
69
|
+
this.timer.unref();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
tick(now = Date.now()): void {
|
|
73
|
+
try {
|
|
74
|
+
this.tickUnsafe(now);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logInternalError("heartbeat-watcher.tick", error);
|
|
77
|
+
} finally {
|
|
78
|
+
this.scheduleTick();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private tickUnsafe(now: number): void {
|
|
83
|
+
const thresholds = this.opts.thresholds ?? DEFAULT_GRADIENT_THRESHOLDS;
|
|
84
|
+
const tickThreshold = this.opts.deadletterTickThreshold ?? 3;
|
|
85
|
+
const activeKeys = new Set<string>();
|
|
86
|
+
|
|
87
|
+
for (const run of this.opts.manifestCache.list(50)) {
|
|
88
|
+
if (run.status !== "running") continue;
|
|
89
|
+
// Bug #5 fix: if stateRoot doesn't exist, the run was pruned — skip it silently.
|
|
90
|
+
// This prevents stale "heartbeat dead" notifications for runs that no longer exist.
|
|
91
|
+
if (!fs.existsSync(run.stateRoot)) continue;
|
|
92
|
+
const loaded = loadRunManifestById(this.opts.cwd, run.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency;
|
|
93
|
+
if (!loaded) continue;
|
|
94
|
+
// Defensive guard: cache may return stale "running" while disk says terminal.
|
|
95
|
+
// Re-read manifest from disk to verify before entering the heavy loop.
|
|
96
|
+
// This closes the false-positive alert loop when a run completes but the
|
|
97
|
+
// manifestCache.list(50) still serves a pre-completion snapshot.
|
|
98
|
+
if (loaded.manifest.status !== "running") continue;
|
|
99
|
+
for (const task of loaded.tasks) {
|
|
100
|
+
if (task.status !== "running") continue;
|
|
101
|
+
const key = `${run.runId}:${task.id}`;
|
|
102
|
+
activeKeys.add(key);
|
|
103
|
+
this.lastSeen.set(key, now);
|
|
104
|
+
|
|
105
|
+
// Check heartbeat staleness with lastActivityAt fallback
|
|
106
|
+
let elapsed = heartbeatAgeMs(task.heartbeat, now);
|
|
107
|
+
// PR #6 partial: use lastActivityAt as fallback when heartbeat is stale
|
|
108
|
+
// If heartbeat is stale but lastActivityAt is fresher, use activity age instead.
|
|
109
|
+
// This prevents false-positive dead detection for live-session tasks during long operations.
|
|
110
|
+
if (task.agentProgress?.lastActivityAt) {
|
|
111
|
+
const activityAt = new Date(task.agentProgress.lastActivityAt).getTime();
|
|
112
|
+
if (Number.isFinite(activityAt)) {
|
|
113
|
+
const activityAge = now - activityAt;
|
|
114
|
+
// Use activity age if it's fresher than heartbeat age
|
|
115
|
+
// (no upper bound - if agent has recent activity, trust it even if old)
|
|
116
|
+
if (activityAge < elapsed) {
|
|
117
|
+
elapsed = activityAge;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// PID liveness gate: if the worker process is still alive, downgrade
|
|
122
|
+
// "dead" to "stale". This prevents false positives when the LLM spends
|
|
123
|
+
// a long time generating a response (>5 min) without tool calls.
|
|
124
|
+
// A truly dead process will eventually be detected by exit handlers.
|
|
125
|
+
let isProcessAlive = false;
|
|
126
|
+
const workerPid = task.heartbeat?.pid ?? task.checkpoint?.childPid;
|
|
127
|
+
if (workerPid && workerPid > 0) {
|
|
128
|
+
try {
|
|
129
|
+
process.kill(workerPid, 0);
|
|
130
|
+
isProcessAlive = true;
|
|
131
|
+
} catch {
|
|
132
|
+
// Process is dead
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let level: HeartbeatLevel =
|
|
136
|
+
elapsed > thresholds.deadMs
|
|
137
|
+
? "dead"
|
|
138
|
+
: elapsed > thresholds.staleMs
|
|
139
|
+
? "stale"
|
|
140
|
+
: elapsed > thresholds.warnMs
|
|
141
|
+
? "warn"
|
|
142
|
+
: "healthy";
|
|
143
|
+
if (level === "dead" && isProcessAlive) {
|
|
144
|
+
level = "stale";
|
|
145
|
+
}
|
|
146
|
+
// W8 fix: completion-artifact check — prevents false-positive "dead"
|
|
147
|
+
// during the exit-before-manifest-update race. When a worker process
|
|
148
|
+
// exits normally after completing its task, the result artifact is
|
|
149
|
+
// already on disk, but the manifest status update may lag by a few
|
|
150
|
+
// seconds (status + finishedAt are set atomically in task-runner.ts).
|
|
151
|
+
// If the result file exists, the task completed — downgrade to
|
|
152
|
+
// "stale" so the watcher doesn't fire a misleading "dead" notification
|
|
153
|
+
// for a task that already produced its output.
|
|
154
|
+
// W8-fix-v2 — path-traversal defense-in-depth. task.id is
|
|
155
|
+
// generated internally (e.g. "ts1", "ts2") but we still
|
|
156
|
+
// resolve the candidate path and verify it's strictly
|
|
157
|
+
// contained within <artifactsRoot>/results/. If task.id
|
|
158
|
+
// contained "../" or absolute path segments, the containment
|
|
159
|
+
// check fails and we skip the W8 check (fail-closed: don't
|
|
160
|
+
// accidentally treat a malicious task ID as "completed").
|
|
161
|
+
if (level === "dead" && !isProcessAlive && loaded.manifest.artifactsRoot) {
|
|
162
|
+
const resultsDir = path.resolve(loaded.manifest.artifactsRoot, "results");
|
|
163
|
+
const candidate = path.resolve(resultsDir, `${task.id}.txt`);
|
|
164
|
+
if (candidate.startsWith(resultsDir + path.sep) && fs.existsSync(candidate)) {
|
|
165
|
+
level = "stale";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.opts.registry
|
|
169
|
+
.gauge("crew.heartbeat.staleness_ms", "Heartbeat elapsed since last seen, milliseconds")
|
|
170
|
+
.set({ runId: run.runId, taskId: task.id }, Number.isFinite(elapsed) ? elapsed : thresholds.deadMs);
|
|
171
|
+
this.opts.registry
|
|
172
|
+
.counter("crew.heartbeat.level_total", "Heartbeat classifications by level")
|
|
173
|
+
.inc({ runId: run.runId, level });
|
|
174
|
+
const previous = this.lastLevel.get(key);
|
|
175
|
+
this.lastLevel.set(key, level);
|
|
176
|
+
if (level === "dead" && previous !== "dead") {
|
|
177
|
+
this.opts.registry.counter("crew.heartbeat.dead_total", "Dead heartbeat detections").inc({ runId: run.runId });
|
|
178
|
+
appendEvent(loaded.manifest.eventsPath, {
|
|
179
|
+
type: "crew.task.heartbeat_dead",
|
|
180
|
+
runId: run.runId,
|
|
181
|
+
taskId: task.id,
|
|
182
|
+
message: `Task ${task.id} heartbeat dead.`,
|
|
183
|
+
data: {
|
|
184
|
+
elapsedMs: Number.isFinite(elapsed) ? elapsed : undefined,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
// W9 fix — prefix title with short run label (first 8 chars of runId)
|
|
188
|
+
// so ambient notifications are scannable when multiple runs are
|
|
189
|
+
// in flight. Full runId remains in the notification object.
|
|
190
|
+
const runLabel = run.runId.slice(0, 8);
|
|
191
|
+
this.opts.router.enqueue({
|
|
192
|
+
id: `dead_${run.runId}_${task.id}`,
|
|
193
|
+
severity: "warning",
|
|
194
|
+
source: "heartbeat-watcher",
|
|
195
|
+
runId: run.runId,
|
|
196
|
+
title: `[${runLabel}] Task ${task.id} heartbeat dead`,
|
|
197
|
+
body: "Background watcher detected a stuck worker.",
|
|
198
|
+
});
|
|
199
|
+
this.opts.onDead?.(run.runId, task.id, Number.isFinite(elapsed) ? elapsed : thresholds.deadMs);
|
|
200
|
+
}
|
|
201
|
+
if (level === "dead") {
|
|
202
|
+
const count = (this.consecutiveDead.get(key) ?? 0) + 1;
|
|
203
|
+
this.consecutiveDead.set(key, count);
|
|
204
|
+
if (count === tickThreshold) {
|
|
205
|
+
// 3.6 cooldown gate
|
|
206
|
+
const cooldown = this.opts.deadletterCooldownMs ?? 60_000;
|
|
207
|
+
const lastTrigger = this.lastDeadletterTriggerAt.get(key) ?? 0;
|
|
208
|
+
if (now - lastTrigger >= cooldown) {
|
|
209
|
+
this.lastDeadletterTriggerAt.set(key, now);
|
|
210
|
+
this.opts.onDeadletterTrigger?.(loaded.manifest, task.id);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
this.consecutiveDead.delete(key);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Cleanup: drop keys that were NOT in this tick's active set AND
|
|
220
|
+
// haven't been seen for > maxKeyAgeMs. This covers runs that
|
|
221
|
+
// completed or fell out of the manifest cache's top-50 window.
|
|
222
|
+
const cutoff = now - this.maxKeyAgeMs;
|
|
223
|
+
for (const [key, ts] of this.lastSeen) {
|
|
224
|
+
if (!activeKeys.has(key) && ts < cutoff) {
|
|
225
|
+
this.lastLevel.delete(key);
|
|
226
|
+
this.consecutiveDead.delete(key);
|
|
227
|
+
this.lastSeen.delete(key);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
dispose(): void {
|
|
233
|
+
if (this.timer) clearTimeout(this.timer);
|
|
234
|
+
this.timer = undefined;
|
|
235
|
+
this.lastLevel.clear();
|
|
236
|
+
this.consecutiveDead.clear();
|
|
237
|
+
this.lastSeen.clear();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HiddenHandoffService - Sends hidden boomerang-handoff messages to parent agents.
|
|
3
|
+
*
|
|
4
|
+
* Based on pi-boomerang's triggerHiddenOrchestratorHandoff() pattern:
|
|
5
|
+
* - Sends customType: "boomerang-handoff" messages
|
|
6
|
+
* - Full handoff details in hidden message to parent
|
|
7
|
+
* - Orchestrator immediately reads summary
|
|
8
|
+
*
|
|
9
|
+
* @see docs/pi-boomerang-integration-plan.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
13
|
+
import type { HandoffSummary } from "./handoff-manager.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Type of hidden handoff message.
|
|
17
|
+
*/
|
|
18
|
+
export type HiddenHandoffType = "boomerang-handoff" | "task-complete" | "context-ready";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Hidden handoff message sent to parent agent.
|
|
22
|
+
*/
|
|
23
|
+
export interface HiddenHandoff {
|
|
24
|
+
type: HiddenHandoffType;
|
|
25
|
+
hidden: true;
|
|
26
|
+
content: HandoffContent;
|
|
27
|
+
metadata: HiddenHandoffMetadata;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Metadata for hidden handoff message.
|
|
32
|
+
*/
|
|
33
|
+
export interface HiddenHandoffMetadata {
|
|
34
|
+
taskId: string;
|
|
35
|
+
runId: string;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
priority: HandoffPriority;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Priority level for hidden handoffs.
|
|
42
|
+
*/
|
|
43
|
+
export type HandoffPriority = "low" | "normal" | "high";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Content of a hidden handoff message.
|
|
47
|
+
*/
|
|
48
|
+
export interface HandoffContent {
|
|
49
|
+
summary: string;
|
|
50
|
+
files: {
|
|
51
|
+
created: string[];
|
|
52
|
+
modified: string[];
|
|
53
|
+
deleted: string[];
|
|
54
|
+
};
|
|
55
|
+
decisions: {
|
|
56
|
+
rationale: string;
|
|
57
|
+
outcome: string;
|
|
58
|
+
}[];
|
|
59
|
+
nextSteps: string[];
|
|
60
|
+
metrics: {
|
|
61
|
+
tokens: number;
|
|
62
|
+
duration: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Options for HiddenHandoffService.
|
|
68
|
+
*/
|
|
69
|
+
export interface HiddenHandoffServiceOptions {
|
|
70
|
+
/** Custom mailbox service for sending messages */
|
|
71
|
+
mailbox?: HiddenHandoffMailbox;
|
|
72
|
+
/** Event emitter for handoff events */
|
|
73
|
+
eventEmitter?: HiddenHandoffEventEmitter;
|
|
74
|
+
/** Get parent agent ID callback */
|
|
75
|
+
getParentAgentId?: () => string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Mailbox interface for sending hidden handoffs.
|
|
80
|
+
*/
|
|
81
|
+
export interface HiddenHandoffMailbox {
|
|
82
|
+
send(recipient: string, message: HiddenHandoff): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Event emitter for hidden handoff events.
|
|
87
|
+
*/
|
|
88
|
+
export interface HiddenHandoffEventEmitter {
|
|
89
|
+
emit(event: string, data: unknown): void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Event data for hidden handoff sent event.
|
|
94
|
+
*/
|
|
95
|
+
export interface HiddenHandoffSentEventData {
|
|
96
|
+
summary: HandoffSummary;
|
|
97
|
+
recipient: string;
|
|
98
|
+
priority: HandoffPriority;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* HiddenHandoffService sends hidden boomerang-handoff messages to parent agents.
|
|
103
|
+
* This enables agents to communicate progress and context without explicit user-visible output.
|
|
104
|
+
*/
|
|
105
|
+
export class HiddenHandoffService {
|
|
106
|
+
private mailbox: HiddenHandoffMailbox | null = null;
|
|
107
|
+
private eventEmitter: HiddenHandoffEventEmitter | null = null;
|
|
108
|
+
private getParentAgentIdFn: (() => string) | null = null;
|
|
109
|
+
private enabled = true;
|
|
110
|
+
// C7: Track rate limiting per recipient
|
|
111
|
+
private sendTimestamps = new Map<string, number[]>();
|
|
112
|
+
private readonly RATE_LIMIT_WINDOW_MS = 60000; // 1 minute
|
|
113
|
+
private readonly RATE_LIMIT_MAX_SENDS = 10; // Max handoffs per window
|
|
114
|
+
|
|
115
|
+
constructor(options: HiddenHandoffServiceOptions = {}) {
|
|
116
|
+
if (options.mailbox) {
|
|
117
|
+
this.mailbox = options.mailbox;
|
|
118
|
+
}
|
|
119
|
+
if (options.eventEmitter) {
|
|
120
|
+
this.eventEmitter = options.eventEmitter;
|
|
121
|
+
}
|
|
122
|
+
if (options.getParentAgentId) {
|
|
123
|
+
this.getParentAgentIdFn = options.getParentAgentId;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if hidden handoff service is enabled.
|
|
129
|
+
*/
|
|
130
|
+
isEnabled(): boolean {
|
|
131
|
+
return this.enabled;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Enable or disable hidden handoff service.
|
|
136
|
+
*/
|
|
137
|
+
setEnabled(enabled: boolean): void {
|
|
138
|
+
this.enabled = enabled;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Set the mailbox service for sending messages.
|
|
143
|
+
*/
|
|
144
|
+
setMailbox(mailbox: HiddenHandoffMailbox): void {
|
|
145
|
+
this.mailbox = mailbox;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Set the event emitter for events.
|
|
150
|
+
*/
|
|
151
|
+
setEventEmitter(eventEmitter: HiddenHandoffEventEmitter): void {
|
|
152
|
+
this.eventEmitter = eventEmitter;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Set the function to get parent agent ID.
|
|
157
|
+
*/
|
|
158
|
+
setGetParentAgentId(fn: () => string): void {
|
|
159
|
+
this.getParentAgentIdFn = fn;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Send a hidden handoff to the parent agent or specified recipient.
|
|
164
|
+
*
|
|
165
|
+
* @param summary - The handoff summary to send
|
|
166
|
+
* @param options - Send options
|
|
167
|
+
*/
|
|
168
|
+
sendHandoff(summary: HandoffSummary, options: SendHandoffOptions = {}): void {
|
|
169
|
+
if (!this.enabled) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const priority = options.priority ?? this.inferPriority(summary);
|
|
174
|
+
const content = this.buildContent(summary);
|
|
175
|
+
const recipient = options.to ?? this.getParentAgentId();
|
|
176
|
+
|
|
177
|
+
if (!recipient) {
|
|
178
|
+
// No parent to send to, but we still emit the event
|
|
179
|
+
this.eventEmitter?.emit("handoff:sent_no_recipient", {
|
|
180
|
+
summary,
|
|
181
|
+
priority,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// C7: Validate recipient is a reasonable agent ID
|
|
187
|
+
if (!this.isValidRecipient(recipient)) {
|
|
188
|
+
this.eventEmitter?.emit("handoff:invalid_recipient", {
|
|
189
|
+
recipient,
|
|
190
|
+
summary,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// C7: Check rate limit
|
|
196
|
+
if (this.isRateLimited(recipient)) {
|
|
197
|
+
this.eventEmitter?.emit("handoff:rate_limited", {
|
|
198
|
+
recipient,
|
|
199
|
+
summary,
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const message: HiddenHandoff = {
|
|
205
|
+
type: options.customType ?? "boomerang-handoff",
|
|
206
|
+
hidden: true,
|
|
207
|
+
content,
|
|
208
|
+
metadata: {
|
|
209
|
+
taskId: summary.taskId,
|
|
210
|
+
runId: summary.runId,
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
priority,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (this.mailbox) {
|
|
217
|
+
this.mailbox.send(recipient, message);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// C7: Record send for rate limiting
|
|
221
|
+
this.recordSend(recipient);
|
|
222
|
+
|
|
223
|
+
this.eventEmitter?.emit("handoff:sent", {
|
|
224
|
+
summary,
|
|
225
|
+
recipient,
|
|
226
|
+
priority,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Send a hidden handoff immediately (fire and forget).
|
|
232
|
+
*/
|
|
233
|
+
sendHandoffAsync(summary: HandoffSummary, options?: SendHandoffOptions): void {
|
|
234
|
+
// Fire and forget - no await
|
|
235
|
+
try {
|
|
236
|
+
this.sendHandoff(summary, options);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// Log but don't throw
|
|
239
|
+
logInternalError("hidden-handoff.async", error, `taskId=${summary.taskId} runId=${summary.runId}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Infer priority based on summary outcome.
|
|
245
|
+
*/
|
|
246
|
+
private inferPriority(summary: HandoffSummary): HandoffPriority {
|
|
247
|
+
if (summary.outcome === "failure") {
|
|
248
|
+
return "high";
|
|
249
|
+
}
|
|
250
|
+
if (summary.blockers.length > 0) {
|
|
251
|
+
return "normal";
|
|
252
|
+
}
|
|
253
|
+
if (summary.metrics.tokensUsed > 10000) {
|
|
254
|
+
return "normal";
|
|
255
|
+
}
|
|
256
|
+
return "low";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Build handoff content from summary.
|
|
261
|
+
*/
|
|
262
|
+
private buildContent(summary: HandoffSummary): HandoffContent {
|
|
263
|
+
return {
|
|
264
|
+
summary: this.buildSummaryText(summary),
|
|
265
|
+
files: {
|
|
266
|
+
created: summary.filesCreated,
|
|
267
|
+
modified: summary.filesModified,
|
|
268
|
+
deleted: summary.filesDeleted,
|
|
269
|
+
},
|
|
270
|
+
decisions: summary.decisions.map((d) => ({
|
|
271
|
+
rationale: d.rationale,
|
|
272
|
+
outcome: d.outcome,
|
|
273
|
+
})),
|
|
274
|
+
nextSteps: summary.nextSteps,
|
|
275
|
+
metrics: {
|
|
276
|
+
tokens: summary.metrics.tokensUsed,
|
|
277
|
+
duration: summary.metrics.duration,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Build summary text from summary.
|
|
284
|
+
*/
|
|
285
|
+
private buildSummaryText(summary: HandoffSummary): string {
|
|
286
|
+
const parts: string[] = [`Completed: ${summary.task}`, `Outcome: ${summary.outcome}`];
|
|
287
|
+
|
|
288
|
+
if (summary.filesCreated.length > 0) {
|
|
289
|
+
parts.push(`Files created: ${summary.filesCreated.join(", ")}`);
|
|
290
|
+
}
|
|
291
|
+
if (summary.filesModified.length > 0) {
|
|
292
|
+
parts.push(`Files modified: ${summary.filesModified.join(", ")}`);
|
|
293
|
+
}
|
|
294
|
+
if (summary.decisions.length > 0) {
|
|
295
|
+
parts.push(`Decisions: ${summary.decisions.length}`);
|
|
296
|
+
}
|
|
297
|
+
if (summary.blockers.length > 0) {
|
|
298
|
+
parts.push(`Blockers: ${summary.blockers.join("; ")}`);
|
|
299
|
+
}
|
|
300
|
+
if (summary.nextSteps.length > 0) {
|
|
301
|
+
parts.push(`Next steps: ${summary.nextSteps.join("; ")}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
parts.push(`Tokens: ${summary.metrics.tokensUsed}`, `Duration: ${Math.round(summary.metrics.duration / 1000)}s`);
|
|
305
|
+
|
|
306
|
+
return parts.join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get parent agent ID from callback or context.
|
|
311
|
+
*/
|
|
312
|
+
private getParentAgentId(): string | undefined {
|
|
313
|
+
if (this.getParentAgentIdFn) {
|
|
314
|
+
return this.getParentAgentIdFn();
|
|
315
|
+
}
|
|
316
|
+
// Fallback: try to get from global context
|
|
317
|
+
const ctx = (globalThis as Record<string, unknown>).__piCrewContext;
|
|
318
|
+
if (ctx && typeof ctx === "object") {
|
|
319
|
+
return (ctx as Record<string, unknown>).parentAgentId as string | undefined;
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* C7: Validate recipient is a reasonable agent ID.
|
|
326
|
+
*/
|
|
327
|
+
private isValidRecipient(recipient: string): boolean {
|
|
328
|
+
if (!recipient || typeof recipient !== "string") {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
// Reasonable length for an agent ID
|
|
332
|
+
if (recipient.length < 1 || recipient.length > 256) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
// Only allow alphanumeric, hyphen, underscore, colon, and period
|
|
336
|
+
// This prevents injection in mailbox routing
|
|
337
|
+
if (!/^[a-zA-Z0-9_:.-]+$/.test(recipient)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* C7: Check if recipient is rate limited.
|
|
345
|
+
* Also cleans up empty recipient entries to prevent unbounded Map growth.
|
|
346
|
+
*/
|
|
347
|
+
private isRateLimited(recipient: string): boolean {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const timestamps = this.sendTimestamps.get(recipient) ?? [];
|
|
350
|
+
|
|
351
|
+
// Filter out old timestamps outside the window
|
|
352
|
+
const recentTimestamps = timestamps.filter((t) => now - t < this.RATE_LIMIT_WINDOW_MS);
|
|
353
|
+
|
|
354
|
+
// HIGH-11: If no recent timestamps, remove the empty key to prevent unbounded growth
|
|
355
|
+
if (recentTimestamps.length === 0) {
|
|
356
|
+
this.sendTimestamps.delete(recipient);
|
|
357
|
+
} else {
|
|
358
|
+
this.sendTimestamps.set(recipient, recentTimestamps);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return recentTimestamps.length >= this.RATE_LIMIT_MAX_SENDS;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* C7: Record a send for rate limiting.
|
|
366
|
+
*/
|
|
367
|
+
private recordSend(recipient: string): void {
|
|
368
|
+
const now = Date.now();
|
|
369
|
+
const timestamps = this.sendTimestamps.get(recipient) ?? [];
|
|
370
|
+
timestamps.push(now);
|
|
371
|
+
|
|
372
|
+
// MEDIUM-14: Use RATE_LIMIT_WINDOW_MS consistently for both filter and record
|
|
373
|
+
const recentTimestamps = timestamps.filter((t) => now - t < this.RATE_LIMIT_WINDOW_MS);
|
|
374
|
+
|
|
375
|
+
this.sendTimestamps.set(recipient, recentTimestamps);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Dispose of resources.
|
|
380
|
+
* Call this when the service is no longer needed.
|
|
381
|
+
*/
|
|
382
|
+
dispose(): void {
|
|
383
|
+
this.mailbox = null;
|
|
384
|
+
this.eventEmitter = null;
|
|
385
|
+
this.getParentAgentIdFn = null;
|
|
386
|
+
this.sendTimestamps.clear();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Options for sending hidden handoffs.
|
|
392
|
+
*/
|
|
393
|
+
export interface SendHandoffOptions {
|
|
394
|
+
/** Recipient agent ID (defaults to parent) */
|
|
395
|
+
to?: string;
|
|
396
|
+
/** Priority level */
|
|
397
|
+
priority?: HandoffPriority;
|
|
398
|
+
/** Custom handoff type */
|
|
399
|
+
customType?: HiddenHandoffType;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Create a HiddenHandoffService with default options.
|
|
404
|
+
*/
|
|
405
|
+
export function createHiddenHandoffService(options?: HiddenHandoffServiceOptions): HiddenHandoffService {
|
|
406
|
+
return new HiddenHandoffService(options);
|
|
407
|
+
}
|