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,1013 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dynamic-workflow-context.ts — WorkflowCtx facade for dynamic-workflow scripts (P2).
|
|
3
|
+
*
|
|
4
|
+
* Spec: research-findings/goal-workflow/00-SPEC.md §3.2
|
|
5
|
+
* Plan: 07-PLAN.md v3 P2 + §0b G4 + §0c C4/C5/C7.
|
|
6
|
+
*
|
|
7
|
+
* The `ctx` object passed to a `.dwf.ts` script's `export default async function(ctx)`.
|
|
8
|
+
* Capability-locked: exposes ONLY the documented methods (no raw manifest/process/require).
|
|
9
|
+
* The script host (dynamic-workflow-runner.ts) loads the script via jiti in plain module
|
|
10
|
+
* scope with a FROZEN WorkflowCtx. v1 has NO vm sandbox (review H-2): the script CAN
|
|
11
|
+
* reach `process`/`require`/`import` directly — the frozen ctx is a contract surface,
|
|
12
|
+
* not a security boundary. `.dwf.ts` = postinstall-equivalent trust. isolated-vm v1.5.
|
|
13
|
+
*
|
|
14
|
+
* `agent()` resolution (§0b G4): 4-tier precedence
|
|
15
|
+
* 1. opts.agent (explicit name) — bypasses team lookup
|
|
16
|
+
* 2. team.roles.find(r => r.name === role)?.agent → allAgents lookup
|
|
17
|
+
* 3. allAgents(discoverAgents(cwd)).find(a => a.name === role) (role name == agent name)
|
|
18
|
+
* 4. synthesize minimal AgentConfig (source:"dynamic", systemPrompt:"You are {role}.")
|
|
19
|
+
*
|
|
20
|
+
* Isolation (§0b G3 / report 05 §C.4): worker output → artifact file; `agent()` returns
|
|
21
|
+
* structured data + writes a side artifact. The script holds results in JS vars; only
|
|
22
|
+
* `setResult()` reaches the main context.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { randomBytes } from "node:crypto";
|
|
26
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
27
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
28
|
+
import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
|
|
29
|
+
import { writeArtifact } from "../state/artifact-store.ts";
|
|
30
|
+
import { appendEvent } from "../state/event-log.ts";
|
|
31
|
+
import { appendMailboxMessage, readMailbox } from "../state/mailbox.ts";
|
|
32
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
33
|
+
import type { TeamConfig } from "../teams/team-config.ts";
|
|
34
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
35
|
+
import { cleanupAgentWorktreeAsync, prepareAgentWorktreeAsync } from "../worktree/worktree-manager.ts";
|
|
36
|
+
import type { DwfCheckpointState } from "./dwf-state-store.ts";
|
|
37
|
+
import { mapConcurrent } from "./parallel-utils.ts";
|
|
38
|
+
import { parsePiJsonOutput } from "./pi-json-output.ts";
|
|
39
|
+
import { renderPlanTemplate } from "./plan-templates.ts";
|
|
40
|
+
import { extractStructuredResult } from "./result-extractor.ts";
|
|
41
|
+
import { executeWithRetry } from "./retry-executor.ts";
|
|
42
|
+
import { runWorker } from "./run-worker.ts";
|
|
43
|
+
import { Semaphore } from "./semaphore.ts";
|
|
44
|
+
|
|
45
|
+
export interface AgentCallOpts {
|
|
46
|
+
prompt: string;
|
|
47
|
+
/** Role name (resolved via G4 4-tier chain) OR explicit agent name. */
|
|
48
|
+
role?: string;
|
|
49
|
+
/** Explicit agent name — bypasses team-role lookup (tier 1). */
|
|
50
|
+
agent?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
model?: string;
|
|
53
|
+
skill?: string[] | false;
|
|
54
|
+
maxTurns?: number;
|
|
55
|
+
graceTurns?: number;
|
|
56
|
+
/** Dependency artifact paths injected into the agent prompt. */
|
|
57
|
+
inputs?: string[];
|
|
58
|
+
/** Disable ALL tools for this call (Pi `--no-tools`, §0c C6). Use for pure-judgment /
|
|
59
|
+
* verdict steps where the agent must answer directly without exploring, e.g.
|
|
60
|
+
* `ctx.review()`'s JSON-verdict call. Without this, role-based tools (read/grep/bash)
|
|
61
|
+
* apply and the model may loop exploring instead of answering. */
|
|
62
|
+
disableTools?: boolean;
|
|
63
|
+
/** Override the resolved agent's system prompt. Use when the call needs a different
|
|
64
|
+
* persona/output-format than the role's defined agent — e.g. `ctx.review()` needs a
|
|
65
|
+
* JSON-verdict judge, but the user's reviewer.md agent is a markdown code-reviewer.
|
|
66
|
+
* When set, the resolved agent's systemPrompt is replaced entirely. */
|
|
67
|
+
systemPrompt?: string;
|
|
68
|
+
/** Round-13 P0-3: optional TypeBox schema. When set, the call's output is validated
|
|
69
|
+
* against the schema after extraction. Validation failure yields ok:false with a
|
|
70
|
+
* structured `error` and undefined `structured` field. Forward-compatible: when
|
|
71
|
+
* undefined, behavior is identical to the regex-based extractor. */
|
|
72
|
+
schema?: TSchema;
|
|
73
|
+
/** round-17 P2-4: spawn this agent in an isolated git worktree.
|
|
74
|
+
* Useful when parallel agents modify files concurrently (avoids conflicts). The
|
|
75
|
+
* worktree is created from HEAD, the agent runs there, and on completion the
|
|
76
|
+
* diff is captured as an artifact before cleanup. Default false.
|
|
77
|
+
* If worktree creation fails (no git repo, dirty leader), the agent runs in the
|
|
78
|
+
* normal cwd and a warning is logged via ctx.log(). Backward compatible —
|
|
79
|
+
* omitting it is identical to `false`. */
|
|
80
|
+
worktree?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface AgentResult {
|
|
84
|
+
ok: boolean;
|
|
85
|
+
text: string;
|
|
86
|
+
structured?: unknown;
|
|
87
|
+
usage?: { input?: number; output?: number; cost?: number; turns?: number };
|
|
88
|
+
runId?: string;
|
|
89
|
+
taskId?: string;
|
|
90
|
+
artifactPath?: string;
|
|
91
|
+
error?: string;
|
|
92
|
+
durationMs?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** round-14 P1-2: per-workflow token budget. Frozen read-only surface exposed as ctx.budget. */
|
|
96
|
+
export interface WorkflowBudget {
|
|
97
|
+
/** Configured budget, or null when unbounded. */
|
|
98
|
+
total: number | null;
|
|
99
|
+
/** Tokens consumed so far (accumulated from each ctx.agent() run's usage). */
|
|
100
|
+
spent(): number;
|
|
101
|
+
/** Tokens remaining; Infinity when total is null. */
|
|
102
|
+
remaining(): number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface WorkflowCtx {
|
|
106
|
+
cwd: string;
|
|
107
|
+
runId: string;
|
|
108
|
+
goal?: string;
|
|
109
|
+
/** Spawn one agent, await result. Concurrency enforced by ctx.semaphore. */
|
|
110
|
+
agent(opts: AgentCallOpts): Promise<AgentResult>;
|
|
111
|
+
/** Bounded fan-out preserving order (wraps mapConcurrent). */
|
|
112
|
+
fanOut<T>(items: T[], limit: number, fn: (item: T, i: number) => Promise<AgentResult>): Promise<AgentResult[]>;
|
|
113
|
+
/** Pipeline: sequential per-item stages, parallel across items (bounded by
|
|
114
|
+
* ctx.semaphore). Each item passes through all stages in order; different
|
|
115
|
+
* items may run concurrently. A failed stage yields `null` for that item
|
|
116
|
+
* (logged via ctx.log) and other items continue. Aborts propagate.
|
|
117
|
+
* round-16 (P2-1). */
|
|
118
|
+
pipeline<TItem, TResult = unknown>(
|
|
119
|
+
items: TItem[],
|
|
120
|
+
...stages: Array<(previous: TResult, original: TItem, index: number) => Promise<TResult> | TResult>
|
|
121
|
+
): Promise<(TResult | null)[]>;
|
|
122
|
+
/** Run a reviewer agent over an artifact; parse {outcome, feedback}. §3.2. */
|
|
123
|
+
review(
|
|
124
|
+
taskId: string,
|
|
125
|
+
reviewerRole?: string,
|
|
126
|
+
opts?: {
|
|
127
|
+
content?: string;
|
|
128
|
+
artifactPath?: string;
|
|
129
|
+
disableTools?: boolean;
|
|
130
|
+
},
|
|
131
|
+
): Promise<{
|
|
132
|
+
outcome: "accept" | "reject" | "changes_requested";
|
|
133
|
+
feedback: string;
|
|
134
|
+
}>;
|
|
135
|
+
/** Re-run a task with feedback (wraps executeWithRetry). */
|
|
136
|
+
retry(taskId: string, opts?: { feedback?: string }): Promise<AgentResult>;
|
|
137
|
+
/** Send a mailbox message to another agent/leader. */
|
|
138
|
+
mail(
|
|
139
|
+
to: string,
|
|
140
|
+
body: string,
|
|
141
|
+
opts?: {
|
|
142
|
+
kind?: string;
|
|
143
|
+
taskId?: string;
|
|
144
|
+
replyTo?: string;
|
|
145
|
+
replyDeadline?: number;
|
|
146
|
+
},
|
|
147
|
+
): string;
|
|
148
|
+
/** Block until N mailbox replies arrive or deadline. ~10 LOC net-new (report 05 §G.4). */
|
|
149
|
+
gatherReplies(messageIds: string[], deadlineMs: number): Promise<unknown[]>;
|
|
150
|
+
/** Render a built-in plan template (full-implementation / standard-review). */
|
|
151
|
+
renderTemplate(name: string, vars: Record<string, string>): unknown;
|
|
152
|
+
/** Persistent variables (revived intermediate-store). */
|
|
153
|
+
vars: Record<string, unknown>;
|
|
154
|
+
/** Mark the final result. ONLY this artifact reaches the main context. */
|
|
155
|
+
setResult(artifactPath: string, meta?: Record<string, unknown>): void;
|
|
156
|
+
/** Mark the start of a named workflow phase. Emits a `dwf.phase_started` event
|
|
157
|
+
* (and a `dwf.phase_completed` for the previous phase, if any) to the run's
|
|
158
|
+
* events.jsonl. Idempotent on the same title — calling twice with the same
|
|
159
|
+
* title is a no-op. Phase titles are in-memory only; the events log is the
|
|
160
|
+
* durable source of truth for phase boundaries. */
|
|
161
|
+
phase(title: string): void;
|
|
162
|
+
/** round-14 P1-3: append a workflow-level log line. Persists to events.jsonl
|
|
163
|
+
* as a `dwf.log` event and keeps a bounded in-memory copy (capped at 1000). */
|
|
164
|
+
log(message: unknown): void;
|
|
165
|
+
/** round-14 P1-2: per-workflow token budget. ctx.agent() auto-rejects with
|
|
166
|
+
* ok:false once exhausted. */
|
|
167
|
+
budget: WorkflowBudget;
|
|
168
|
+
/** round-14 P1-5: typed workflow arguments. Reads the value passed via
|
|
169
|
+
* MakeWorkflowCtxOptions.args (sourced from manifest.args). Defaults to {}
|
|
170
|
+
* when unset. */
|
|
171
|
+
args<T = unknown>(): T;
|
|
172
|
+
semaphore: Semaphore;
|
|
173
|
+
/** Abort signal (cancel/stop). */
|
|
174
|
+
signal: AbortSignal;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface MakeWorkflowCtxOptions {
|
|
178
|
+
concurrency?: number;
|
|
179
|
+
signal: AbortSignal;
|
|
180
|
+
team?: TeamConfig;
|
|
181
|
+
modelOverride?: string;
|
|
182
|
+
/** round-14 P1-2: per-workflow token budget. null/undefined = unbounded. */
|
|
183
|
+
tokenBudget?: number | null;
|
|
184
|
+
/** round-14 P1-5: typed workflow arguments (sourced from manifest.args). Defaults to {}. */
|
|
185
|
+
args?: unknown;
|
|
186
|
+
/** round-18 P2-3: checkpoint state to hydrate ctx with on resume. When provided,
|
|
187
|
+
* the ctx starts with the resumed vars/phases/logs/spent/agentCount instead of
|
|
188
|
+
* empty defaults. Omit (or undefined) for a fresh run — backward compatible. */
|
|
189
|
+
resumedState?: DwfCheckpointState;
|
|
190
|
+
/** round-18 P2-3: callback invoked after each `ctx.agent()` call completes
|
|
191
|
+
* (success OR fail). The runner wires this to `DwfStore.save()` so a crash after
|
|
192
|
+
* an agent call leaves a durable checkpoint. Best-effort — failures are swallowed
|
|
193
|
+
* so checkpointing can never crash the workflow. */
|
|
194
|
+
onCheckpoint?: (state: DwfCheckpointState) => void;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolve a role/agent name to a full AgentConfig (§0b G4 4-tier precedence).
|
|
199
|
+
* Module-local — NOT promoted to a shared module (keeps P2 isolated from the
|
|
200
|
+
* load-bearing team-runner path).
|
|
201
|
+
*/
|
|
202
|
+
export function resolveAgentForRole(
|
|
203
|
+
roleName: string | undefined,
|
|
204
|
+
opts: { explicitAgent?: string; team?: TeamConfig; cwd: string },
|
|
205
|
+
): AgentConfig {
|
|
206
|
+
const cwd = opts.cwd;
|
|
207
|
+
// Tier 1: explicit agent name.
|
|
208
|
+
if (opts.explicitAgent) {
|
|
209
|
+
const found = allAgents(discoverAgents(cwd)).find((a) => a.name === opts.explicitAgent);
|
|
210
|
+
if (found) return found;
|
|
211
|
+
// Fall through to synthesize if the named agent doesn't exist (P2-friendly).
|
|
212
|
+
}
|
|
213
|
+
// Tier 2: team.roles[].agent lookup.
|
|
214
|
+
if (opts.team) {
|
|
215
|
+
const role = opts.team.roles.find((r) => r.name === roleName);
|
|
216
|
+
if (role) {
|
|
217
|
+
const byAgentName = allAgents(discoverAgents(cwd)).find((a) => a.name === role.agent);
|
|
218
|
+
if (byAgentName) return byAgentName;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Tier 3: discoverAgents by role name (role name == agent name).
|
|
222
|
+
if (roleName) {
|
|
223
|
+
const byRoleName = allAgents(discoverAgents(cwd)).find((a) => a.name === roleName);
|
|
224
|
+
if (byRoleName) return byRoleName;
|
|
225
|
+
}
|
|
226
|
+
// Tier 4: synthesize a minimal AgentConfig.
|
|
227
|
+
const name = opts.explicitAgent ?? roleName ?? "executor";
|
|
228
|
+
return synthesizeAgentConfig(name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Synthesize a minimal AgentConfig (§0c C7: source:"dynamic", not "synthetic"). */
|
|
232
|
+
export function synthesizeAgentConfig(name: string, model?: string): AgentConfig {
|
|
233
|
+
return {
|
|
234
|
+
name,
|
|
235
|
+
description: `Synthesized agent for dynamic workflow (${name}).`,
|
|
236
|
+
source: "dynamic",
|
|
237
|
+
filePath: `<dynamic-workflow>`,
|
|
238
|
+
systemPrompt: `You are ${name}, an agent in a dynamic pi-crew workflow. Use the provided tools (read, grep, find, ls, bash) to investigate the target and produce concrete written findings. Do not return an empty response — always output substantive content for your task.`,
|
|
239
|
+
model,
|
|
240
|
+
// Round-N fix: give synthesized agents the STANDARD file-investigation toolkit
|
|
241
|
+
// (matches real built-in agents). Previously tools:[] relied on pi-args default
|
|
242
|
+
// behavior, which left most synthesized agents (explorer/analyst/critic/executor)
|
|
243
|
+
// unable to read the codebase → 10/11 agents returned empty/!ok in distill-dwf runs.
|
|
244
|
+
tools: ["read", "grep", "find", "ls", "bash"],
|
|
245
|
+
inheritProjectContext: false,
|
|
246
|
+
inheritSkills: false,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Build the WorkflowCtx facade. Capability-locked: only documented methods exposed. */
|
|
251
|
+
export function makeWorkflowCtx(manifest: TeamRunManifest, opts: MakeWorkflowCtxOptions): WorkflowCtx {
|
|
252
|
+
const concurrency = Math.max(1, opts.concurrency ?? 4);
|
|
253
|
+
const semaphore = new Semaphore(concurrency);
|
|
254
|
+
let finalResult: { artifactPath: string; meta?: Record<string, unknown> } | undefined;
|
|
255
|
+
// round-18 P2-3: agent invocation counter. Hydrated from a resumed checkpoint so a
|
|
256
|
+
// resumed run keeps an accurate count; incremented in agent()'s finally block.
|
|
257
|
+
let agentCount = opts.resumedState ? opts.resumedState.agentCount : 0;
|
|
258
|
+
// round-12 P0-1: in-memory phase state, exposed via non-enumerable getter like __finalResult.
|
|
259
|
+
// The events log is the durable source of truth for phase boundaries.
|
|
260
|
+
// round-18 P2-3: hydrate phaseState from a resumed checkpoint (backward compatible when unset).
|
|
261
|
+
const phaseState: { currentPhase: string | undefined; phases: string[] } = opts.resumedState
|
|
262
|
+
? {
|
|
263
|
+
currentPhase: opts.resumedState.currentPhase,
|
|
264
|
+
phases: [...opts.resumedState.phases],
|
|
265
|
+
}
|
|
266
|
+
: { currentPhase: undefined, phases: [] };
|
|
267
|
+
let phaseCapWarned = false;
|
|
268
|
+
// round-14 P1-2/P1-3/P1-5: closure-scoped runtime state shared by budget/log/args.
|
|
269
|
+
// Mirrors the pi-dynamic-workflows RuntimeState pattern (workflow.ts:state).
|
|
270
|
+
// round-18 P2-3: hydrate spent/logs from a resumed checkpoint (backward compatible when unset).
|
|
271
|
+
const wfState: { spent: number; logs: string[]; args: unknown } = {
|
|
272
|
+
spent: opts.resumedState?.spent ?? 0,
|
|
273
|
+
logs: opts.resumedState ? [...opts.resumedState.logs].slice(0, 1000) : [],
|
|
274
|
+
args: opts.args ?? {},
|
|
275
|
+
};
|
|
276
|
+
// PERS-1: per-agent-call idempotency cache. Hydrated from resumedState so a DWF
|
|
277
|
+
// resume skips already-completed agent calls (avoids duplicating artifacts/mailbox/tokens).
|
|
278
|
+
// Uses Record (not Map) for JSON serialization safety.
|
|
279
|
+
const completedAgentCalls: Record<string, { text: string; usage?: { input: number; output: number } }> =
|
|
280
|
+
opts.resumedState?.completedAgentCalls ?? {};
|
|
281
|
+
// round-14 P1-2: frozen budget surface. The closures read wfState.spent so the
|
|
282
|
+
// object stays live after Object.freeze(ctx). total is a snapshot primitive.
|
|
283
|
+
const budget = Object.freeze({
|
|
284
|
+
total: opts.tokenBudget ?? null,
|
|
285
|
+
spent: () => wfState.spent,
|
|
286
|
+
remaining: () => (opts.tokenBudget == null ? Infinity : Math.max(0, opts.tokenBudget - wfState.spent)),
|
|
287
|
+
} satisfies WorkflowBudget);
|
|
288
|
+
|
|
289
|
+
const ctx: WorkflowCtx = {
|
|
290
|
+
cwd: manifest.cwd,
|
|
291
|
+
runId: manifest.runId,
|
|
292
|
+
goal: manifest.goal,
|
|
293
|
+
signal: opts.signal,
|
|
294
|
+
semaphore,
|
|
295
|
+
async agent(call: AgentCallOpts): Promise<AgentResult> {
|
|
296
|
+
await semaphore.acquire();
|
|
297
|
+
const started = Date.now();
|
|
298
|
+
// round-17 P2-4: declared before the try so the finally can clean it up
|
|
299
|
+
// regardless of which return/throw path is taken.
|
|
300
|
+
let worktreePath: string | undefined;
|
|
301
|
+
let worktreeBranch: string | undefined;
|
|
302
|
+
// BDG-2: declared before the try so the catch block can un-reserve on failure.
|
|
303
|
+
const ESTIMATE = 4096;
|
|
304
|
+
let reserved = false;
|
|
305
|
+
try {
|
|
306
|
+
// PERS-1: per-agent-call idempotency. Compute a deterministic call ID
|
|
307
|
+
// from the call arguments and check if this call was already completed
|
|
308
|
+
// (e.g. during a previous run before a crash). If so, return the cached
|
|
309
|
+
// result without re-spawning the agent.
|
|
310
|
+
const callId = JSON.stringify({
|
|
311
|
+
role: call.role,
|
|
312
|
+
agent: call.agent,
|
|
313
|
+
prompt: call.prompt,
|
|
314
|
+
model: call.model,
|
|
315
|
+
schema: call.schema ? "yes" : "no",
|
|
316
|
+
});
|
|
317
|
+
const cached = completedAgentCalls[callId];
|
|
318
|
+
if (cached) {
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
text: cached.text,
|
|
322
|
+
usage: cached.usage,
|
|
323
|
+
durationMs: 0,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// BDG-2: reserve-then-adjust budget. Before spawning, estimate the cost and
|
|
328
|
+
// reserve it by adding to wfState.spent. This prevents N concurrent calls from
|
|
329
|
+
// all seeing the same remaining budget and overspending.
|
|
330
|
+
if (budget.total !== null && budget.remaining() < ESTIMATE) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
text: "",
|
|
334
|
+
error: "workflow token budget exhausted",
|
|
335
|
+
durationMs: 0,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Reserve the estimate before spawning.
|
|
339
|
+
wfState.spent += ESTIMATE;
|
|
340
|
+
reserved = true;
|
|
341
|
+
|
|
342
|
+
const agentConfig = resolveAgentForRole(call.role, {
|
|
343
|
+
explicitAgent: call.agent,
|
|
344
|
+
team: opts.team,
|
|
345
|
+
cwd: manifest.cwd,
|
|
346
|
+
});
|
|
347
|
+
// §0c C6: per-call disableTools override. When set, force Pi `--no-tools` so the
|
|
348
|
+
// agent answers directly without exploring. Applied AFTER role resolution so it
|
|
349
|
+
// wins over any role-defined tools.
|
|
350
|
+
let effectiveAgent = call.disableTools === true ? { ...agentConfig, disableTools: true, tools: [] } : agentConfig;
|
|
351
|
+
// Per-call systemPrompt override (replaces the resolved agent's persona/output-format).
|
|
352
|
+
// Used by ctx.review() to force a JSON-verdict judge instead of the role's markdown reviewer.
|
|
353
|
+
// Round-13 P0-3: when a schema is provided, append a JSON-output instruction so
|
|
354
|
+
// the model returns parseable JSON instead of prose. Schema name is intentionally
|
|
355
|
+
// generic — we don't reveal TypeBox internal types.
|
|
356
|
+
//
|
|
357
|
+
// Smoke-test fix: when BOTH schema AND an explicit call.systemPrompt are set,
|
|
358
|
+
// the call.systemPrompt is the caller's intended persona (e.g. a JSON-verdict
|
|
359
|
+
// judge). It MUST be used as the base for the JSON instruction — otherwise the
|
|
360
|
+
// role's persona leaks through and the model returns prose, failing schema
|
|
361
|
+
// validation. Previously call.systemPrompt was silently dropped when a schema
|
|
362
|
+
// was present, which confused models into returning text like "hello".
|
|
363
|
+
if (call.schema !== undefined) {
|
|
364
|
+
const base = call.systemPrompt ?? effectiveAgent.systemPrompt;
|
|
365
|
+
effectiveAgent = {
|
|
366
|
+
...effectiveAgent,
|
|
367
|
+
systemPrompt: composeSchemaSystemPrompt(base, call.schema),
|
|
368
|
+
};
|
|
369
|
+
} else if (call.systemPrompt !== undefined) {
|
|
370
|
+
effectiveAgent = {
|
|
371
|
+
...effectiveAgent,
|
|
372
|
+
systemPrompt: call.systemPrompt,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const task = composeAgentTask(call);
|
|
376
|
+
|
|
377
|
+
// round-17 P2-4: worktree isolation per agent. When requested, spawn the
|
|
378
|
+
// agent in an isolated git worktree so parallel file-modifying agents
|
|
379
|
+
// don't clobber each other. Falls back to the normal cwd (with a warning)
|
|
380
|
+
// when worktree creation is unavailable (no git repo, dirty leader).
|
|
381
|
+
let agentCwd = manifest.cwd;
|
|
382
|
+
if (call.worktree === true) {
|
|
383
|
+
const wt = await prepareAgentWorktreeAsync(manifest, `dwf-agent-${Date.now()}-${randomBytes(4).toString("hex")}`);
|
|
384
|
+
if (wt?.worktreePath) {
|
|
385
|
+
agentCwd = wt.cwd;
|
|
386
|
+
worktreePath = wt.worktreePath;
|
|
387
|
+
worktreeBranch = wt.branch;
|
|
388
|
+
ctx.log(`worktree: agent isolated at ${wt.worktreePath}`);
|
|
389
|
+
} else {
|
|
390
|
+
ctx.log("worktree: creation unavailable — falling back to normal cwd");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const childResult = await runWorker({
|
|
395
|
+
cwd: agentCwd,
|
|
396
|
+
task,
|
|
397
|
+
agent: effectiveAgent,
|
|
398
|
+
model: call.model ?? opts.modelOverride ?? agentConfig.model,
|
|
399
|
+
skillPaths: undefined, // skills resolved via agent config + team-role plumbing
|
|
400
|
+
maxTurns: call.maxTurns,
|
|
401
|
+
graceTurns: call.graceTurns,
|
|
402
|
+
signal: opts.signal,
|
|
403
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
404
|
+
runId: manifest.runId,
|
|
405
|
+
role: call.role ?? call.agent,
|
|
406
|
+
});
|
|
407
|
+
if (childResult.exitCode !== 0 || childResult.error) {
|
|
408
|
+
// BDG-2: un-reserve the estimate on spawn failure.
|
|
409
|
+
wfState.spent -= ESTIMATE;
|
|
410
|
+
reserved = false;
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
text: "",
|
|
414
|
+
error: childResult.error ?? `exit ${childResult.exitCode}`,
|
|
415
|
+
durationMs: Date.now() - started,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const parsed = parsePiJsonOutput(childResult.stdout);
|
|
419
|
+
// round-14 P1-2: accumulate this run's token usage into the workflow budget.
|
|
420
|
+
// BDG-2: adjust the reserve — subtract the estimate, add the actual usage.
|
|
421
|
+
// This correctly reduces spent when actualUsage < ESTIMATE.
|
|
422
|
+
wfState.spent += (parsed.usage?.input ?? 0) + (parsed.usage?.output ?? 0) - ESTIMATE;
|
|
423
|
+
reserved = false;
|
|
424
|
+
let text = childResult.rawFinalText || parsed.finalText || "";
|
|
425
|
+
// Round-11 test fix: parsePiJsonOutput only extracts text from pi event stream
|
|
426
|
+
// ({type:"message_end", message:{role:"assistant", content:[...]}}). When the
|
|
427
|
+
// agent emits plain JSON, plain text, or a different format, finalText is empty.
|
|
428
|
+
// Fallback to a more permissive extraction that handles multiple output shapes.
|
|
429
|
+
if (!text.trim()) {
|
|
430
|
+
text = extractTextFallback(childResult.stdout);
|
|
431
|
+
}
|
|
432
|
+
// Round-13 P0-3: schema validation post-extraction. The schema option is
|
|
433
|
+
// additive — when undefined the call site is unchanged. With a schema,
|
|
434
|
+
// extracted.error means the worker output didn't match expected shape and
|
|
435
|
+
// the script should treat the result as failed (ok:false, error set).
|
|
436
|
+
const extracted = extractStructuredResult(text, call.schema);
|
|
437
|
+
// Write a side artifact for audit/isolation (§0b G3).
|
|
438
|
+
const rel = `wf/${Date.now()}-${randomBytes(4).toString("hex")}.md`;
|
|
439
|
+
const artifact = writeArtifact(manifest.artifactsRoot, {
|
|
440
|
+
kind: "result",
|
|
441
|
+
relativePath: rel,
|
|
442
|
+
content: text,
|
|
443
|
+
producer: "dynamic-workflow",
|
|
444
|
+
});
|
|
445
|
+
if (call.schema !== undefined && !extracted.structured) {
|
|
446
|
+
// BDG-2: un-reserve was already done above (reserved = false after adjust).
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
text,
|
|
450
|
+
usage: parsed.usage,
|
|
451
|
+
artifactPath: artifact.path,
|
|
452
|
+
error: extracted.error ?? "structured output does not match schema",
|
|
453
|
+
durationMs: Date.now() - started,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
// PERS-1: cache the successful result for idempotency on resume.
|
|
457
|
+
completedAgentCalls[callId] = {
|
|
458
|
+
text,
|
|
459
|
+
usage: { input: parsed.usage?.input ?? 0, output: parsed.usage?.output ?? 0 },
|
|
460
|
+
};
|
|
461
|
+
return {
|
|
462
|
+
ok: true,
|
|
463
|
+
text,
|
|
464
|
+
structured: extracted.structured ? extracted.data : undefined,
|
|
465
|
+
usage: parsed.usage,
|
|
466
|
+
artifactPath: artifact.path,
|
|
467
|
+
durationMs: Date.now() - started,
|
|
468
|
+
};
|
|
469
|
+
} catch (error) {
|
|
470
|
+
// BDG-2: un-reserve the estimate on failure (only if still reserved).
|
|
471
|
+
if (reserved) wfState.spent -= ESTIMATE;
|
|
472
|
+
logInternalError("dynamic-workflow-context.agent", error, `runId=${manifest.runId}`);
|
|
473
|
+
return {
|
|
474
|
+
ok: false,
|
|
475
|
+
text: "",
|
|
476
|
+
error: error instanceof Error ? error.message : String(error),
|
|
477
|
+
durationMs: Date.now() - started,
|
|
478
|
+
};
|
|
479
|
+
} finally {
|
|
480
|
+
// round-17 P2-4: clean up the worktree after the agent completes (success
|
|
481
|
+
// OR failure). Captures the diff as an artifact before removal. Best-effort
|
|
482
|
+
// — a leak must never crash the workflow.
|
|
483
|
+
if (worktreePath) {
|
|
484
|
+
try {
|
|
485
|
+
await cleanupAgentWorktreeAsync(manifest, worktreePath, worktreeBranch);
|
|
486
|
+
} catch (cleanupError) {
|
|
487
|
+
logInternalError("dynamic-workflow-context.worktree-cleanup", cleanupError, `worktreePath=${worktreePath}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// round-18 P2-3: checkpoint AFTER the agent completes (success or fail) so a
|
|
491
|
+
// crash between agent calls leaves durable state to resume from. The counter is
|
|
492
|
+
// incremented here (after the call) so the checkpoint reflects the call that ran.
|
|
493
|
+
agentCount++;
|
|
494
|
+
if (opts.onCheckpoint) {
|
|
495
|
+
try {
|
|
496
|
+
opts.onCheckpoint({
|
|
497
|
+
runId: manifest.runId,
|
|
498
|
+
vars: ctx.vars,
|
|
499
|
+
phases: phaseState.phases,
|
|
500
|
+
currentPhase: phaseState.currentPhase,
|
|
501
|
+
logs: wfState.logs.slice(0, 1000),
|
|
502
|
+
spent: wfState.spent,
|
|
503
|
+
agentCount,
|
|
504
|
+
completedAgentCalls,
|
|
505
|
+
updatedAt: new Date().toISOString(),
|
|
506
|
+
});
|
|
507
|
+
} catch (checkpointError) {
|
|
508
|
+
logInternalError("dynamic-workflow-context.checkpoint", checkpointError, `runId=${manifest.runId}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
semaphore.release();
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
async fanOut<T>(items: T[], limit: number, fn: (item: T, i: number) => Promise<AgentResult>): Promise<AgentResult[]> {
|
|
515
|
+
return mapConcurrent(items, Math.max(1, limit), fn);
|
|
516
|
+
},
|
|
517
|
+
async pipeline<TItem, TResult = unknown>(
|
|
518
|
+
items: TItem[],
|
|
519
|
+
...stages: Array<(previous: TResult, original: TItem, index: number) => Promise<TResult> | TResult>
|
|
520
|
+
): Promise<(TResult | null)[]> {
|
|
521
|
+
if (!Array.isArray(items)) {
|
|
522
|
+
throw new TypeError("pipeline() expects an array as the first argument");
|
|
523
|
+
}
|
|
524
|
+
if (stages.length === 0 || stages.some((s) => typeof s !== "function")) {
|
|
525
|
+
throw new TypeError("pipeline() stages must be functions");
|
|
526
|
+
}
|
|
527
|
+
if (items.length === 0) return [];
|
|
528
|
+
// Parallel across items, bounded by the workflow concurrency (mirrors fanOut).
|
|
529
|
+
// Per-item stages run sequentially. A failed stage yields null for that item
|
|
530
|
+
// (logged via ctx.log) and the remaining items continue. Aborts propagate.
|
|
531
|
+
return mapConcurrent(items, concurrency, async (item, index): Promise<TResult | null> => {
|
|
532
|
+
let value: unknown = item;
|
|
533
|
+
for (const stage of stages) {
|
|
534
|
+
try {
|
|
535
|
+
value = await stage(value as TResult, item, index);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
if (opts.signal.aborted) throw error;
|
|
538
|
+
ctx.log(`pipeline[${index}] failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return value as TResult;
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
async review(
|
|
546
|
+
taskId: string,
|
|
547
|
+
reviewerRole = "reviewer",
|
|
548
|
+
reviewOpts?: {
|
|
549
|
+
content?: string;
|
|
550
|
+
artifactPath?: string;
|
|
551
|
+
disableTools?: boolean;
|
|
552
|
+
},
|
|
553
|
+
): Promise<{
|
|
554
|
+
outcome: "accept" | "reject" | "changes_requested";
|
|
555
|
+
feedback: string;
|
|
556
|
+
}> {
|
|
557
|
+
// review() is a VERDICT step: it must produce a parseable JSON {outcome, feedback}, not a
|
|
558
|
+
// free-form markdown review. The resolved reviewer agent (e.g. ~/.pi/agent/agents/reviewer.md)
|
|
559
|
+
// has tools (read/grep/bash) + a markdown-output system prompt. Without disableTools, the
|
|
560
|
+
// reviewer explores the repo looking for the task's work, loops, and gets killed (exit 143)
|
|
561
|
+
// before producing JSON — leaving text="" and the fallback verdict. Default: disableTools so
|
|
562
|
+
// the reviewer judges the provided content (or taskId context) directly.
|
|
563
|
+
const disableTools = reviewOpts?.disableTools !== false; // default true
|
|
564
|
+
const workContext = reviewOpts?.content
|
|
565
|
+
? `\n\nWork to review:\n"""\n${reviewOpts.content}\n"""`
|
|
566
|
+
: reviewOpts?.artifactPath
|
|
567
|
+
? `\n\nRead the work from artifact: ${reviewOpts.artifactPath}`
|
|
568
|
+
: "";
|
|
569
|
+
const res = await ctx.agent({
|
|
570
|
+
role: reviewerRole,
|
|
571
|
+
prompt: `You are reviewing the work for task '${taskId}'.${workContext}\n\nEvaluate the work and respond with ONLY a single JSON object, no prose, no markdown:\n{"outcome":"accept|reject|changes_requested","feedback":"<one-paragraph explanation>"}\n\n- "accept": work is complete and correct.\n- "reject": work is fundamentally wrong.\n- "changes_requested": work needs revision (explain what in feedback).`,
|
|
572
|
+
maxTurns: 3,
|
|
573
|
+
disableTools,
|
|
574
|
+
systemPrompt:
|
|
575
|
+
'You are a JSON verdict judge. You output ONLY a single JSON object with keys "outcome" (one of accept/reject/changes_requested) and "feedback" (a concise explanation). Never output prose, markdown, or code fences. Begin your response with { and end with }.',
|
|
576
|
+
});
|
|
577
|
+
const extracted = res.structured as { outcome?: string; feedback?: string } | undefined;
|
|
578
|
+
if (extracted && typeof extracted.outcome === "string" && typeof extracted.feedback === "string") {
|
|
579
|
+
const outcome =
|
|
580
|
+
extracted.outcome === "accept" || extracted.outcome === "reject" || extracted.outcome === "changes_requested"
|
|
581
|
+
? extracted.outcome
|
|
582
|
+
: "changes_requested";
|
|
583
|
+
return { outcome, feedback: extracted.feedback };
|
|
584
|
+
}
|
|
585
|
+
// Fallback (round-11 runtime): many models (e.g. MiniMax-M3) ignore JSON-output
|
|
586
|
+
// instructions and produce a prose review instead. Rather than report an
|
|
587
|
+
// unparseable verdict, run a tiny judge call that converts the prose review into a
|
|
588
|
+
// JSON verdict. This guarantees ctx.review() always returns a structured verdict
|
|
589
|
+
// regardless of the reviewer's output format. Skipped when the reviewer produced
|
|
590
|
+
// no text at all (genuine failure).
|
|
591
|
+
if (res.text.trim()) {
|
|
592
|
+
const judge = await ctx.agent({
|
|
593
|
+
role: reviewerRole,
|
|
594
|
+
prompt: `Convert the following code review into a verdict JSON. Read the review and decide the outcome.\n\nREVIEW:\n"""\n${res.text.slice(0, 4000)}\n"""\n\nRespond with ONLY a JSON object:\n{"outcome":"accept|reject|changes_requested","feedback":"<concise summary>"}\n- accept: review found no real issues.\n- reject: review found critical/fundamental problems.\n- changes_requested: review found issues that need fixing.`,
|
|
595
|
+
maxTurns: 1,
|
|
596
|
+
disableTools: true,
|
|
597
|
+
systemPrompt:
|
|
598
|
+
"You output ONLY a single JSON object with keys outcome and feedback. Begin with { and end with }. Never output prose.",
|
|
599
|
+
});
|
|
600
|
+
const judged = judge.structured as { outcome?: string; feedback?: string } | undefined;
|
|
601
|
+
if (judged && typeof judged.outcome === "string" && typeof judged.feedback === "string") {
|
|
602
|
+
const outcome =
|
|
603
|
+
judged.outcome === "accept" || judged.outcome === "reject" || judged.outcome === "changes_requested"
|
|
604
|
+
? judged.outcome
|
|
605
|
+
: "changes_requested";
|
|
606
|
+
return { outcome, feedback: judged.feedback };
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
// Tier-3 sentiment fallback (round-11): when neither the reviewer nor the judge
|
|
610
|
+
// produced JSON (common with MiniMax-M3, GLM, which ignore JSON-output
|
|
611
|
+
// instructions), classify the outcome from the REVIEWER's prose sentiment. We use
|
|
612
|
+
// the reviewer's text (not the judge's terse output) because the original review is
|
|
613
|
+
// the richest sentiment signal. This keeps outcome ACCURATE (accept vs reject vs
|
|
614
|
+
// changes_requested) even when no JSON is ever produced — without it, outcome was
|
|
615
|
+
// always the hardcoded 'changes_requested' default (e.g. correct code was
|
|
616
|
+
// misclassified as needing changes).
|
|
617
|
+
if (res.text.trim()) {
|
|
618
|
+
return {
|
|
619
|
+
outcome: classifyReviewOutcome(res.text),
|
|
620
|
+
feedback: res.text,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
outcome: "changes_requested",
|
|
625
|
+
feedback: res.text || "(reviewer produced no parseable verdict)",
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
async retry(taskId: string, retryOpts?: { feedback?: string }): Promise<AgentResult> {
|
|
629
|
+
return executeWithRetry(
|
|
630
|
+
async () =>
|
|
631
|
+
ctx.agent({
|
|
632
|
+
role: "executor",
|
|
633
|
+
prompt: `Re-do task '${taskId}'.${retryOpts?.feedback ? ` Feedback: ${retryOpts.feedback}` : ""}`,
|
|
634
|
+
}),
|
|
635
|
+
{
|
|
636
|
+
maxAttempts: 3,
|
|
637
|
+
backoffMs: 0,
|
|
638
|
+
jitterRatio: 0,
|
|
639
|
+
exponentialFactor: 1,
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
},
|
|
643
|
+
mail(
|
|
644
|
+
to: string,
|
|
645
|
+
body: string,
|
|
646
|
+
mailOpts?: {
|
|
647
|
+
kind?: string;
|
|
648
|
+
taskId?: string;
|
|
649
|
+
replyTo?: string;
|
|
650
|
+
replyDeadline?: number;
|
|
651
|
+
},
|
|
652
|
+
): string {
|
|
653
|
+
const msg = appendMailboxMessage(manifest, {
|
|
654
|
+
direction: "outbox",
|
|
655
|
+
from: "dynamic-workflow",
|
|
656
|
+
to,
|
|
657
|
+
body,
|
|
658
|
+
kind: (mailOpts?.kind as never) ?? "message",
|
|
659
|
+
taskId: mailOpts?.taskId,
|
|
660
|
+
replyTo: mailOpts?.replyTo,
|
|
661
|
+
replyDeadline: mailOpts?.replyDeadline,
|
|
662
|
+
});
|
|
663
|
+
return msg.id;
|
|
664
|
+
},
|
|
665
|
+
async gatherReplies(messageIds: string[], deadlineMs: number): Promise<unknown[]> {
|
|
666
|
+
const deadline = Date.now() + deadlineMs;
|
|
667
|
+
while (Date.now() < deadline) {
|
|
668
|
+
const inbox = readMailbox(manifest, "inbox");
|
|
669
|
+
const got = inbox.filter((m) => m.replyTo && messageIds.includes(m.replyTo));
|
|
670
|
+
if (got.length >= messageIds.length) return got;
|
|
671
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
672
|
+
if (opts.signal.aborted) return inbox.filter((m) => m.replyTo && messageIds.includes(m.replyTo));
|
|
673
|
+
}
|
|
674
|
+
return readMailbox(manifest, "inbox").filter((m) => m.replyTo && messageIds.includes(m.replyTo));
|
|
675
|
+
},
|
|
676
|
+
renderTemplate(name: string, vars: Record<string, string>): unknown {
|
|
677
|
+
return renderPlanTemplate(name, vars);
|
|
678
|
+
},
|
|
679
|
+
vars: opts.resumedState ? { ...opts.resumedState.vars } : ({} as Record<string, unknown>),
|
|
680
|
+
setResult(artifactPath: string, meta?: Record<string, unknown>): void {
|
|
681
|
+
finalResult = { artifactPath, meta };
|
|
682
|
+
},
|
|
683
|
+
phase(title: string): void {
|
|
684
|
+
if (typeof title !== "string" || title.length === 0) {
|
|
685
|
+
throw new TypeError("ctx.phase(title) requires a non-empty string title.");
|
|
686
|
+
}
|
|
687
|
+
// Idempotency: same phase title → no event, no state change.
|
|
688
|
+
if (title === phaseState.currentPhase) return;
|
|
689
|
+
// Close out the previous open phase BEFORE the new one opens.
|
|
690
|
+
if (phaseState.currentPhase !== undefined) {
|
|
691
|
+
appendEvent(manifest.eventsPath, {
|
|
692
|
+
type: "dwf.phase_completed",
|
|
693
|
+
runId: manifest.runId,
|
|
694
|
+
data: { phase: phaseState.currentPhase },
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
phaseState.currentPhase = title;
|
|
698
|
+
// Dedup append with hard cap to bound memory; events still flow.
|
|
699
|
+
if (!phaseState.phases.includes(title)) {
|
|
700
|
+
if (phaseState.phases.length < 100) {
|
|
701
|
+
phaseState.phases.push(title);
|
|
702
|
+
} else if (!phaseCapWarned) {
|
|
703
|
+
phaseCapWarned = true;
|
|
704
|
+
logInternalError(
|
|
705
|
+
"dynamic-workflow-context.phase-cap",
|
|
706
|
+
new Error(
|
|
707
|
+
"Phase list cap of 100 reached; further phases still emit events but are not added to the in-memory phases[] list. Use the events log as the durable source of truth.",
|
|
708
|
+
),
|
|
709
|
+
`runId=${manifest.runId}`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
appendEvent(manifest.eventsPath, {
|
|
714
|
+
type: "dwf.phase_started",
|
|
715
|
+
runId: manifest.runId,
|
|
716
|
+
data: { phase: title },
|
|
717
|
+
});
|
|
718
|
+
},
|
|
719
|
+
budget,
|
|
720
|
+
log(message: unknown): void {
|
|
721
|
+
// round-14 P1-3: stringify non-strings, keep a bounded in-memory copy, and
|
|
722
|
+
// always emit a dwf.log event (the events log is the durable source of truth).
|
|
723
|
+
const text = typeof message === "string" ? message : JSON.stringify(message);
|
|
724
|
+
if (wfState.logs.length < 1000) {
|
|
725
|
+
wfState.logs.push(text);
|
|
726
|
+
}
|
|
727
|
+
appendEvent(manifest.eventsPath, {
|
|
728
|
+
type: "dwf.log",
|
|
729
|
+
runId: manifest.runId,
|
|
730
|
+
data: { message: text },
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
args<T = unknown>(): T {
|
|
734
|
+
// round-14 P1-5: typed workflow args sourced from manifest (via opts.args).
|
|
735
|
+
return wfState.args as T;
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// Attach the final-result slot via a non-enumerable getter so the runner can read it
|
|
740
|
+
// without exposing a mutation surface on the ctx the script sees.
|
|
741
|
+
Object.defineProperty(ctx, "__finalResult", {
|
|
742
|
+
get: () => finalResult,
|
|
743
|
+
enumerable: false,
|
|
744
|
+
});
|
|
745
|
+
// round-12 P0-1: phase state is read-only from the runner; the script can only mutate
|
|
746
|
+
// it via ctx.phase(title), which is the documented public surface.
|
|
747
|
+
Object.defineProperty(ctx, "__phaseState", {
|
|
748
|
+
get: () => phaseState,
|
|
749
|
+
enumerable: false,
|
|
750
|
+
});
|
|
751
|
+
// round-14 P1-3: in-memory log buffer is read-only from the runner; the script can only
|
|
752
|
+
// append via ctx.log(message). The events log remains the durable source of truth.
|
|
753
|
+
Object.defineProperty(ctx, "__logs", {
|
|
754
|
+
get: () => wfState.logs,
|
|
755
|
+
enumerable: false,
|
|
756
|
+
});
|
|
757
|
+
// round-18 P2-3: agent invocation counter is read-only from the runner. The script can
|
|
758
|
+
// only advance it via ctx.agent() (incremented in agent()'s finally). Exposed so
|
|
759
|
+
// getWorkflowCheckpoint() can report an accurate count.
|
|
760
|
+
Object.defineProperty(ctx, "__agentCount", {
|
|
761
|
+
get: () => agentCount,
|
|
762
|
+
enumerable: false,
|
|
763
|
+
});
|
|
764
|
+
// PERS-1: completedAgentCalls is read-only from the runner; the agent() method
|
|
765
|
+
// is the only writer. Exposed so getWorkflowCheckpoint() can include it.
|
|
766
|
+
Object.defineProperty(ctx, "__completedAgentCalls", {
|
|
767
|
+
get: () => completedAgentCalls,
|
|
768
|
+
enumerable: false,
|
|
769
|
+
});
|
|
770
|
+
return ctx;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** Read the final result set by the script (runner-only; not part of the public ctx surface). */
|
|
774
|
+
export function getWorkflowFinalResult(ctx: WorkflowCtx): { artifactPath: string; meta?: Record<string, unknown> } | undefined {
|
|
775
|
+
return (
|
|
776
|
+
ctx as unknown as {
|
|
777
|
+
__finalResult?: {
|
|
778
|
+
artifactPath: string;
|
|
779
|
+
meta?: Record<string, unknown>;
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
).__finalResult;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Read the in-memory phase state set by the script (runner-only; not part of the public ctx surface). */
|
|
786
|
+
export function getWorkflowPhaseState(ctx: WorkflowCtx): { currentPhase: string | undefined; phases: string[] } | undefined {
|
|
787
|
+
return (
|
|
788
|
+
ctx as unknown as {
|
|
789
|
+
__phaseState?: {
|
|
790
|
+
currentPhase: string | undefined;
|
|
791
|
+
phases: string[];
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
).__phaseState;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** Read the in-memory log buffer appended by ctx.log() (runner-only; not part of the public ctx surface).
|
|
798
|
+
* Capped at 1000 entries — the events log (dwf.log) is the durable source of truth. */
|
|
799
|
+
export function getWorkflowLogs(ctx: WorkflowCtx): string[] | undefined {
|
|
800
|
+
return (ctx as unknown as { __logs?: string[] }).__logs;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** round-18 P2-3: snapshot the current DWF checkpoint state (runner-only; not part of the public
|
|
804
|
+
* ctx surface). Mirrors getWorkflowFinalResult/getWorkflowPhaseState. The runner relies on the
|
|
805
|
+
* `onCheckpoint` callback for accurate per-agent-call checkpoints (it captures the closure value
|
|
806
|
+
* at call time); this helper is a best-effort snapshot for inspection/debugging. */
|
|
807
|
+
export function getWorkflowCheckpoint(ctx: WorkflowCtx): DwfCheckpointState {
|
|
808
|
+
const phaseState = getWorkflowPhaseState(ctx);
|
|
809
|
+
const logs = getWorkflowLogs(ctx);
|
|
810
|
+
return {
|
|
811
|
+
runId: ctx.runId,
|
|
812
|
+
vars: ctx.vars,
|
|
813
|
+
phases: phaseState?.phases ?? [],
|
|
814
|
+
currentPhase: phaseState?.currentPhase,
|
|
815
|
+
logs: logs ?? [],
|
|
816
|
+
spent: ctx.budget.spent(),
|
|
817
|
+
agentCount: (ctx as unknown as { __agentCount?: number }).__agentCount ?? 0,
|
|
818
|
+
completedAgentCalls:
|
|
819
|
+
(ctx as unknown as { __completedAgentCalls?: Record<string, { text: string; usage?: { input: number; output: number } }> })
|
|
820
|
+
.__completedAgentCalls ?? {},
|
|
821
|
+
updatedAt: new Date().toISOString(),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/** Compose the agent task: prompt + optional dependency-input context block. */
|
|
826
|
+
function composeAgentTask(call: AgentCallOpts): string {
|
|
827
|
+
let base = call.prompt;
|
|
828
|
+
if (call.inputs?.length) {
|
|
829
|
+
const block = call.inputs.map((p) => `- ${p}`).join("\n");
|
|
830
|
+
base = `${base}\n\n## Inputs (artifact paths)\n${block}`;
|
|
831
|
+
}
|
|
832
|
+
// Round-13 P0-3: when a schema is requested, append a JSON-output directive.
|
|
833
|
+
// The directive lives at the END of the prompt so it wins over any conflicting
|
|
834
|
+
// persona instruction in the agent's system prompt.
|
|
835
|
+
if (call.schema !== undefined) {
|
|
836
|
+
base = `${base}\n\n## Output format\nRespond with ONLY a single JSON object that matches the schema described in your instructions. Begin your response with { and end with }. Do not wrap the JSON in a code fence. Do not add any prose before or after the JSON.`;
|
|
837
|
+
}
|
|
838
|
+
return base;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Round-13 P0-3: compose a system-prompt suffix that asks the agent to output a
|
|
843
|
+
* structured JSON object matching the schema's required shape. We don't expose
|
|
844
|
+
* the TypeBox internal type — we describe the SHAPE so the model can match it.
|
|
845
|
+
*/
|
|
846
|
+
function composeSchemaSystemPrompt(base: string | undefined, schema: TSchema): string {
|
|
847
|
+
const shape = describeSchemaShape(schema, 0);
|
|
848
|
+
const intro = "You are a structured-output assistant. ";
|
|
849
|
+
const instruction = `When responding, output ONLY a single JSON object matching this shape (no prose, no markdown fences, no commentary): ${shape}. Begin your response with { and end with }.`;
|
|
850
|
+
if (typeof base === "string" && base.length > 0) {
|
|
851
|
+
return `${base}\n\n${intro}${instruction}`;
|
|
852
|
+
}
|
|
853
|
+
return `${intro}${instruction}`;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Walk a TypeBox schema recursively and produce a human-readable shape description.
|
|
858
|
+
* Depth-limited to avoid runaway expansion on deeply nested schemas.
|
|
859
|
+
*/
|
|
860
|
+
function describeSchemaShape(schema: unknown, depth: number): string {
|
|
861
|
+
if (depth > 4) return "{...}";
|
|
862
|
+
if (!schema || typeof schema !== "object") return "any";
|
|
863
|
+
const obj = schema as Record<string, unknown>;
|
|
864
|
+
// TypeBox: every schema has a `type` discriminator or a `kind` field.
|
|
865
|
+
const kind = obj.kind as string | undefined;
|
|
866
|
+
const type = obj.type as string | undefined;
|
|
867
|
+
if (kind === "object" || type === "object") {
|
|
868
|
+
const properties = obj.properties;
|
|
869
|
+
if (!properties || typeof properties !== "object") return "{}";
|
|
870
|
+
const required = Array.isArray(obj.required) ? new Set(obj.required as string[]) : new Set<string>();
|
|
871
|
+
const props = Object.entries(properties as Record<string, unknown>)
|
|
872
|
+
.map(([key, sub]) => {
|
|
873
|
+
const mark = required.has(key) ? "" : "?";
|
|
874
|
+
return `"${key}"${mark}: ${describeSchemaShape(sub, depth + 1)}`;
|
|
875
|
+
})
|
|
876
|
+
.join(", ");
|
|
877
|
+
return `{${props}}`;
|
|
878
|
+
}
|
|
879
|
+
if (kind === "array" || type === "array") {
|
|
880
|
+
const items = obj.items;
|
|
881
|
+
return `[${describeSchemaShape(items, depth + 1)}]`;
|
|
882
|
+
}
|
|
883
|
+
if (type === "string") return "string";
|
|
884
|
+
if (type === "number" || type === "integer") return "number";
|
|
885
|
+
if (type === "boolean") return "boolean";
|
|
886
|
+
if (type === "null") return "null";
|
|
887
|
+
// Union/Enum fallbacks.
|
|
888
|
+
if (Array.isArray(obj.anyOf)) return obj.anyOf.map((s) => describeSchemaShape(s, depth + 1)).join(" | ");
|
|
889
|
+
if (Array.isArray(obj.oneOf)) return obj.oneOf.map((s) => describeSchemaShape(s, depth + 1)).join(" | ");
|
|
890
|
+
if (Array.isArray(obj.enum)) return obj.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
891
|
+
return "any";
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Classify a review outcome from prose when no JSON was produced (round-11 tier-3/4 fallback).
|
|
896
|
+
* Scans the reviewer's prose for sentiment signals to decide accept / reject / changes_requested.
|
|
897
|
+
* This keeps the outcome ACCURATE for models that ignore JSON-output instructions.
|
|
898
|
+
*
|
|
899
|
+
* Decision order: reject (critical issues) → accept (explicit approval) → changes_requested (default).
|
|
900
|
+
* reject is checked first because a review can mention both "correctly" (describing existing code)
|
|
901
|
+
* AND "critical bug" (the verdict) — the verdict signal must win.
|
|
902
|
+
*/
|
|
903
|
+
export function classifyReviewOutcome(prose: string): "accept" | "reject" | "changes_requested" {
|
|
904
|
+
const text = prose.toLowerCase();
|
|
905
|
+
// Strong negative signals → reject. These indicate fundamental/critical problems.
|
|
906
|
+
const rejectSignals = [
|
|
907
|
+
"\breject\b",
|
|
908
|
+
"fundamentally",
|
|
909
|
+
"completely broken",
|
|
910
|
+
"totally broken",
|
|
911
|
+
"critical bug",
|
|
912
|
+
"critical issue",
|
|
913
|
+
"critical flaw",
|
|
914
|
+
"security vulnerability",
|
|
915
|
+
"does not work",
|
|
916
|
+
"doesn't work",
|
|
917
|
+
"will not work",
|
|
918
|
+
"fails to",
|
|
919
|
+
"unacceptable",
|
|
920
|
+
"must not be merged",
|
|
921
|
+
"do not merge",
|
|
922
|
+
"wrong approach",
|
|
923
|
+
"logically incorrect",
|
|
924
|
+
"incorrectly implements",
|
|
925
|
+
"returns the opposite",
|
|
926
|
+
"subtraction instead of addition",
|
|
927
|
+
"opposite of its intended",
|
|
928
|
+
];
|
|
929
|
+
// Acceptance signals → accept. These indicate explicit approval with no real issues.
|
|
930
|
+
const acceptSignals = [
|
|
931
|
+
"\baccept\b",
|
|
932
|
+
"looks good",
|
|
933
|
+
"well done",
|
|
934
|
+
"no issues",
|
|
935
|
+
"no real issues",
|
|
936
|
+
"no problems",
|
|
937
|
+
"no concerns",
|
|
938
|
+
"nothing to change",
|
|
939
|
+
"ready to merge",
|
|
940
|
+
"lgtm",
|
|
941
|
+
"ship it",
|
|
942
|
+
"correctly implements",
|
|
943
|
+
"correctly returns",
|
|
944
|
+
"works as expected",
|
|
945
|
+
"works correctly",
|
|
946
|
+
"no bugs",
|
|
947
|
+
"no defects",
|
|
948
|
+
"meets all requirements",
|
|
949
|
+
"all requirements met",
|
|
950
|
+
"passes all",
|
|
951
|
+
"is correct",
|
|
952
|
+
"are correct",
|
|
953
|
+
"no changes needed",
|
|
954
|
+
"no changes required",
|
|
955
|
+
"no further changes",
|
|
956
|
+
"nothing more to",
|
|
957
|
+
"complete and correct",
|
|
958
|
+
"sound implementation",
|
|
959
|
+
];
|
|
960
|
+
const hasReject = rejectSignals.some((sig) => new RegExp(sig).test(text));
|
|
961
|
+
const hasAccept = acceptSignals.some((sig) => new RegExp(sig).test(text));
|
|
962
|
+
if (hasReject) return "reject";
|
|
963
|
+
if (hasAccept) return "accept";
|
|
964
|
+
return "changes_requested";
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Round-11 test fix: permissive text extraction for ctx.agent().
|
|
969
|
+
* parsePiJsonOutput only handles the canonical pi event stream. When the child emits
|
|
970
|
+
* a different shape, finalText is empty. This fallback walks the JSON tree looking
|
|
971
|
+
* for any text-shaped string at any depth, then returns the longest one (typically
|
|
972
|
+
* the final assistant response).
|
|
973
|
+
*/
|
|
974
|
+
export function extractTextFallback(stdout: string): string {
|
|
975
|
+
const trimmed = stdout.trim();
|
|
976
|
+
if (!trimmed) return "";
|
|
977
|
+
const candidates: string[] = [];
|
|
978
|
+
const collect = (value: unknown): void => {
|
|
979
|
+
if (typeof value === "string") {
|
|
980
|
+
const t = value.trim();
|
|
981
|
+
// Skip very short strings and JSON-ish strings
|
|
982
|
+
if (t.length >= 2 && !t.startsWith("{") && !t.startsWith("[") && !/^[\d.]+$/.test(t)) {
|
|
983
|
+
candidates.push(t);
|
|
984
|
+
}
|
|
985
|
+
} else if (Array.isArray(value)) {
|
|
986
|
+
for (const item of value) collect(item);
|
|
987
|
+
} else if (value && typeof value === "object") {
|
|
988
|
+
for (const v of Object.values(value as Record<string, unknown>)) collect(v);
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
// 1. Try parsing each line as JSON, walk tree
|
|
992
|
+
for (const line of trimmed.split("\n")) {
|
|
993
|
+
const lineTrim = line.trim();
|
|
994
|
+
if (!lineTrim.startsWith("{")) continue;
|
|
995
|
+
try {
|
|
996
|
+
const obj = JSON.parse(lineTrim);
|
|
997
|
+
collect(obj);
|
|
998
|
+
} catch {
|
|
999
|
+
/* skip */
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
// 2. If nothing from JSON, try plain text (longest non-empty line that's not JSON)
|
|
1003
|
+
if (candidates.length === 0) {
|
|
1004
|
+
for (const line of trimmed.split("\n")) {
|
|
1005
|
+
const l = line.trim();
|
|
1006
|
+
if (l.length >= 3 && !l.startsWith("{") && !l.startsWith("[") && !l.startsWith("=")) candidates.push(l);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// 3. Return the longest candidate (typically the final answer)
|
|
1010
|
+
if (candidates.length === 0) return "";
|
|
1011
|
+
candidates.sort((a, b) => b.length - a.length);
|
|
1012
|
+
return candidates[0];
|
|
1013
|
+
}
|