@cohortapp/agent-sdk 2.16.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 +23 -7
- 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/poller-daemon-setup.md +4 -1
- 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/context/budget.mjs +327 -0
- package/lib/context/history-scope.mjs +138 -0
- 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/economics.mjs +9 -0
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +95 -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 +82 -4
- package/lib/org/inbound/hydrate.mjs +555 -51
- 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 +15 -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 +104 -59
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +804 -157
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/lib/session-router.mjs +310 -42
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +121 -12
- package/scripts/daemon/responder.mjs +315 -146
- 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/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 -453
- 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 -300
- 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/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 -295
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -344
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -185
- 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
|
@@ -28,7 +28,13 @@ import { spawn } from "child_process";
|
|
|
28
28
|
import { join } from "path";
|
|
29
29
|
import { randomUUID } from "crypto";
|
|
30
30
|
import { checkRecentlySent, registerSent } from "./session-lock.mjs";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
routingKey as deriveRoutingKey,
|
|
33
|
+
createRouter,
|
|
34
|
+
routerItemFromDaemonItem,
|
|
35
|
+
claimSession,
|
|
36
|
+
releaseSession,
|
|
37
|
+
} from "./lib/session-router.mjs";
|
|
32
38
|
// Permission scoping (security CRITICAL / audit H1). sessionPermissionArgs()
|
|
33
39
|
// preserves the historical "--dangerously-skip-permissions" by default and only
|
|
34
40
|
// scopes tools when the operator opts in (MAESTRO_SCOPED_PERMISSIONS=1), so the
|
|
@@ -66,11 +72,22 @@ import { generateAck } from "./assurance.mjs";
|
|
|
66
72
|
// This path never renders the persona block, which is why the doctrine is a
|
|
67
73
|
// separately-exported constant rather than part of voiceRules().
|
|
68
74
|
import { MESSAGE_CRAFT } from "../../lib/identity/persona.mjs";
|
|
75
|
+
// A human's roll-call to the whole room. The reply-shaping block (≤ 5 lines,
|
|
76
|
+
// about myself only, no @mentions, no ack) lives with the vocabulary in
|
|
77
|
+
// broadcast.mjs so the session path (prompt-builder) says the same thing this
|
|
78
|
+
// path does. What the seat is DOING is not read from here: the one renderer of
|
|
79
|
+
// "what am I doing" is lib/session/current-work (loadCurrentWork below), and
|
|
80
|
+
// responder-single-source.test.mjs pins that no second one is imported again.
|
|
81
|
+
import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
|
|
82
|
+
import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
|
|
69
83
|
|
|
70
84
|
const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
|
|
71
85
|
const SONNET_MODEL = "claude-sonnet-4-6";
|
|
72
86
|
// Resolve claude against the agent's PATH (not launchd's bare env).
|
|
73
|
-
import { resolveClaudeBin
|
|
87
|
+
import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
|
|
88
|
+
import { buildSpawn } from "../../lib/runtime/adapter.mjs";
|
|
89
|
+
import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
|
|
90
|
+
import { isPrivateConversation, interactionWriteDirNames } from "../../lib/context/history-scope.mjs";
|
|
74
91
|
const CLAUDE_BIN = resolveClaudeBin();
|
|
75
92
|
const CLAUDE_CLI_TIMEOUT_MS = 60_000;
|
|
76
93
|
const SESSION_REGISTRY_PATH = join(AGENT_REPO_DIR, "state", "daemon", "session-router-registry.json");
|
|
@@ -101,45 +118,15 @@ function getRouter() {
|
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
/**
|
|
104
|
-
* Translate a daemon-shaped item into the source
|
|
105
|
-
*
|
|
121
|
+
* Translate a daemon-shaped item into the `{source, channel, …}` shape
|
|
122
|
+
* `session-router`'s `routingKey()` expects.
|
|
106
123
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* Returns null for items the router can't key (e.g. service we don't yet
|
|
112
|
-
* support, or missing required fields). Caller falls back to a fresh
|
|
113
|
-
* pre-minted UUID + EPHEMERAL semantics.
|
|
124
|
+
* The adapter itself now lives in `lib/session-router.mjs` beside the key
|
|
125
|
+
* function, because `dispatcher.mjs` needs the SAME translation: two copies
|
|
126
|
+
* would let one room be keyed two ways, which is the same thing as having no
|
|
127
|
+
* router. Exported here for the responder's own tests.
|
|
114
128
|
*/
|
|
115
|
-
|
|
116
|
-
if (!item || typeof item !== "object") return null;
|
|
117
|
-
|
|
118
|
-
if (item.service === "slack") {
|
|
119
|
-
const channel = item.channel || item.channel_id;
|
|
120
|
-
if (!channel) return null;
|
|
121
|
-
return {
|
|
122
|
-
source: "slack",
|
|
123
|
-
channel,
|
|
124
|
-
thread_ts: item.thread_id || item.thread_ts || null,
|
|
125
|
-
ts: item.ts || item.timestamp || null,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (item.service === "gmail") {
|
|
130
|
-
const tid = item.thread_id || item.threadId;
|
|
131
|
-
if (!tid) return null;
|
|
132
|
-
return { source: "gmail", thread_id: tid };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (item.service === "calendar") {
|
|
136
|
-
const eid = item.event_id || item.eventId;
|
|
137
|
-
if (!eid) return null;
|
|
138
|
-
return { source: "calendar", event_id: eid };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
129
|
+
export const deriveRouterItem = routerItemFromDaemonItem;
|
|
143
130
|
|
|
144
131
|
// Lazy token access — dotenv loads in daemon main before these are called
|
|
145
132
|
function getSlackToken() {
|
|
@@ -167,6 +154,82 @@ export function unmeasuredReasonFor({ parsed, timedOut, exitCode, parseError, ti
|
|
|
167
154
|
return "no-json-envelope";
|
|
168
155
|
}
|
|
169
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Build the value `runClaudeCLI` resolves with, from a clean-exit close.
|
|
159
|
+
*
|
|
160
|
+
* With a parsed JSON envelope the reply text is `result`. Without one the
|
|
161
|
+
* call FAILS CLOSED: `text` is "" and `error` names why. The raw stdout is
|
|
162
|
+
* never surfaced as text — it is a whole model turn, not a reply, and the
|
|
163
|
+
* caller's empty-text guard turns it into "nothing to send" rather than a
|
|
164
|
+
* message on the wire. `withheldChars` records how much was dropped so the
|
|
165
|
+
* operator can see the size of the turn that was kept off the wire.
|
|
166
|
+
*
|
|
167
|
+
* @param {{ parsed: object|null, rawStdout: string, exitCode: number|null }} a
|
|
168
|
+
* @returns {{ text: string, jsonResult: object|null, exitCode: number|null, error?: string, withheldChars?: number }}
|
|
169
|
+
*/
|
|
170
|
+
export function cliResultFor({ parsed, rawStdout, exitCode }) {
|
|
171
|
+
if (parsed) {
|
|
172
|
+
return { text: parsed.result ?? "", jsonResult: parsed, exitCode };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
text: "",
|
|
176
|
+
jsonResult: null,
|
|
177
|
+
exitCode,
|
|
178
|
+
error: "claude CLI output was not JSON; raw stdout withheld from the wire",
|
|
179
|
+
withheldChars: (rawStdout || "").length,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The tracker argv for a finished responder spawn — PURE, so the ledger row a
|
|
185
|
+
* given envelope produces is a unit test (engine-envelope-parity.test.mjs).
|
|
186
|
+
* `args` is everything after the tracker's script path.
|
|
187
|
+
*
|
|
188
|
+
* @param {{json:object|null, model:string, durationMs:number, exitCode:number, unmeasuredReason:string|null}} o
|
|
189
|
+
* @returns {{args:string[], measured:boolean, unmeasured:string|null}}
|
|
190
|
+
*/
|
|
191
|
+
export function responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason }) {
|
|
192
|
+
const usage = json && typeof json.usage === "object" && json.usage ? json.usage : null;
|
|
193
|
+
const inputTokens = usage ? Number(usage.input_tokens) : NaN;
|
|
194
|
+
const outputTokens = usage ? Number(usage.output_tokens) : NaN;
|
|
195
|
+
const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
|
|
196
|
+
|
|
197
|
+
// Map the CLI-resolved model id onto the tracker's coarse pricing class.
|
|
198
|
+
const resolved = json && typeof json.model === "string" ? json.model : model || "";
|
|
199
|
+
const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
|
|
200
|
+
|
|
201
|
+
const args = [
|
|
202
|
+
"record",
|
|
203
|
+
"--cadence", "responder",
|
|
204
|
+
"--source", "responder",
|
|
205
|
+
"--model", modelClass,
|
|
206
|
+
"--duration-ms", String(durationMs),
|
|
207
|
+
"--exit", String(exitCode),
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
let unmeasured = null;
|
|
211
|
+
if (measured) {
|
|
212
|
+
args.push("--input-tokens", String(inputTokens));
|
|
213
|
+
args.push("--output-tokens", String(outputTokens));
|
|
214
|
+
const cacheRead = Number(usage.cache_read_input_tokens);
|
|
215
|
+
if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
|
|
216
|
+
const cacheWrite = Number(usage.cache_creation_input_tokens);
|
|
217
|
+
if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
|
|
218
|
+
// A NUMBER, not anything Number() will coerce: the Cohort Engine writes
|
|
219
|
+
// `total_cost_usd: null` when not every request reported gateway cost,
|
|
220
|
+
// and `Number(null)` is 0 — which passed the finite/non-negative check
|
|
221
|
+
// and priced the session as $0.00 authoritative. Absent is the honest
|
|
222
|
+
// flag: the tracker then prices from tokens or records the row unpriced.
|
|
223
|
+
// (dispatcher.mjs#parseUsageFromText carries the same fix.)
|
|
224
|
+
const totalCost = json.total_cost_usd;
|
|
225
|
+
if (typeof totalCost === "number" && Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
|
|
226
|
+
} else {
|
|
227
|
+
unmeasured = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
|
|
228
|
+
args.push("--tokens-unknown", unmeasured);
|
|
229
|
+
}
|
|
230
|
+
return { args, measured, unmeasured };
|
|
231
|
+
}
|
|
232
|
+
|
|
170
233
|
/**
|
|
171
234
|
* Append a cost-ledger row for a responder session (source "responder").
|
|
172
235
|
*
|
|
@@ -199,40 +262,12 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
|
|
|
199
262
|
const trackerPath = join(AGENT_REPO_DIR, "scripts/cost/track-claude-usage.mjs");
|
|
200
263
|
if (!existsSync(trackerPath)) return;
|
|
201
264
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
|
|
206
|
-
|
|
207
|
-
// Map the CLI-resolved model id onto the tracker's coarse pricing class.
|
|
208
|
-
const resolved = json && typeof json.model === "string" ? json.model : model || "";
|
|
209
|
-
const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
|
|
210
|
-
|
|
211
|
-
const args = [
|
|
212
|
-
trackerPath, "record",
|
|
213
|
-
"--cadence", "responder",
|
|
214
|
-
"--source", "responder",
|
|
215
|
-
"--model", modelClass,
|
|
216
|
-
"--duration-ms", String(durationMs),
|
|
217
|
-
"--exit", String(exitCode),
|
|
218
|
-
];
|
|
219
|
-
|
|
220
|
-
if (measured) {
|
|
221
|
-
args.push("--input-tokens", String(inputTokens));
|
|
222
|
-
args.push("--output-tokens", String(outputTokens));
|
|
223
|
-
const cacheRead = Number(usage.cache_read_input_tokens);
|
|
224
|
-
if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
|
|
225
|
-
const cacheWrite = Number(usage.cache_creation_input_tokens);
|
|
226
|
-
if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
|
|
227
|
-
const totalCost = Number(json.total_cost_usd);
|
|
228
|
-
if (Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
|
|
229
|
-
} else {
|
|
230
|
-
const reason = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
|
|
231
|
-
args.push("--tokens-unknown", reason);
|
|
232
|
-
console.warn(`[responder] session usage unmeasured (${reason}) — recorded as unknown, not zero`);
|
|
265
|
+
const { args, unmeasured } = responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason });
|
|
266
|
+
if (unmeasured) {
|
|
267
|
+
console.warn(`[responder] session usage unmeasured (${unmeasured}) — recorded as unknown, not zero`);
|
|
233
268
|
}
|
|
234
269
|
|
|
235
|
-
spawn(process.execPath, args, {
|
|
270
|
+
spawn(process.execPath, [trackerPath, ...args], {
|
|
236
271
|
stdio: "ignore",
|
|
237
272
|
env: { ...process.env, AGENT_ROOT: AGENT_REPO_DIR, AGENT_DIR: AGENT_REPO_DIR },
|
|
238
273
|
}).unref();
|
|
@@ -257,37 +292,56 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
|
|
|
257
292
|
// the same UUID across spawns is the resume mechanism).
|
|
258
293
|
//
|
|
259
294
|
// @returns {Promise<{ text: string, jsonResult: object|null, exitCode: number }>}
|
|
295
|
+
/**
|
|
296
|
+
* The responder lane's spawn — binary, argv, env — from the runtime adapter
|
|
297
|
+
* (lane "responder"): --print <permissions> <bare MCP> --model <m>
|
|
298
|
+
* --append-system-prompt <sys> --output-format json [--session-id <id>].
|
|
299
|
+
* --output-format json is requested unconditionally (it is only valid with
|
|
300
|
+
* --print, which is always passed): a reply without the usage envelope cannot
|
|
301
|
+
* be priced, and the parse keeps a raw-text fallback for older CLIs. Env is the
|
|
302
|
+
* stock posture — stale ANTHROPIC_API_KEY/AUTH_TOKEN stripped so the CLI rides
|
|
303
|
+
* the seat's subscription credential; PATH augmented.
|
|
304
|
+
*
|
|
305
|
+
* W4-E1 (CF-44): `seat` is the seat's engine resolved once for this spawn
|
|
306
|
+
* (lib/runtime/seat-engine.mjs). A claude seat adds no fields, so its spawn is
|
|
307
|
+
* unchanged; an engine-cohort seat runs `cli.mjs run`, continuing --session-id.
|
|
308
|
+
* @param {{systemPrompt:string, userPrompt:string, model:string, sessionId?:string|null, env?:object, bin?:string, seat?:{fields:object}}} o
|
|
309
|
+
*/
|
|
310
|
+
export function responderSpawn({ systemPrompt, userPrompt, model, sessionId = null, env = process.env, bin = CLAUDE_BIN, seat = { fields: {} } }) {
|
|
311
|
+
return buildSpawn({
|
|
312
|
+
lane: "responder",
|
|
313
|
+
bin,
|
|
314
|
+
model,
|
|
315
|
+
systemPrompt,
|
|
316
|
+
prompt: userPrompt,
|
|
317
|
+
sessionId,
|
|
318
|
+
permissions: sessionPermissionArgs({ source: "responder" }),
|
|
319
|
+
mcp: { source: "responder" },
|
|
320
|
+
env,
|
|
321
|
+
...seat.fields,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
260
325
|
function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
326
|
+
// W4-E1: the seat's engine, once per spawn (a claude seat reads config/agent.json only).
|
|
327
|
+
return resolveSeatSpawn(AGENT_REPO_DIR).then((seat) => runSeatCLI(seat, systemPrompt, userPrompt, model, opts));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function runSeatCLI(seat, systemPrompt, userPrompt, model, opts = {}) {
|
|
261
331
|
const { sessionId = null, router = null, routingKey = null } = opts;
|
|
262
332
|
const startedAt = Date.now();
|
|
263
333
|
|
|
264
334
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"--model", model,
|
|
270
|
-
"--append-system-prompt", systemPrompt,
|
|
271
|
-
// --output-format json is only valid in combination with --print (per b1
|
|
272
|
-
// report). We always pass --print above, so this is safe.
|
|
273
|
-
//
|
|
274
|
-
// This used to be requested ONLY when a sessionId was supplied, which
|
|
275
|
-
// meant every non-routed reply ran with no usage envelope and therefore
|
|
276
|
-
// no way to price it. Every reply this daemon sends costs real money;
|
|
277
|
-
// asking for the envelope unconditionally is what makes it measurable.
|
|
278
|
-
// The parse below already has a raw-text fallback for older CLIs.
|
|
279
|
-
"--output-format", "json",
|
|
280
|
-
];
|
|
281
|
-
if (sessionId) {
|
|
282
|
-
args.push("--session-id", sessionId);
|
|
335
|
+
const spawnSpec = responderSpawn({ systemPrompt, userPrompt, model, sessionId, seat });
|
|
336
|
+
if (!spawnSpec.ok) {
|
|
337
|
+
rejectPromise(new Error(`claude CLI unavailable: ${spawnSpec.error.message}`));
|
|
338
|
+
return;
|
|
283
339
|
}
|
|
340
|
+
const args = spawnSpec.argv;
|
|
284
341
|
|
|
285
|
-
const proc = spawn(
|
|
342
|
+
const proc = spawn(spawnSpec.bin, args, {
|
|
286
343
|
stdio: ["pipe", "pipe", "pipe"],
|
|
287
|
-
|
|
288
|
-
// stale ANTHROPIC_API_KEY/AUTH_TOKEN inherited from the daemon env.
|
|
289
|
-
// Augment PATH so the subsession finds homebrew/nvm tools.
|
|
290
|
-
env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
|
|
344
|
+
env: spawnSpec.env,
|
|
291
345
|
});
|
|
292
346
|
|
|
293
347
|
let stdout = "";
|
|
@@ -350,11 +404,13 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
|
350
404
|
parsed = trimmed ? JSON.parse(trimmed) : null;
|
|
351
405
|
if (!parsed) parseError = new Error("no stdout");
|
|
352
406
|
} catch (err) {
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
//
|
|
407
|
+
// Fail closed: older CLIs or unexpected output shapes must not crash
|
|
408
|
+
// the daemon, but raw stdout of a failed parse is a whole model turn
|
|
409
|
+
// (tool chatter, thinking, the un-enveloped reply) and has reached the
|
|
410
|
+
// wire before. It is withheld; the caller sees empty text plus an
|
|
411
|
+
// error and treats it as nothing to send.
|
|
356
412
|
parseError = err;
|
|
357
|
-
console.
|
|
413
|
+
console.error(`[responder] claude CLI JSON parse failed (sessionId=${sessionId}): ${err.message} — raw stdout withheld from the wire (${trimmed.length} chars)`);
|
|
358
414
|
}
|
|
359
415
|
|
|
360
416
|
// Every reply this daemon sends is a paid Claude session — INCLUDING the
|
|
@@ -381,11 +437,7 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
|
381
437
|
return;
|
|
382
438
|
}
|
|
383
439
|
|
|
384
|
-
|
|
385
|
-
resolvePromise({ text: parsed.result ?? "", jsonResult: parsed, exitCode: code });
|
|
386
|
-
} else {
|
|
387
|
-
resolvePromise({ text: trimmed, jsonResult: null, exitCode: code });
|
|
388
|
-
}
|
|
440
|
+
resolvePromise(cliResultFor({ parsed, rawStdout: trimmed, exitCode: code }));
|
|
389
441
|
});
|
|
390
442
|
|
|
391
443
|
try {
|
|
@@ -438,10 +490,17 @@ function logInteraction(item, responseText, o = {}) {
|
|
|
438
490
|
? resolveSlackChannel(item)
|
|
439
491
|
: (item.channel_id || item.channel || null);
|
|
440
492
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
493
|
+
// The room always; the PERSON only when the exchange was actually private.
|
|
494
|
+
// This used to write both unconditionally, which made `dm-<slug>` a mixed
|
|
495
|
+
// store of private and public-room content — inert only for as long as
|
|
496
|
+
// nothing read it per-service. It is read now, so the gate belongs at both
|
|
497
|
+
// ends (`lib/context/history-scope.mjs`, design §5.5).
|
|
498
|
+
const dirs = interactionWriteDirNames({
|
|
499
|
+
channelId,
|
|
500
|
+
senderSlug,
|
|
501
|
+
isPrivate: isPrivateConversation(item),
|
|
502
|
+
}).map((name) => join(AGENT_REPO_DIR, "memory", "interactions", service, String(name)));
|
|
503
|
+
if (dirs.length === 0) return;
|
|
445
504
|
|
|
446
505
|
const incomingEntry = {
|
|
447
506
|
ts: item.ts || item.timestamp || new Date().toISOString(),
|
|
@@ -675,6 +734,7 @@ export async function loadConversationHistory(item, deps = {}) {
|
|
|
675
734
|
// the real generator, so behaviour is unchanged. The public senders call
|
|
676
735
|
// generateResponse() through this slot.
|
|
677
736
|
let _generateResponse;
|
|
737
|
+
|
|
678
738
|
/**
|
|
679
739
|
* Test seam: replace the response generator. Pass a function
|
|
680
740
|
* `(item, classResult) => Promise<string>` to force reply text; pass nothing to
|
|
@@ -699,11 +759,99 @@ export function setGenerateResponseForTests(fn) {
|
|
|
699
759
|
* and the flag is deliberately not kept "just in case", because keeping it is
|
|
700
760
|
* how it comes back.
|
|
701
761
|
*/
|
|
702
|
-
|
|
762
|
+
/**
|
|
763
|
+
* The bounded "what am I doing right now" block for a quick reply.
|
|
764
|
+
*
|
|
765
|
+
* The quick path is the one that answers a DM or a channel question IN the
|
|
766
|
+
* turn, and it carried nothing about the agent's own sessions: a "what are you
|
|
767
|
+
* doing right now?" was answered from the conversation alone, i.e. from
|
|
768
|
+
* nothing. lib/session/current-work reads the front door, its peers, the
|
|
769
|
+
* daemon's live children (with the board item each is linked to), the board
|
|
770
|
+
* items in progress and — as existence only — any interactive session a
|
|
771
|
+
* person has open on the box. ≤ 8 lines; "" when there is nothing to say.
|
|
772
|
+
*
|
|
773
|
+
* SCOPED TO THE SENDER'S CONVERSATION. A live session's summary is a
|
|
774
|
+
* restatement of someone's message; it is disclosed here only when it came
|
|
775
|
+
* from this item's conversation, and what is disclosable passes the sender's
|
|
776
|
+
* information barrier — the context compiler's `forbidsTextFor`, the same
|
|
777
|
+
* predicate the spawn path applies to history. Fail-open on the block (no
|
|
778
|
+
* block, not a failed reply); fail-CLOSED on the barrier (no predicate → the
|
|
779
|
+
* audience carries none and only same-conversation work is shown, which is
|
|
780
|
+
* the narrower answer). Injectable for tests.
|
|
781
|
+
*
|
|
782
|
+
* ON A ROLL-CALL this is the whole account of the seat — the front door, its
|
|
783
|
+
* peers, the daemon's live children, the board rows in progress / blocked /
|
|
784
|
+
* queued, the open handoffs — read ONCE, through the same audience and
|
|
785
|
+
* barrier as any other reply (a room never hears a private ask restated). A
|
|
786
|
+
* roll-call asks for `explicitEmpty`: when nothing is known the block still
|
|
787
|
+
* carries the header and the one line that says so, because a roll-call
|
|
788
|
+
* composed from no block is composed from invention.
|
|
789
|
+
*
|
|
790
|
+
* THIS is the path where an absent front door means "the daemon is
|
|
791
|
+
* answering on its own" — so this caller, and only this one, supplies
|
|
792
|
+
* {@link MAIN_ABSENT_NOTE}. The renderer is shared with `maestro session
|
|
793
|
+
* status`, `session_status` and the spawned session's context, from which
|
|
794
|
+
* that sentence would be false, so it does not carry the copy itself.
|
|
795
|
+
*/
|
|
796
|
+
export const MAIN_ABSENT_NOTE = "this reply comes from the background responder";
|
|
797
|
+
|
|
798
|
+
export async function loadCurrentWork(item, deps = {}) {
|
|
799
|
+
const rollCall = isRollCallItem(item);
|
|
800
|
+
try {
|
|
801
|
+
const mod = await import("../../lib/session/current-work.mjs");
|
|
802
|
+
const impl = deps.currentWorkBlock || mod.currentWorkBlock;
|
|
803
|
+
let forbidsText;
|
|
804
|
+
try {
|
|
805
|
+
const ff = deps.forbidsTextFor || (await import("./context-compiler.mjs")).forbidsTextFor;
|
|
806
|
+
const slug = item && item.sender ? String(item.sender).replace(/\s+/g, "-").toLowerCase() : null;
|
|
807
|
+
forbidsText = ff(slug);
|
|
808
|
+
} catch {
|
|
809
|
+
forbidsText = undefined;
|
|
810
|
+
}
|
|
811
|
+
const audience = mod.audienceForItem(item, { isPrivate: isPrivateConversation(item), forbidsText });
|
|
812
|
+
return (await impl(AGENT_REPO_DIR, { now: Date.now(), audience, explicitEmpty: rollCall, mainAbsentNote: MAIN_ABSENT_NOTE })) || "";
|
|
813
|
+
} catch {
|
|
814
|
+
return "";
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* The quick reply's user content. PURE — exported so the shape is pinned.
|
|
820
|
+
*
|
|
821
|
+
* The current-work block goes in under its own heading with the one rule the
|
|
822
|
+
* persona needs: describe it as your own work, and never quote a session name
|
|
823
|
+
* or id (they are internal — `session_status` says the same).
|
|
824
|
+
*/
|
|
825
|
+
export function buildQuickReplyUserContent({ item = {}, classResult = {}, conversationHistory = null, currentWork = "", rollCall = false } = {}) {
|
|
826
|
+
return [
|
|
827
|
+
`From: ${item.sender} (${item.sender_privilege || "unknown"})`,
|
|
828
|
+
`Via: ${item.service} / ${item.channel}`,
|
|
829
|
+
item.subject ? `Subject: ${item.subject}` : null,
|
|
830
|
+
conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
|
|
831
|
+
currentWork
|
|
832
|
+
? `\n${currentWork}\n(If asked what you are doing, answer from the lines above — as your own work, in your own words, board item ids included where a person could look one up. Never quote session names or ids.)`
|
|
833
|
+
: null,
|
|
834
|
+
`\nCurrent message:\n${item.content || "(empty)"}`,
|
|
835
|
+
// A roll-call's thread context is the room's other answers — not evidence
|
|
836
|
+
// about this seat, and exactly what the reply must not summarise.
|
|
837
|
+
!rollCall && item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
|
|
838
|
+
`\nClassification: ${classResult.summary}`,
|
|
839
|
+
].filter(Boolean).join("\n");
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* The real, CLI-backed answer generator. `deps` are test seams only —
|
|
844
|
+
* `{runCLI, currentWorkBlock, forbidsTextFor, loadConversationHistory}` — so
|
|
845
|
+
* the user content that reaches the model can be pinned without a spawn.
|
|
846
|
+
* Exported for that pin; production calls it through the `_generateResponse`
|
|
847
|
+
* slot with no deps.
|
|
848
|
+
*/
|
|
849
|
+
export async function realGenerateResponse(item, classResult, deps = {}) {
|
|
703
850
|
const preamble = loadPreamble();
|
|
704
851
|
const profile = loadUserProfile(item.sender);
|
|
705
852
|
// The classifier's recommended model (Opus for CEO, Sonnet for routine).
|
|
706
853
|
const model = classResult.model === "opus" ? "claude-opus-4-6" : SONNET_MODEL;
|
|
854
|
+
const rollCall = isRollCallItem(item);
|
|
707
855
|
|
|
708
856
|
const systemPrompt = `${preamble}
|
|
709
857
|
|
|
@@ -714,20 +862,20 @@ If it's informational, acknowledge appropriately.
|
|
|
714
862
|
Keep responses focused — 1-4 sentences for simple items, up to a short paragraph for more nuanced ones.
|
|
715
863
|
Match the sender's tone and urgency level.
|
|
716
864
|
${profile ? `\nSender profile:\n${profile}` : ""}
|
|
717
|
-
|
|
865
|
+
${rollCall ? `\n${collectiveInstructions({ intent: itemCollectiveIntent(item, isRollCallItem), respondents: item.respondents })}\n` : ""}
|
|
718
866
|
${MESSAGE_CRAFT}`;
|
|
719
867
|
|
|
720
|
-
|
|
868
|
+
// A roll-call is answered from what this seat KNOWS about itself — its main
|
|
869
|
+
// session, its peers and their purposes, its board items, the sessions the
|
|
870
|
+
// daemon has running — not from the room's history, which is other people's
|
|
871
|
+
// chatter and exactly what the answer must not summarise. ONE view of self:
|
|
872
|
+
// the audience-scoped current-work block (lib/session/current-work; the
|
|
873
|
+
// session registry, barrier-aware, so a room never hears a private ask
|
|
874
|
+
// restated), read once by loadCurrentWork on every path.
|
|
875
|
+
const conversationHistory = rollCall ? null : await (deps.loadConversationHistory || loadConversationHistory)(item);
|
|
876
|
+
const currentWork = await loadCurrentWork(item, deps);
|
|
721
877
|
|
|
722
|
-
const userContent =
|
|
723
|
-
`From: ${item.sender} (${item.sender_privilege || "unknown"})`,
|
|
724
|
-
`Via: ${item.service} / ${item.channel}`,
|
|
725
|
-
item.subject ? `Subject: ${item.subject}` : null,
|
|
726
|
-
conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
|
|
727
|
-
`\nCurrent message:\n${item.content || "(empty)"}`,
|
|
728
|
-
item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
|
|
729
|
-
`\nClassification: ${classResult.summary}`,
|
|
730
|
-
].filter(Boolean).join("\n");
|
|
878
|
+
const userContent = buildQuickReplyUserContent({ item, classResult, conversationHistory, currentWork, rollCall });
|
|
731
879
|
|
|
732
880
|
// (b2) Session-router decision. Compute the routing key from a daemon→router
|
|
733
881
|
// adapter view of the item. If the item can't be keyed (unknown service,
|
|
@@ -738,14 +886,25 @@ ${MESSAGE_CRAFT}`;
|
|
|
738
886
|
let sessionId = null;
|
|
739
887
|
if (routerItem) {
|
|
740
888
|
try {
|
|
741
|
-
|
|
742
|
-
const decision = router.route(
|
|
743
|
-
|
|
744
|
-
|
|
889
|
+
const candidateKey = deriveRoutingKey(routerItem);
|
|
890
|
+
const decision = router.route(candidateKey);
|
|
891
|
+
// One CLI process per key. `claimSession` is the same lease the dispatcher
|
|
892
|
+
// takes, in the same process, so a 60-second quick reply can never be
|
|
893
|
+
// handed the session id of a long agentic run that is still going — and
|
|
894
|
+
// two quick replies from one burst cannot share a transcript either.
|
|
895
|
+
if (claimSession(candidateKey)) {
|
|
896
|
+
key = candidateKey;
|
|
897
|
+
if (decision.decision === "RESUME" && decision.resumeId) {
|
|
898
|
+
sessionId = decision.resumeId;
|
|
899
|
+
} else {
|
|
900
|
+
// EPHEMERAL or EPHEMERAL_REPLACE — pre-mint a fresh UUID. Reusing
|
|
901
|
+
// the same key on next call (with a different sessionId) is fine;
|
|
902
|
+
// touch() will overwrite the registry entry.
|
|
903
|
+
sessionId = randomUUID();
|
|
904
|
+
}
|
|
745
905
|
} else {
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
// touch() will overwrite the registry entry.
|
|
906
|
+
// Already in flight: spawn cold, and stay out of the registry so the
|
|
907
|
+
// running session keeps ownership of the row.
|
|
749
908
|
sessionId = randomUUID();
|
|
750
909
|
}
|
|
751
910
|
} catch (err) {
|
|
@@ -757,30 +916,40 @@ ${MESSAGE_CRAFT}`;
|
|
|
757
916
|
}
|
|
758
917
|
}
|
|
759
918
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
919
|
+
// The claim is held across the spawn AND the registry write that follows it,
|
|
920
|
+
// and released in a `finally`: a throw anywhere in between must not leave the
|
|
921
|
+
// room's key claimed forever, which would silently disable continuity for it.
|
|
922
|
+
try {
|
|
923
|
+
const cliResult = await (deps.runCLI || runClaudeCLI)(systemPrompt, userContent, model, {
|
|
924
|
+
sessionId,
|
|
925
|
+
router: key ? router : null,
|
|
926
|
+
routingKey: key,
|
|
927
|
+
});
|
|
765
928
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
929
|
+
const text = (cliResult.text || "").trim();
|
|
930
|
+
if (!text) {
|
|
931
|
+
// Empty text is "nothing to send" — including the fail-closed non-JSON
|
|
932
|
+
// path, where the raw model turn was withheld. Throwing here is what
|
|
933
|
+
// keeps it off the wire; the reason rides along for the log.
|
|
934
|
+
throw new Error(`claude CLI returned empty result text in generateResponse${cliResult.error ? ` (${cliResult.error}; ${cliResult.withheldChars ?? 0} chars withheld)` : ""}`);
|
|
935
|
+
}
|
|
770
936
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
937
|
+
// (b3) On success, touch the registry with the CLI-resolved session_id so
|
|
938
|
+
// the next call routed to this key gets a RESUME decision. Skip touch on
|
|
939
|
+
// is_error responses or when JSON parsing failed (legacy fallback path).
|
|
940
|
+
if (key && cliResult.jsonResult && cliResult.jsonResult.is_error !== true) {
|
|
941
|
+
const claudeSessionId = cliResult.jsonResult.session_id || sessionId;
|
|
942
|
+
try {
|
|
943
|
+
await router.touch(key, { claudeSessionId, model });
|
|
944
|
+
} catch (err) {
|
|
945
|
+
console.warn(`[responder] router.touch failed for ${key}: ${err.message}`);
|
|
946
|
+
}
|
|
780
947
|
}
|
|
781
|
-
}
|
|
782
948
|
|
|
783
|
-
|
|
949
|
+
return text;
|
|
950
|
+
} finally {
|
|
951
|
+
if (key) releaseSession(key);
|
|
952
|
+
}
|
|
784
953
|
}
|
|
785
954
|
|
|
786
955
|
// Default the generator slot to the real CLI-backed implementation. (Declared
|