@cohortapp/agent-sdk 2.17.0 → 2.18.4
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/.claude/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/workflow/notifications.mjs — one notification source for the loop
|
|
3
|
+
* from several (the agent runtime's background tasks, workflow runs).
|
|
4
|
+
*
|
|
5
|
+
* The loop takes one `drainNotifications` and one `awaitNotifications`
|
|
6
|
+
* (loop.mjs). This merges sources that each have those plus `hasRunning`:
|
|
7
|
+
*
|
|
8
|
+
* drain every source's ready notifications, in source order
|
|
9
|
+
* await ready ones now; otherwise wait until any running source produces
|
|
10
|
+
* one (a source that wakes with nothing — its work was read early —
|
|
11
|
+
* waits again), or nothing is running, or the signal fires
|
|
12
|
+
*
|
|
13
|
+
* The other sources' waits are released when one wins, and whatever they
|
|
14
|
+
* drained on release is kept, so no notification is lost to the race.
|
|
15
|
+
*
|
|
16
|
+
* @module lib/engine/workflow/notifications
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {{drainNotifications:() => string[], awaitNotifications:(signal?:AbortSignal) => Promise<string[]>, hasRunning:() => boolean}} NotificationSource
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** @param {Array<NotificationSource|null|undefined>} sources */
|
|
24
|
+
export function mergeNotificationSources(sources) {
|
|
25
|
+
const list = /** @type {NotificationSource[]} */ (sources.filter(Boolean));
|
|
26
|
+
const drainNotifications = () => list.flatMap((s) => s.drainNotifications());
|
|
27
|
+
return {
|
|
28
|
+
drainNotifications,
|
|
29
|
+
hasRunning: () => list.some((s) => s.hasRunning()),
|
|
30
|
+
/** @param {AbortSignal} [signal] */
|
|
31
|
+
async awaitNotifications(signal) {
|
|
32
|
+
for (;;) {
|
|
33
|
+
const ready = drainNotifications();
|
|
34
|
+
if (ready.length > 0) return ready;
|
|
35
|
+
const running = list.filter((s) => s.hasRunning());
|
|
36
|
+
if (running.length === 0 || signal?.aborted) return [];
|
|
37
|
+
const inner = new AbortController();
|
|
38
|
+
const onOuter = () => inner.abort();
|
|
39
|
+
signal?.addEventListener("abort", onOuter, { once: true });
|
|
40
|
+
try {
|
|
41
|
+
const waits = running.map((s) => s.awaitNotifications(inner.signal));
|
|
42
|
+
await Promise.race(waits);
|
|
43
|
+
inner.abort();
|
|
44
|
+
const got = (await Promise.all(waits)).flat();
|
|
45
|
+
if (got.length > 0) return [...got, ...drainNotifications()];
|
|
46
|
+
} finally {
|
|
47
|
+
signal?.removeEventListener("abort", onOuter);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/workflow/runtime.mjs — the session's workflow runs: starting one
|
|
3
|
+
* in the background, resuming from a journal, progress, completion
|
|
4
|
+
* notifications, stopping.
|
|
5
|
+
*
|
|
6
|
+
* A run starts from an inline `script`, a `scriptPath`, or (with
|
|
7
|
+
* `resumeFromRunId` alone) the script the earlier run persisted. Everything
|
|
8
|
+
* that can be refused is refused BEFORE the run exists and is the Workflow
|
|
9
|
+
* call's error: the size cap, the meta literal, `import`, a syntax error,
|
|
10
|
+
* args that are not JSON, a script file the session's Read rules refuse, an
|
|
11
|
+
* unknown run (or another project's) to resume, and a run that may still be
|
|
12
|
+
* running (in this process, or in another live process that has not written its
|
|
13
|
+
* `end` record).
|
|
14
|
+
*
|
|
15
|
+
* The call returns {runId, scriptPath} at once. The run executes in this
|
|
16
|
+
* process (host.mjs), and when it ends a notification — the script's return
|
|
17
|
+
* value, cut at `RESULT_INLINE_CHARS` with the path of the full `result.json`
|
|
18
|
+
* — is queued and delivered by the loop as a user message, exactly like a
|
|
19
|
+
* background subagent's report (agents/runtime.mjs; merged with it by
|
|
20
|
+
* notifications.mjs). A run read to its end with WorkflowStatus is not
|
|
21
|
+
* notified again. Runs still going when the conversation's turn ends are
|
|
22
|
+
* stopped, as background subagents are.
|
|
23
|
+
*
|
|
24
|
+
* @module lib/engine/workflow/runtime
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import { writeFileAtomic } from "../../fs-atomic.mjs";
|
|
30
|
+
import { LIFETIME_AGENT_CAP, createRunState, createSemaphore, defaultConcurrency, executeRun } from "./host.mjs";
|
|
31
|
+
import { createJournal, createReplay, findRunDir, isRunId, newRunId, readJournal, runDirFor, runSummary } from "./journal.mjs";
|
|
32
|
+
import { MAX_SCRIPT_BYTES, parseWorkflowSource } from "./meta.mjs";
|
|
33
|
+
import { compileWorkflow } from "./sandbox.mjs";
|
|
34
|
+
import { PROCESS_NONCE, judgeOwner, processAlive, processStartToken } from "../process-identity.mjs";
|
|
35
|
+
|
|
36
|
+
export const RESULT_INLINE_CHARS = 20_000;
|
|
37
|
+
export const MAX_ARGS_CHARS = 1_000_000;
|
|
38
|
+
const STOP_WAIT_MS = 10_000;
|
|
39
|
+
const STATUS_LOG_LINES = 20;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {Object} WorkflowRuntimeDeps
|
|
43
|
+
* @property {string} sessionId
|
|
44
|
+
* @property {string} sessionsDir
|
|
45
|
+
* @property {string} cwd
|
|
46
|
+
* @property {import('../agents/definitions.mjs').AgentDefinition[]} agents
|
|
47
|
+
* @property {(p:any) => Promise<any>} runChild agents/runtime.mjs runChild
|
|
48
|
+
* @property {(o:{agentType:string}) => {behavior:'allow'|'deny'|'ask', reason:string}} [checkAgent]
|
|
49
|
+
* @property {() => number} [now]
|
|
50
|
+
* @property {number} [maxConcurrency] default min(16, cpus − 2), at least 1
|
|
51
|
+
* @property {number} [lifetimeCap] agent() calls per run, default 1000
|
|
52
|
+
* @property {number} [maxItems] items per parallel()/pipeline(), default 4096
|
|
53
|
+
* @property {number|null} [budgetTokens] the budget's total; null = unlimited
|
|
54
|
+
* @property {number} [syncTimeoutMs]
|
|
55
|
+
* @property {number} [busyTimeoutMs] CF-90: how long a script may go without yielding (sandbox.mjs BUSY_TIMEOUT_MS)
|
|
56
|
+
* @property {number|null} [runTimeoutMs] CF-90: a run's time limit; null = none
|
|
57
|
+
* @property {number} [budgetGraceMs] CF-90: once a run's budget is spent, how long its script may go on (host.mjs BUDGET_GRACE_MS)
|
|
58
|
+
* @property {HostWorktrees} [worktrees]
|
|
59
|
+
* @property {(o:{path:string}) => {behavior:'allow'|'deny'|'ask', reason:string}} [checkRead]
|
|
60
|
+
* the session's Read evaluation for a script file named by scriptPath or workflow(ref)
|
|
61
|
+
* @property {number} [pid] this process (default process.pid)
|
|
62
|
+
* @property {string} [nonce] this process's nonce (default process-identity.mjs PROCESS_NONCE)
|
|
63
|
+
* @property {(pid:number) => boolean} [isProcessAlive]
|
|
64
|
+
* @property {(pid:number) => string|null} [processStartToken] the OS start time of a PID (process-identity.mjs)
|
|
65
|
+
* @property {number} [settleWaitMs]
|
|
66
|
+
* @property {() => void} [onNotify] a run's notification was queued (wakes an idle `cohort session`)
|
|
67
|
+
*/
|
|
68
|
+
/** @typedef {NonNullable<import('./host.mjs').HostDeps['worktrees']>} HostWorktrees */
|
|
69
|
+
|
|
70
|
+
/** Run ids executing in THIS process, whichever runtime started them (a host may build several). */
|
|
71
|
+
const PROCESS_RUNNING_RUNS = new Set();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Workflow timing knobs from the environment (W4-E2 review: a canary can loosen them without a code
|
|
75
|
+
* change). Unset or invalid: the defaults (sandbox.mjs BUSY_TIMEOUT_MS, host.mjs BUDGET_GRACE_MS, no run
|
|
76
|
+
* time limit). Pure.
|
|
77
|
+
* COHORT_ENGINE_WORKFLOW_TIMEOUT_MS a run's time limit
|
|
78
|
+
* COHORT_ENGINE_WORKFLOW_BUSY_TIMEOUT_MS how long a script may go without its event loop turning
|
|
79
|
+
* COHORT_ENGINE_WORKFLOW_BUDGET_GRACE_MS how long a script may go on once its token budget is spent
|
|
80
|
+
* @param {Record<string, string|undefined>} env
|
|
81
|
+
* @returns {{runTimeoutMs:number|null, busyTimeoutMs?:number, budgetGraceMs?:number}}
|
|
82
|
+
*/
|
|
83
|
+
export function workflowTimingFromEnv(env) {
|
|
84
|
+
const ms = (/** @type {string|undefined} */ v) => {
|
|
85
|
+
const n = Number(v);
|
|
86
|
+
return v !== undefined && v !== "" && Number.isInteger(n) && n > 0 ? n : null;
|
|
87
|
+
};
|
|
88
|
+
const busy = ms(env.COHORT_ENGINE_WORKFLOW_BUSY_TIMEOUT_MS);
|
|
89
|
+
const grace = ms(env.COHORT_ENGINE_WORKFLOW_BUDGET_GRACE_MS);
|
|
90
|
+
return { runTimeoutMs: ms(env.COHORT_ENGINE_WORKFLOW_TIMEOUT_MS), ...(busy !== null ? { busyTimeoutMs: busy } : {}), ...(grace !== null ? { budgetGraceMs: grace } : {}) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @param {WorkflowRuntimeDeps} d */
|
|
94
|
+
export function createWorkflowRuntime(d) {
|
|
95
|
+
const now = d.now ?? Date.now;
|
|
96
|
+
const pid = d.pid ?? process.pid;
|
|
97
|
+
const nonce = d.nonce ?? PROCESS_NONCE;
|
|
98
|
+
const isAlive = d.isProcessAlive ?? processAlive;
|
|
99
|
+
const startTokenOf = d.processStartToken ?? processStartToken;
|
|
100
|
+
/**
|
|
101
|
+
* Is a run whose journal has no `end` still running somewhere (W4-E2 review: not by bare PID)?
|
|
102
|
+
* @param {string} runId @param {{pid:number|null, nonce:string|null, startToken:string|null}} summary
|
|
103
|
+
*/
|
|
104
|
+
const stillRunning = (runId, summary) => {
|
|
105
|
+
if (summary.pid === null) return false;
|
|
106
|
+
const owner = judgeOwner({ pid: summary.pid, nonce: summary.nonce, startToken: summary.startToken }, { pid, nonce }, { isAlive, startToken: startTokenOf });
|
|
107
|
+
return owner === "alive" || (owner === "self" && PROCESS_RUNNING_RUNS.has(runId));
|
|
108
|
+
};
|
|
109
|
+
const semaphore = createSemaphore(d.maxConcurrency ?? defaultConcurrency());
|
|
110
|
+
/** @type {Map<string, any>} */
|
|
111
|
+
const runs = new Map();
|
|
112
|
+
/** @type {Array<{runId:string, text:string}>} */
|
|
113
|
+
const queue = [];
|
|
114
|
+
/** @type {Set<() => void>} */
|
|
115
|
+
const waiters = new Set();
|
|
116
|
+
const wake = () => {
|
|
117
|
+
for (const w of [...waiters]) w();
|
|
118
|
+
waiters.clear();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} file
|
|
123
|
+
* @param {boolean} [named] a path the model named (scriptPath, workflow(ref)): the session's Read rules apply
|
|
124
|
+
* @returns {{ok:true, source:string, path:string}|{ok:false, error:string}}
|
|
125
|
+
*/
|
|
126
|
+
const readScript = (file, named = false) => {
|
|
127
|
+
if (named && d.checkRead) {
|
|
128
|
+
const verdict = d.checkRead({ path: file });
|
|
129
|
+
if (verdict.behavior !== "allow") return { ok: false, error: `reading ${file} is not allowed for this session (${verdict.reason})` };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const st = statSync(file);
|
|
133
|
+
if (!st.isFile()) return { ok: false, error: `${file} is not a file` };
|
|
134
|
+
if (st.size > MAX_SCRIPT_BYTES) return { ok: false, error: `${file} is ${st.size} bytes; a workflow script may be at most ${MAX_SCRIPT_BYTES}` };
|
|
135
|
+
return { ok: true, source: readFileSync(file, "utf8"), path: file };
|
|
136
|
+
} catch (e) {
|
|
137
|
+
return { ok: false, error: `cannot read ${file} (${e instanceof Error ? e.message : String(e)})` };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* workflow(ref): an earlier run's id, a path (relative to the project), or a
|
|
143
|
+
* name under <project>/.cohort/workflows or <project>/.claude/workflows.
|
|
144
|
+
* @param {string} ref
|
|
145
|
+
*/
|
|
146
|
+
const resolveRef = (ref) => {
|
|
147
|
+
if (isRunId(ref)) {
|
|
148
|
+
const dir = findRunDir(d.sessionsDir, d.sessionId, ref, { cwd: d.cwd });
|
|
149
|
+
return dir ? readScript(path.join(dir, "script.js")) : { ok: false, error: `there is no workflow run ${ref} in this session or this project` };
|
|
150
|
+
}
|
|
151
|
+
if (ref.includes("/") || /\.m?js$/.test(ref)) return readScript(path.resolve(d.cwd, ref), true);
|
|
152
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(ref)) return { ok: false, error: `workflow(${JSON.stringify(ref)}): not a path, a run id or a workflow name` };
|
|
153
|
+
for (const dir of [".cohort", ".claude"]) {
|
|
154
|
+
for (const ext of [".js", ".mjs"]) {
|
|
155
|
+
const file = path.join(d.cwd, dir, "workflows", `${ref}${ext}`);
|
|
156
|
+
if (existsSync(file)) return readScript(file, true);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { ok: false, error: `there is no workflow named "${ref}" in .cohort/workflows or .claude/workflows` };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** @param {any} run */
|
|
163
|
+
const view = (run) => {
|
|
164
|
+
const s = run.state;
|
|
165
|
+
return {
|
|
166
|
+
runId: run.id,
|
|
167
|
+
name: run.meta.name,
|
|
168
|
+
status: run.status,
|
|
169
|
+
scriptPath: run.scriptPath,
|
|
170
|
+
resumedFrom: run.resumedFrom,
|
|
171
|
+
agents: { ...s.agents },
|
|
172
|
+
phases: s.phases.map((/** @type any */ p) => ({ ...p })),
|
|
173
|
+
currentPhase: s.currentPhase,
|
|
174
|
+
budget: { total: s.budgetTotal, spent: s.spent },
|
|
175
|
+
worktreesKept: s.worktreesKept.map((/** @type any */ w) => ({ ...w })),
|
|
176
|
+
recentLogs: s.logs.slice(-STATUS_LOG_LINES),
|
|
177
|
+
...(run.error ? { error: run.error } : {}),
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** @param {any} run */
|
|
182
|
+
const resultText = (run) => {
|
|
183
|
+
const json = run.resultJson ?? "null";
|
|
184
|
+
if (json.length <= RESULT_INLINE_CHARS) return json;
|
|
185
|
+
return `${json.slice(0, RESULT_INLINE_CHARS)}\n… [the result is ${json.length} characters; the whole value is in ${run.resultPath}]`;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** @param {any} run */
|
|
189
|
+
const notificationText = (run) => {
|
|
190
|
+
const a = run.state.agents;
|
|
191
|
+
const counts = `${a.done} agent${a.done === 1 ? "" : "s"} done${a.replayed ? ` (${a.replayed} replayed from ${run.resumedFrom})` : ""}, ${a.failed} failed`;
|
|
192
|
+
if (run.status === "completed") return `[Workflow ${run.id} finished] "${run.meta.name}" completed: ${counts}. Its result:\n\n${resultText(run)}`;
|
|
193
|
+
return `[Workflow ${run.id} ended] "${run.meta.name}" did not finish: ${run.error ?? "unknown error"} (${counts}). The journal is kept: Workflow {"resumeFromRunId":"${run.id}"} runs it again, replaying the agent calls that completed.`;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** @param {any} run */
|
|
197
|
+
const consume = (run) => {
|
|
198
|
+
run.notified = true;
|
|
199
|
+
const i = queue.findIndex((q) => q.runId === run.id);
|
|
200
|
+
if (i !== -1) queue.splice(i, 1);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Start a run. Synchronous up to the point the run exists.
|
|
205
|
+
* @param {{script?:string, scriptPath?:string, args?:unknown, resumeFromRunId?:string}} input
|
|
206
|
+
* @param {{toolUseId?:string|null, signal?:AbortSignal}} [ctx]
|
|
207
|
+
* @returns {{ok:true, runId:string, scriptPath:string, name:string, resumedFrom:string|null, replayable:number}|{ok:false, error:string}}
|
|
208
|
+
*/
|
|
209
|
+
function start(input, ctx = {}) {
|
|
210
|
+
const hasScript = typeof input.script === "string";
|
|
211
|
+
const hasPath = typeof input.scriptPath === "string" && input.scriptPath.trim() !== "";
|
|
212
|
+
if (hasScript && hasPath) return { ok: false, error: "pass either script or scriptPath, not both" };
|
|
213
|
+
|
|
214
|
+
let replay = null;
|
|
215
|
+
let resumedFrom = null;
|
|
216
|
+
/** @type {{ok:true, source:string, path:string|null}|{ok:false, error:string}|null} */
|
|
217
|
+
let src = null;
|
|
218
|
+
if (input.resumeFromRunId !== undefined) {
|
|
219
|
+
const prev = input.resumeFromRunId;
|
|
220
|
+
if (!isRunId(prev)) return { ok: false, error: `resumeFromRunId "${prev}" is not a workflow run id (wf_ and 12 hex digits)` };
|
|
221
|
+
if (runs.get(prev)?.status === "running") return { ok: false, error: `workflow run ${prev} is still running; wait for it or stop it before resuming` };
|
|
222
|
+
const dir = findRunDir(d.sessionsDir, d.sessionId, prev, { cwd: d.cwd });
|
|
223
|
+
if (!dir) return { ok: false, error: `there is no workflow run ${prev} to resume (in this session, or started in this project directory)` };
|
|
224
|
+
const records = readJournal(path.join(dir, "journal.jsonl")).records;
|
|
225
|
+
// A journal with no `end` is a run that is still going, or one whose process died.
|
|
226
|
+
// Another live process may still be writing it: refuse rather than race it.
|
|
227
|
+
const summary = runSummary(records);
|
|
228
|
+
if (!summary.ended && stillRunning(prev, summary)) {
|
|
229
|
+
return { ok: false, error: `workflow run ${prev} has not ended and may still be running in ${summary.pid === pid ? "another runtime of this engine process" : "another process"} (pid ${summary.pid}); wait for it or stop that process before resuming` };
|
|
230
|
+
}
|
|
231
|
+
replay = createReplay(records);
|
|
232
|
+
resumedFrom = prev;
|
|
233
|
+
if (!hasScript && !hasPath) src = readScript(path.join(dir, "script.js"));
|
|
234
|
+
}
|
|
235
|
+
if (!src) {
|
|
236
|
+
if (hasScript) src = { ok: true, source: /** @type string */ (input.script), path: null };
|
|
237
|
+
else if (hasPath) src = readScript(path.resolve(d.cwd, /** @type string */ (input.scriptPath)), true);
|
|
238
|
+
else return { ok: false, error: "pass script (the JavaScript source), scriptPath, or resumeFromRunId" };
|
|
239
|
+
}
|
|
240
|
+
if (!src.ok) return src;
|
|
241
|
+
|
|
242
|
+
const parsed = parseWorkflowSource(src.source);
|
|
243
|
+
if (!parsed.ok) return parsed;
|
|
244
|
+
const compiled = compileWorkflow(parsed.body, `workflow:${parsed.meta.name}`);
|
|
245
|
+
if (!compiled.ok) return compiled;
|
|
246
|
+
const args = input.args === undefined ? null : input.args;
|
|
247
|
+
let argsJson;
|
|
248
|
+
try {
|
|
249
|
+
argsJson = JSON.stringify(args);
|
|
250
|
+
} catch {
|
|
251
|
+
return { ok: false, error: "args must be JSON" };
|
|
252
|
+
}
|
|
253
|
+
if (argsJson.length > MAX_ARGS_CHARS) return { ok: false, error: `args are ${argsJson.length} characters of JSON; the limit is ${MAX_ARGS_CHARS}` };
|
|
254
|
+
|
|
255
|
+
const runId = newRunId();
|
|
256
|
+
const dir = runDirFor(d.sessionsDir, d.sessionId, runId);
|
|
257
|
+
const journal = createJournal({ dir, now });
|
|
258
|
+
const scriptPath = path.join(dir, "script.js");
|
|
259
|
+
writeFileAtomic(scriptPath, src.source);
|
|
260
|
+
journal.append({ type: "run", runId, sessionId: d.sessionId, cwd: d.cwd, pid, nonce, startToken: startTokenOf(pid), name: parsed.meta.name, scriptPath, source: src.path, resumedFrom, args });
|
|
261
|
+
PROCESS_RUNNING_RUNS.add(runId);
|
|
262
|
+
|
|
263
|
+
const state = createRunState({ runId, meta: parsed.meta, budgetTotal: d.budgetTokens ?? null });
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const outer = ctx.signal;
|
|
266
|
+
const onOuter = () => controller.abort();
|
|
267
|
+
if (outer?.aborted) controller.abort();
|
|
268
|
+
else outer?.addEventListener("abort", onOuter, { once: true });
|
|
269
|
+
|
|
270
|
+
const run = { id: runId, meta: parsed.meta, dir, scriptPath, resultPath: path.join(dir, "result.json"), status: "running", state, resultJson: /** @type {string|null} */ (null), error: /** @type {string|null} */ (null), notified: false, stopRequested: false, controller, resumedFrom, done: Promise.resolve() };
|
|
271
|
+
runs.set(runId, run);
|
|
272
|
+
run.done = executeRun({
|
|
273
|
+
script: compiled.script,
|
|
274
|
+
meta: parsed.meta,
|
|
275
|
+
args,
|
|
276
|
+
state,
|
|
277
|
+
journal,
|
|
278
|
+
replay,
|
|
279
|
+
signal: controller.signal,
|
|
280
|
+
deps: {
|
|
281
|
+
cwd: d.cwd,
|
|
282
|
+
runDir: dir,
|
|
283
|
+
agents: d.agents,
|
|
284
|
+
runChild: d.runChild,
|
|
285
|
+
checkAgent: d.checkAgent,
|
|
286
|
+
semaphore,
|
|
287
|
+
lifetimeCap: d.lifetimeCap ?? LIFETIME_AGENT_CAP,
|
|
288
|
+
maxItems: d.maxItems,
|
|
289
|
+
resolveRef,
|
|
290
|
+
worktrees: d.worktrees,
|
|
291
|
+
parentToolUseId: ctx.toolUseId ?? null,
|
|
292
|
+
syncTimeoutMs: d.syncTimeoutMs,
|
|
293
|
+
busyTimeoutMs: d.busyTimeoutMs,
|
|
294
|
+
runTimeoutMs: d.runTimeoutMs ?? null,
|
|
295
|
+
budgetGraceMs: d.budgetGraceMs,
|
|
296
|
+
settleWaitMs: d.settleWaitMs,
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
.then(
|
|
300
|
+
(r) => {
|
|
301
|
+
if (r.ok) {
|
|
302
|
+
run.status = "completed";
|
|
303
|
+
run.resultJson = r.json;
|
|
304
|
+
writeFileAtomic(run.resultPath, r.json);
|
|
305
|
+
} else {
|
|
306
|
+
run.status = run.stopRequested || r.stopped ? "stopped" : "failed";
|
|
307
|
+
run.error = r.error;
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
(e) => {
|
|
311
|
+
run.status = run.stopRequested ? "stopped" : "failed";
|
|
312
|
+
run.error = `the workflow runtime failed unexpectedly (${e instanceof Error ? e.message : String(e)})`;
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
.finally(() => {
|
|
316
|
+
outer?.removeEventListener("abort", onOuter);
|
|
317
|
+
// executeRun settles only after every child it started has (host.mjs), so `end` is the last record.
|
|
318
|
+
journal.append({ type: "end", status: run.status, error: run.error, resultPath: run.status === "completed" ? run.resultPath : null, worktrees: state.worktreesKept });
|
|
319
|
+
PROCESS_RUNNING_RUNS.delete(runId);
|
|
320
|
+
if (!run.notified && run.status !== "stopped") {
|
|
321
|
+
queue.push({ runId, text: notificationText(run) });
|
|
322
|
+
d.onNotify?.();
|
|
323
|
+
}
|
|
324
|
+
wake();
|
|
325
|
+
});
|
|
326
|
+
return { ok: true, runId, scriptPath, name: parsed.meta.name, resumedFrom, replayable: replay?.size ?? 0 };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const drainNotifications = () => {
|
|
330
|
+
const out = queue.splice(0);
|
|
331
|
+
for (const n of out) {
|
|
332
|
+
const r = runs.get(n.runId);
|
|
333
|
+
if (r) r.notified = true;
|
|
334
|
+
}
|
|
335
|
+
return out.map((n) => n.text);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
start,
|
|
340
|
+
resolveRef,
|
|
341
|
+
/**
|
|
342
|
+
* Progress of a run; with `block`, wait up to `timeoutMs` for it to end. A
|
|
343
|
+
* finished run read here is not notified later.
|
|
344
|
+
* @param {string} runId @param {{block?:boolean, timeoutMs?:number, signal?:AbortSignal}} [o]
|
|
345
|
+
*/
|
|
346
|
+
async status(runId, { block = false, timeoutMs = 30_000, signal } = {}) {
|
|
347
|
+
const run = runs.get(runId);
|
|
348
|
+
if (!run) return { ok: false, error: `There is no workflow run "${runId}" in this session. Known: ${[...runs.keys()].join(", ") || "none"}.` };
|
|
349
|
+
if (run.status === "running" && block) await waitFor(run.done, timeoutMs, signal);
|
|
350
|
+
const v = view(run);
|
|
351
|
+
if (run.status === "running") return { ok: true, view: v, result: null };
|
|
352
|
+
consume(run);
|
|
353
|
+
return { ok: true, view: v, result: run.status === "completed" ? resultText(run) : null };
|
|
354
|
+
},
|
|
355
|
+
list: () => [...runs.values()].map(view),
|
|
356
|
+
/** W4-E2: is this run one of this runtime's (TaskOutput / TaskStop take run ids)? @param {string} runId */
|
|
357
|
+
has: (runId) => runs.has(runId),
|
|
358
|
+
/**
|
|
359
|
+
* W4-E2: a run this process does not hold, read from its journal — one started before an engine
|
|
360
|
+
* restart (this session, or this project). Ended: its status and result; not ended and its
|
|
361
|
+
* process gone: interrupted, with the way to resume it.
|
|
362
|
+
* @param {string} runId
|
|
363
|
+
* @returns {{ok:true, running:boolean, content:string}|{ok:false, error:string}}
|
|
364
|
+
*/
|
|
365
|
+
diskStatus(runId) {
|
|
366
|
+
const dir = findRunDir(d.sessionsDir, d.sessionId, runId, { cwd: d.cwd });
|
|
367
|
+
if (!dir) return { ok: false, error: `There is no workflow run "${runId}" in this session or this project.` };
|
|
368
|
+
const records = readJournal(path.join(dir, "journal.jsonl")).records;
|
|
369
|
+
const summary = runSummary(records);
|
|
370
|
+
const header = records.find((r) => r?.type === "run");
|
|
371
|
+
const name = typeof header?.name === "string" ? header.name : runId;
|
|
372
|
+
const end = [...records].reverse().find((r) => r?.type === "end");
|
|
373
|
+
if (end) {
|
|
374
|
+
let result = "";
|
|
375
|
+
if (end.status === "completed") {
|
|
376
|
+
const file = path.join(dir, "result.json"); // never a path the journal names
|
|
377
|
+
try {
|
|
378
|
+
const json = readFileSync(file, "utf8");
|
|
379
|
+
result = json.length <= RESULT_INLINE_CHARS ? json : `${json.slice(0, RESULT_INLINE_CHARS)}\n… [the result is ${json.length} characters; the whole value is in ${file}]`;
|
|
380
|
+
} catch {
|
|
381
|
+
result = `(the result could not be read from ${file})`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return { ok: true, running: false, content: `Workflow ${runId} ("${name}") ${end.status === "completed" ? "completed" : end.status === "stopped" ? "was stopped" : "failed"} in an earlier engine process${typeof end.error === "string" && end.error ? `: ${end.error}` : ""}.${result ? `\n\nResult:\n${result}` : ""}` };
|
|
385
|
+
}
|
|
386
|
+
if (stillRunning(runId, summary)) {
|
|
387
|
+
return { ok: true, running: true, content: `Workflow ${runId} ("${name}") has not ended and may still be running in ${summary.pid === pid ? "another runtime of this engine process" : "another engine process"} (pid ${summary.pid}).` };
|
|
388
|
+
}
|
|
389
|
+
return { ok: true, running: false, content: `Workflow ${runId} ("${name}") was interrupted: its engine process stopped before the run ended. Workflow {"resumeFromRunId":"${runId}"} runs it again, replaying the agent calls that completed.` };
|
|
390
|
+
},
|
|
391
|
+
drainNotifications,
|
|
392
|
+
/** @param {AbortSignal} [signal] */
|
|
393
|
+
async awaitNotifications(signal) {
|
|
394
|
+
if (queue.length) return drainNotifications();
|
|
395
|
+
if (![...runs.values()].some((r) => r.status === "running")) return [];
|
|
396
|
+
await new Promise((resolve) => {
|
|
397
|
+
const done = () => {
|
|
398
|
+
signal?.removeEventListener("abort", done);
|
|
399
|
+
waiters.delete(done);
|
|
400
|
+
resolve(undefined);
|
|
401
|
+
};
|
|
402
|
+
waiters.add(done);
|
|
403
|
+
if (signal?.aborted) done();
|
|
404
|
+
else signal?.addEventListener("abort", done, { once: true });
|
|
405
|
+
});
|
|
406
|
+
return drainNotifications();
|
|
407
|
+
},
|
|
408
|
+
hasRunning: () => [...runs.values()].some((r) => r.status === "running"),
|
|
409
|
+
/** Stop every running run and wait for it (bounded). */
|
|
410
|
+
async stopAll() {
|
|
411
|
+
const running = [...runs.values()].filter((r) => r.status === "running");
|
|
412
|
+
for (const r of running) {
|
|
413
|
+
r.stopRequested = true;
|
|
414
|
+
r.notified = true;
|
|
415
|
+
r.controller.abort();
|
|
416
|
+
}
|
|
417
|
+
await Promise.all(running.map((r) => waitFor(r.done, STOP_WAIT_MS)));
|
|
418
|
+
},
|
|
419
|
+
/** @param {string} runId */
|
|
420
|
+
async stop(runId) {
|
|
421
|
+
const run = runs.get(runId);
|
|
422
|
+
if (!run) return { ok: false, error: `There is no workflow run "${runId}".` };
|
|
423
|
+
if (run.status !== "running") return { ok: true, content: `Workflow ${runId} had already ${run.status === "completed" ? "completed" : "ended"}.` };
|
|
424
|
+
run.stopRequested = true;
|
|
425
|
+
consume(run);
|
|
426
|
+
run.controller.abort();
|
|
427
|
+
await waitFor(run.done, STOP_WAIT_MS);
|
|
428
|
+
return { ok: true, content: `Stopped workflow ${runId} ("${run.meta.name}").` };
|
|
429
|
+
},
|
|
430
|
+
semaphore,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** @param {Promise<unknown>} p @param {number} ms @param {AbortSignal} [signal] */
|
|
435
|
+
function waitFor(p, ms, signal) {
|
|
436
|
+
return new Promise((resolve) => {
|
|
437
|
+
const t = setTimeout(done, ms);
|
|
438
|
+
const onAbort = () => done();
|
|
439
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
440
|
+
p.then(done, done);
|
|
441
|
+
function done() {
|
|
442
|
+
clearTimeout(t);
|
|
443
|
+
signal?.removeEventListener("abort", onAbort);
|
|
444
|
+
resolve(undefined);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|