@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
|
@@ -14,7 +14,16 @@ const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.ur
|
|
|
14
14
|
// --verify` reads `sdk_version` off the dashboard to tell the two apart. The
|
|
15
15
|
// order of trust (sdk-version.mjs): the manifest of the copied tree, then
|
|
16
16
|
// node_modules, then the package this file ships in.
|
|
17
|
-
|
|
17
|
+
//
|
|
18
|
+
// The dashboard carries the INSTALLED version and the verdict beside it,
|
|
19
|
+
// because the number on its own cannot be checked. A seat reported 2.18.0 to
|
|
20
|
+
// the org off its manifest while node_modules already held 2.18.1: both
|
|
21
|
+
// numbers true, the disagreement the only interesting fact, and nothing on the
|
|
22
|
+
// seat or in the org said it. `sdk_version_agrees` is a real tri-state —
|
|
23
|
+
// `unknown` when the running version was INFERRED from the installed one, so
|
|
24
|
+
// equality by fallback can never be read as a clean hop.
|
|
25
|
+
const SDK = resolveRunningSdkVersion({ agentDir: AGENT_REPO_DIR, ownPackagePath: join(new URL(".", import.meta.url).pathname, "../../package.json") });
|
|
26
|
+
const DAEMON_SDK_VERSION = SDK.version;
|
|
18
27
|
|
|
19
28
|
let stats = {
|
|
20
29
|
daemon_start: new Date().toISOString(),
|
|
@@ -58,6 +67,10 @@ export function writeHealthDashboard() {
|
|
|
58
67
|
uptime_s: Math.floor((Date.now() - new Date(stats.daemon_start).getTime()) / 1000),
|
|
59
68
|
pid: process.pid,
|
|
60
69
|
sdk_version: DAEMON_SDK_VERSION,
|
|
70
|
+
sdk_version_source: SDK.runningSource,
|
|
71
|
+
sdk_version_installed: SDK.installed,
|
|
72
|
+
sdk_version_agrees: SDK.agrees === null ? "unknown" : String(SDK.agrees),
|
|
73
|
+
sdk_version_drift: SDK.drift,
|
|
61
74
|
...stats,
|
|
62
75
|
dispatcher,
|
|
63
76
|
};
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* explicitly rejects flock — "no stale lock files if the daemon dies".
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { promises as fsp } from "fs";
|
|
22
|
+
import { promises as fsp, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
23
23
|
import { dirname } from "path";
|
|
24
24
|
|
|
25
25
|
// canonicalizeSlackChannel exists in some installs of scripts/daemon/session-lock.mjs.
|
|
@@ -41,6 +41,122 @@ try {
|
|
|
41
41
|
|
|
42
42
|
const EMPTY_REGISTRY = () => ({ sessions: {}, lru: [] });
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Routing keys with a CLI process live on them right now, in THIS daemon.
|
|
46
|
+
*
|
|
47
|
+
* A registry row only becomes "live" when a session CLOSES cleanly, so the
|
|
48
|
+
* registry alone cannot answer "is something already running on this key". It
|
|
49
|
+
* did not have to before: every dispatch minted its own `randomUUID()`, so two
|
|
50
|
+
* concurrent turns in one room were two unrelated sessions. Now they share a
|
|
51
|
+
* key, and `#capital-formation` — 9,187 messages in 21 days, ~38 an hour,
|
|
52
|
+
* TOP-LEVEL, so `agent-daemon.mjs`'s thread lock (which needs a thread id, or a
|
|
53
|
+
* DM) does not cover it — delivers bursts of three and four at a time. Without
|
|
54
|
+
* this set, all of them would resume the same id: N `claude --print
|
|
55
|
+
* --session-id <same>` processes appending one transcript.
|
|
56
|
+
*
|
|
57
|
+
* So: a key that is in flight never RESUMEs. The later turns spawn cold, which
|
|
58
|
+
* is exactly the behaviour being replaced, and continuity resumes on the next
|
|
59
|
+
* turn after the room goes quiet. Process-local by construction — the lease is
|
|
60
|
+
* about OUR child processes, and a stale entry cannot outlive the process that
|
|
61
|
+
* holds it.
|
|
62
|
+
*/
|
|
63
|
+
const inFlightKeys = new Set();
|
|
64
|
+
|
|
65
|
+
/** Claim a key for a spawn. Returns false when something already holds it. */
|
|
66
|
+
export function claimSession(key) {
|
|
67
|
+
const k = String(key || "");
|
|
68
|
+
if (!k || inFlightKeys.has(k)) return false;
|
|
69
|
+
inFlightKeys.add(k);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Release a claimed key. Safe to call twice, and for a key never claimed. */
|
|
74
|
+
export function releaseSession(key) {
|
|
75
|
+
inFlightKeys.delete(String(key || ""));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Is a CLI process live on this key in this daemon? */
|
|
79
|
+
export function isSessionInFlight(key) {
|
|
80
|
+
return inFlightKeys.has(String(key || ""));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** For tests: forget every claim. */
|
|
84
|
+
export function _resetInFlightForTests() {
|
|
85
|
+
inFlightKeys.clear();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Services whose routing key is `<source>:<channel>[:<thread>]`.
|
|
90
|
+
*
|
|
91
|
+
* Extend by adding a name here, not by editing the key function — a new channel
|
|
92
|
+
* adapter with the same room+thread shape needs nothing else.
|
|
93
|
+
*/
|
|
94
|
+
export const CONVERSATION_SOURCES = new Set(["cohort", "telegram", "whatsapp", "orgmail"]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Translate a DAEMON-shaped item into the `{source, …}` view {@link routingKey}
|
|
98
|
+
* expects. PURE.
|
|
99
|
+
*
|
|
100
|
+
* Daemon items are `{service, channel_id, thread_id, …}`; the router's key
|
|
101
|
+
* function was specced against `{source, channel, thread_ts, …}` (memo §4.2).
|
|
102
|
+
* This is the seam between them, and it lives HERE rather than in one caller so
|
|
103
|
+
* the responder and the dispatcher cannot key the same conversation two ways —
|
|
104
|
+
* which would give one room two sessions and defeat the whole mechanism.
|
|
105
|
+
*
|
|
106
|
+
* Returns null for an item the router cannot key (a service with no shape, or a
|
|
107
|
+
* missing channel/thread). The caller then mints a fresh id, exactly as before.
|
|
108
|
+
*
|
|
109
|
+
* @param {object} item
|
|
110
|
+
* @returns {object|null}
|
|
111
|
+
*/
|
|
112
|
+
export function routerItemFromDaemonItem(item) {
|
|
113
|
+
if (!item || typeof item !== "object") return null;
|
|
114
|
+
|
|
115
|
+
if (item.service === "slack") {
|
|
116
|
+
const channel = item.channel || item.channel_id;
|
|
117
|
+
if (!channel) return null;
|
|
118
|
+
return {
|
|
119
|
+
source: "slack",
|
|
120
|
+
channel,
|
|
121
|
+
thread_ts: item.thread_id || item.thread_ts || null,
|
|
122
|
+
ts: item.ts || item.timestamp || null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (item.service === "gmail") {
|
|
127
|
+
const tid = item.thread_id || item.threadId;
|
|
128
|
+
if (!tid) return null;
|
|
129
|
+
return { source: "gmail", thread_id: tid };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (item.service === "calendar") {
|
|
133
|
+
const eid = item.event_id || item.eventId;
|
|
134
|
+
if (!eid) return null;
|
|
135
|
+
return { source: "calendar", event_id: eid };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Cohort and the other room+thread services. THIS USED TO RETURN NULL FOR
|
|
139
|
+
// COHORT, so every turn in the agent's own workspace fell back to a fresh
|
|
140
|
+
// pre-minted UUID and started cold — the session router existed and the one
|
|
141
|
+
// surface the owner actually uses never reached it (design §3 R10).
|
|
142
|
+
//
|
|
143
|
+
// `channel_id` ONLY, never `channel`: `item.channel` is the display label
|
|
144
|
+
// (`task/<title>`, `doc/<name>`), and keying a session on a label collides
|
|
145
|
+
// two rooms that happen to share a title. A surface with no room id simply
|
|
146
|
+
// has no session to continue.
|
|
147
|
+
if (CONVERSATION_SOURCES.has(item.service)) {
|
|
148
|
+
const channel = item.channel_id;
|
|
149
|
+
if (!channel) return null;
|
|
150
|
+
return {
|
|
151
|
+
source: item.service,
|
|
152
|
+
channel,
|
|
153
|
+
thread_root_id: item.thread_root_id || item.thread_id || null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
44
160
|
/**
|
|
45
161
|
* Pure key-derivation function (memo §4.2 table).
|
|
46
162
|
*
|
|
@@ -71,6 +187,25 @@ export function routingKey(item) {
|
|
|
71
187
|
return `gmail:${tid}`;
|
|
72
188
|
}
|
|
73
189
|
|
|
190
|
+
// Conversation-shaped services: one ROOM, optionally one thread inside it.
|
|
191
|
+
//
|
|
192
|
+
// `cohort` used to fall through to the throw below, so every Cohort turn was
|
|
193
|
+
// keyed by nothing, routed by nothing, and spawned cold — the agent's own
|
|
194
|
+
// workspace was the one surface with no continuity at all (design §3 R10).
|
|
195
|
+
// telegram / whatsapp / orgmail have exactly the same shape and were in the
|
|
196
|
+
// same position.
|
|
197
|
+
//
|
|
198
|
+
// The room is the key when there is no thread, for the same reason a Slack DM
|
|
199
|
+
// is: a room with no threading IS one conversation. A thread root narrows it,
|
|
200
|
+
// so two threads in a busy channel do not share a session and leak each
|
|
201
|
+
// other's context.
|
|
202
|
+
if (CONVERSATION_SOURCES.has(source)) {
|
|
203
|
+
const channel = item.channel || item.channel_id || "";
|
|
204
|
+
if (!channel) throw new Error(`routingKey: ${source} item missing channel`);
|
|
205
|
+
const thread = item.thread_root_id || item.threadRootId || item.thread_id || item.thread_ts || "";
|
|
206
|
+
return thread ? `${source}:${channel}:${thread}` : `${source}:${channel}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
74
209
|
if (source === "calendar") {
|
|
75
210
|
const eid = item.event_id || item.eventId;
|
|
76
211
|
if (!eid) throw new Error("routingKey: calendar item missing event_id");
|
|
@@ -91,6 +226,51 @@ export function routingKey(item) {
|
|
|
91
226
|
throw new Error(`routingKey: unknown source "${source}"`);
|
|
92
227
|
}
|
|
93
228
|
|
|
229
|
+
/**
|
|
230
|
+
* THE routing decision, as a pure function of the entry and the clock.
|
|
231
|
+
*
|
|
232
|
+
* Extracted so the async router (responder.mjs) and the SYNCHRONOUS one
|
|
233
|
+
* (dispatcher.mjs, whose spawn path is not async) cannot drift: one rule, two
|
|
234
|
+
* IO shells. Every branch is the memo §4.4 table.
|
|
235
|
+
*
|
|
236
|
+
* @param {object|undefined} entry the registry row for this key
|
|
237
|
+
* @param {{now:number, ttlSeconds:number}} o
|
|
238
|
+
* @returns {{decision:"EPHEMERAL"|"EPHEMERAL_REPLACE"|"RESUME", resumeId:string|null}}
|
|
239
|
+
*/
|
|
240
|
+
export function decideRoute(entry, { now, ttlSeconds }) {
|
|
241
|
+
if (!entry) return { decision: "EPHEMERAL", resumeId: null };
|
|
242
|
+
if (now - entry.last_used_at > ttlSeconds * 1000) return { decision: "EPHEMERAL_REPLACE", resumeId: null };
|
|
243
|
+
if (entry.status !== "live") return { decision: "EPHEMERAL_REPLACE", resumeId: null };
|
|
244
|
+
if (entry.last_exit_code !== 0) return { decision: "EPHEMERAL_REPLACE", resumeId: null };
|
|
245
|
+
return { decision: "RESUME", resumeId: entry.claude_session_id };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Build the registry row for a touched key. Pure — {@link touchEntry} is the
|
|
250
|
+
* only place the row's shape is decided, for both IO shells.
|
|
251
|
+
*/
|
|
252
|
+
export function touchEntry(existing, { key, claudeSessionId, daemonSessionId, model, now, ttlSeconds }) {
|
|
253
|
+
return {
|
|
254
|
+
daemon_session_id: daemonSessionId || (existing && existing.daemon_session_id) || null,
|
|
255
|
+
claude_session_id: claudeSessionId ?? (existing && existing.claude_session_id) ?? null,
|
|
256
|
+
key,
|
|
257
|
+
model: model || (existing && existing.model) || null,
|
|
258
|
+
created_at: (existing && existing.created_at) || new Date(now).toISOString(),
|
|
259
|
+
last_used_at: now,
|
|
260
|
+
ttl_seconds: ttlSeconds,
|
|
261
|
+
status: "live",
|
|
262
|
+
last_exit_code: 0,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Coerce a parsed blob into the registry shape. Shared by both IO shells. */
|
|
267
|
+
function asRegistry(parsed) {
|
|
268
|
+
if (!parsed || typeof parsed !== "object") return EMPTY_REGISTRY();
|
|
269
|
+
if (!parsed.sessions || typeof parsed.sessions !== "object") parsed.sessions = {};
|
|
270
|
+
if (!Array.isArray(parsed.lru)) parsed.lru = [];
|
|
271
|
+
return parsed;
|
|
272
|
+
}
|
|
273
|
+
|
|
94
274
|
/**
|
|
95
275
|
* Read the registry from disk. Missing file → empty registry shape.
|
|
96
276
|
* Corrupted JSON also degrades to empty (memo §6: "Read failure on the
|
|
@@ -98,15 +278,17 @@ export function routingKey(item) {
|
|
|
98
278
|
*/
|
|
99
279
|
async function readRegistry(path) {
|
|
100
280
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
281
|
+
return asRegistry(JSON.parse(await fsp.readFile(path, "utf-8")));
|
|
282
|
+
} catch {
|
|
283
|
+
return EMPTY_REGISTRY();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** The same read, synchronously — {@link createRouterSync} and every re-read. */
|
|
288
|
+
function readRegistrySync(path) {
|
|
289
|
+
try {
|
|
290
|
+
return asRegistry(JSON.parse(readFileSync(path, "utf-8")));
|
|
291
|
+
} catch {
|
|
110
292
|
return EMPTY_REGISTRY();
|
|
111
293
|
}
|
|
112
294
|
}
|
|
@@ -151,8 +333,25 @@ export async function createRouter({
|
|
|
151
333
|
throw new Error("createRouter: registryPath is required");
|
|
152
334
|
}
|
|
153
335
|
|
|
154
|
-
//
|
|
336
|
+
// RE-READ ON EVERY OPERATION. This used to load the file once and keep the
|
|
337
|
+
// object for the daemon's life, which was safe for exactly as long as this
|
|
338
|
+
// router was the only writer. It is not any more: `dispatcher.mjs` now writes
|
|
339
|
+
// the SAME file through `createRouterSync`, and a cached writer here did two
|
|
340
|
+
// destructive things with it — `touch()` wrote its stale snapshot back, and
|
|
341
|
+
// the LRU sweep at the end of `touch()` DELETED every key absent from that
|
|
342
|
+
// snapshot, so one ordinary quick reply erased the dispatcher's live session
|
|
343
|
+
// rows. Worse in the other direction: this shell would answer RESUME from a
|
|
344
|
+
// cached entry the dispatcher had already recorded as killed.
|
|
345
|
+
//
|
|
346
|
+
// A re-read costs one small JSON parse per spawn, against a process launch.
|
|
347
|
+
// Continuity is not worth a correctness hole, and two shells over one file
|
|
348
|
+
// must agree about what is in it.
|
|
155
349
|
let registry = await readRegistry(registryPath);
|
|
350
|
+
/** Refresh from disk, so another writer's rows are never overwritten. */
|
|
351
|
+
function refresh() {
|
|
352
|
+
registry = readRegistrySync(registryPath);
|
|
353
|
+
return registry;
|
|
354
|
+
}
|
|
156
355
|
|
|
157
356
|
/**
|
|
158
357
|
* Decide RESUME / EPHEMERAL / EPHEMERAL_REPLACE for a key (memo §4.4).
|
|
@@ -162,24 +361,17 @@ export async function createRouter({
|
|
|
162
361
|
* @returns {{ decision: "EPHEMERAL"|"EPHEMERAL_REPLACE"|"RESUME", resumeId: string|null }}
|
|
163
362
|
*/
|
|
164
363
|
function route(key) {
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
364
|
+
// Never hand a running session's id to a second process — see inFlightKeys.
|
|
365
|
+
if (isSessionInFlight(key)) return { decision: "EPHEMERAL", resumeId: null };
|
|
366
|
+
const reg = refresh();
|
|
367
|
+
const entry = reg.sessions[key];
|
|
368
|
+
const out = decideRoute(entry, { now: now(), ttlSeconds });
|
|
369
|
+
if (out.decision === "RESUME") {
|
|
370
|
+
// Touch in-memory; persisted on next touch()/recordExit() write.
|
|
371
|
+
entry.last_used_at = now();
|
|
372
|
+
bumpLru(reg.lru, key);
|
|
174
373
|
}
|
|
175
|
-
|
|
176
|
-
return { decision: "EPHEMERAL_REPLACE", resumeId: null };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Touch in-memory; persisted on next touch()/recordExit() write.
|
|
180
|
-
entry.last_used_at = now();
|
|
181
|
-
bumpLru(registry.lru, key);
|
|
182
|
-
return { decision: "RESUME", resumeId: entry.claude_session_id };
|
|
374
|
+
return out;
|
|
183
375
|
}
|
|
184
376
|
|
|
185
377
|
/**
|
|
@@ -194,20 +386,12 @@ export async function createRouter({
|
|
|
194
386
|
*/
|
|
195
387
|
async function touch(key, { claudeSessionId, daemonSessionId, model } = {}) {
|
|
196
388
|
const ts = now();
|
|
197
|
-
|
|
389
|
+
// Merge onto what is on disk NOW, not onto a snapshot from process start.
|
|
390
|
+
// The LRU sweep below deletes keys, so a stale base does not merely lose an
|
|
391
|
+
// update — it removes another writer's live sessions.
|
|
392
|
+
refresh();
|
|
198
393
|
const existing = registry.sessions[key];
|
|
199
|
-
|
|
200
|
-
const entry = {
|
|
201
|
-
daemon_session_id: daemonSessionId || existing?.daemon_session_id || null,
|
|
202
|
-
claude_session_id: claudeSessionId ?? existing?.claude_session_id ?? null,
|
|
203
|
-
key,
|
|
204
|
-
model: model || existing?.model || null,
|
|
205
|
-
created_at: existing?.created_at || isoNow,
|
|
206
|
-
last_used_at: ts,
|
|
207
|
-
ttl_seconds: ttlSeconds,
|
|
208
|
-
status: "live",
|
|
209
|
-
last_exit_code: 0,
|
|
210
|
-
};
|
|
394
|
+
const entry = touchEntry(existing, { key, claudeSessionId, daemonSessionId, model, now: ts, ttlSeconds });
|
|
211
395
|
registry.sessions[key] = entry;
|
|
212
396
|
bumpLru(registry.lru, key);
|
|
213
397
|
|
|
@@ -231,6 +415,7 @@ export async function createRouter({
|
|
|
231
415
|
* next route() returns EPHEMERAL_REPLACE.
|
|
232
416
|
*/
|
|
233
417
|
async function recordExit(key, exitCode) {
|
|
418
|
+
refresh();
|
|
234
419
|
const entry = registry.sessions[key];
|
|
235
420
|
if (!entry) return; // No-op — key was never touched.
|
|
236
421
|
entry.last_exit_code = exitCode;
|
|
@@ -245,6 +430,7 @@ export async function createRouter({
|
|
|
245
430
|
* Returns the count evicted.
|
|
246
431
|
*/
|
|
247
432
|
async function evictExpired() {
|
|
433
|
+
refresh();
|
|
248
434
|
const ttlMs = ttlSeconds * 1000;
|
|
249
435
|
const cutoff = now() - ttlMs;
|
|
250
436
|
let evicted = 0;
|
|
@@ -267,8 +453,90 @@ export async function createRouter({
|
|
|
267
453
|
* the live registry by accident.
|
|
268
454
|
*/
|
|
269
455
|
function _readForTests() {
|
|
270
|
-
return JSON.parse(JSON.stringify(
|
|
456
|
+
return JSON.parse(JSON.stringify(refresh()));
|
|
271
457
|
}
|
|
272
458
|
|
|
273
459
|
return { route, touch, recordExit, evictExpired, _readForTests };
|
|
274
460
|
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* The SYNCHRONOUS face of the same registry.
|
|
465
|
+
*
|
|
466
|
+
* `dispatcher.mjs#spawnSession` builds its argv and calls `spawn` in one
|
|
467
|
+
* synchronous pass, reached from a synchronous `dispatch()`. Making that path
|
|
468
|
+
* async to await the router would ripple through the queue, the eviction path
|
|
469
|
+
* and every caller — so the router grows a sync shell over the SAME file, the
|
|
470
|
+
* SAME key function and the SAME pure decision ({@link decideRoute}). Two IO
|
|
471
|
+
* shells, one rule; a drift between them would be a bug in one place, not two.
|
|
472
|
+
*
|
|
473
|
+
* The registry is a single small JSON file and these calls happen once per
|
|
474
|
+
* spawn, so the blocking read is nanoseconds against a process launch. Writes
|
|
475
|
+
* keep the temp-file + rename pattern, so a concurrent reader never sees a
|
|
476
|
+
* half-written file.
|
|
477
|
+
*
|
|
478
|
+
* Every method is FAIL-OPEN: an unreadable or corrupt registry routes
|
|
479
|
+
* EPHEMERAL (a cold session, which is exactly today's behaviour), and a failed
|
|
480
|
+
* write is logged by the caller and forgotten. Session continuity is an
|
|
481
|
+
* optimisation; losing it must never cost the work.
|
|
482
|
+
*
|
|
483
|
+
* @param {object} opts same shape as {@link createRouter}
|
|
484
|
+
*/
|
|
485
|
+
export function createRouterSync({
|
|
486
|
+
registryPath,
|
|
487
|
+
ttlSeconds = 1800,
|
|
488
|
+
maxLiveSessions = 8,
|
|
489
|
+
now = () => Date.now(),
|
|
490
|
+
} = {}) {
|
|
491
|
+
if (!registryPath) throw new Error("createRouterSync: registryPath is required");
|
|
492
|
+
|
|
493
|
+
// Missing or corrupt: an empty registry, which routes EPHEMERAL. Same posture
|
|
494
|
+
// as the async shell — never throw on the spawn path.
|
|
495
|
+
const read = () => readRegistrySync(registryPath);
|
|
496
|
+
|
|
497
|
+
function write(registry) {
|
|
498
|
+
try {
|
|
499
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
500
|
+
const tmp = `${registryPath}.tmp.${process.pid}.${Date.now()}`;
|
|
501
|
+
writeFileSync(tmp, JSON.stringify(registry, null, 2), "utf-8");
|
|
502
|
+
renameSync(tmp, registryPath);
|
|
503
|
+
return true;
|
|
504
|
+
} catch {
|
|
505
|
+
// A registry we cannot persist costs continuity on the NEXT turn, nothing
|
|
506
|
+
// on this one. The session is already spawning.
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
/** @returns {{decision:string, resumeId:string|null}} */
|
|
513
|
+
route(key) {
|
|
514
|
+
// Never hand a running session's id to a second process — see inFlightKeys.
|
|
515
|
+
if (isSessionInFlight(key)) return { decision: "EPHEMERAL", resumeId: null };
|
|
516
|
+
return decideRoute(read().sessions[key], { now: now(), ttlSeconds });
|
|
517
|
+
},
|
|
518
|
+
touch(key, { claudeSessionId, daemonSessionId, model } = {}) {
|
|
519
|
+
const registry = read();
|
|
520
|
+
registry.sessions[key] = touchEntry(registry.sessions[key], {
|
|
521
|
+
key, claudeSessionId, daemonSessionId, model, now: now(), ttlSeconds,
|
|
522
|
+
});
|
|
523
|
+
bumpLru(registry.lru, key);
|
|
524
|
+
while (registry.lru.length > maxLiveSessions) {
|
|
525
|
+
const evictKey = registry.lru.shift();
|
|
526
|
+
if (evictKey && evictKey !== key) delete registry.sessions[evictKey];
|
|
527
|
+
}
|
|
528
|
+
for (const k of Object.keys(registry.sessions)) {
|
|
529
|
+
if (!registry.lru.includes(k)) delete registry.sessions[k];
|
|
530
|
+
}
|
|
531
|
+
return write(registry);
|
|
532
|
+
},
|
|
533
|
+
recordExit(key, exitCode) {
|
|
534
|
+
const registry = read();
|
|
535
|
+
const entry = registry.sessions[key];
|
|
536
|
+
if (!entry) return false;
|
|
537
|
+
entry.last_exit_code = exitCode;
|
|
538
|
+
if (exitCode !== 0) entry.status = "killed";
|
|
539
|
+
return write(registry);
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
@@ -39,6 +39,17 @@ process.env.MAESTRO_DAEMON = "1";
|
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
40
|
// 0. Environment — BEFORE anything reads a credential
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
|
+
// But first: the BOOT MARKER. The wrapper appends this process's stdout to
|
|
43
|
+
// logs/daemon/daemon-<start day>.log, which carries no per-line timestamps and
|
|
44
|
+
// is SHARED by every incarnation that starts on the same day — a crash at
|
|
45
|
+
// 09:00 and the healthy respawn at 09:01 write to the same file. This line is
|
|
46
|
+
// the first thing this incarnation prints, tagged with its pid, so a reader
|
|
47
|
+
// can split the file into "before this process" and "since this process":
|
|
48
|
+
// scripts/local-triggers/autoupdate.sh's health gate scans for fatal lines
|
|
49
|
+
// only AFTER `[DAEMON] boot pid=<its pid>`; without it a one-off crash earlier
|
|
50
|
+
// the same day read as "fatal since restart" every hour until midnight. The
|
|
51
|
+
// format is pinned by autoupdate.test.mjs — change both or neither.
|
|
52
|
+
console.log(`[DAEMON] boot pid=${process.pid} at=${new Date().toISOString()}`);
|
|
42
53
|
// The core daemon (agent-daemon.mjs, step 4) loads .env itself, but the org
|
|
43
54
|
// mesh connects at step 3b' — EARLIER in this file. Under launchd the process
|
|
44
55
|
// environment is bare, so the mesh was reading COHORT_API_KEY before anything
|
|
@@ -13,6 +13,12 @@ import { isEnabled as orgEnabled } from "../../lib/org/client.mjs";
|
|
|
13
13
|
import { recall as orgRecall } from "../../lib/org/knowledge.mjs";
|
|
14
14
|
import { outcomeSourceShareable } from "./session-outcomes.mjs";
|
|
15
15
|
import { withParallelism } from "../../lib/prompts/parallelism.mjs";
|
|
16
|
+
import { isPrivateConversation, historyDirNames } from "../../lib/context/history-scope.mjs";
|
|
17
|
+
// A human's roll-call to the whole room: the SAME reply-shaping block the
|
|
18
|
+
// quick-reply responder injects, so a roll-call that fell through to a session
|
|
19
|
+
// (quick reply failed transiently) is answered in the same shape.
|
|
20
|
+
import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
|
|
21
|
+
import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
|
|
16
22
|
|
|
17
23
|
const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
|
|
18
24
|
|
|
@@ -38,6 +44,74 @@ function loadAgent() {
|
|
|
38
44
|
// Legacy: Maximum lines of conversation history (used when DAEMON_CONTEXT_COMPILER is off)
|
|
39
45
|
const MAX_HISTORY_LINES = 30;
|
|
40
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Is the compiled-context path on?
|
|
49
|
+
*
|
|
50
|
+
* IT DEFAULTS ON, and that is the fix. The code read `=== "1"` while
|
|
51
|
+
* `.env.example:231` shipped `DAEMON_CONTEXT_COMPILER=1`, so which of two
|
|
52
|
+
* materially different context paths ran depended on whether the operator had
|
|
53
|
+
* copied the example env — an agent set up by hand got the legacy path and an
|
|
54
|
+
* agent set up from the template got the compiled one, with nothing anywhere
|
|
55
|
+
* saying so (design §3 R11).
|
|
56
|
+
*
|
|
57
|
+
* One path ships. The legacy path stays reachable for a seat that has to turn
|
|
58
|
+
* it off in a hurry (`DAEMON_CONTEXT_COMPILER=0`), which is why the flag is not
|
|
59
|
+
* simply deleted.
|
|
60
|
+
*
|
|
61
|
+
* A value that is neither a recognised on nor a recognised off is the dangerous
|
|
62
|
+
* case, because it selects between two materially different context paths and
|
|
63
|
+
* an operator who wrote `disabled` meant OFF. The word lists below are wide
|
|
64
|
+
* enough to cover what people actually write, and {@link contextCompilerSetting}
|
|
65
|
+
* reports the leftovers so the caller can say so out loud instead of resolving
|
|
66
|
+
* a typo in silence.
|
|
67
|
+
*
|
|
68
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
export function contextCompilerEnabled(env = process.env) {
|
|
72
|
+
return contextCompilerSetting(env).enabled;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Values that mean OFF. Wider than `"0"` because operators write words. */
|
|
76
|
+
const CONTEXT_COMPILER_OFF = Object.freeze(["0", "false", "off", "no", "n", "disabled", "disable", "none"]);
|
|
77
|
+
|
|
78
|
+
/** Values that mean ON. */
|
|
79
|
+
const CONTEXT_COMPILER_ON = Object.freeze(["1", "true", "on", "yes", "y", "enabled", "enable"]);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The flag, and whether the operator's word was one we know.
|
|
83
|
+
*
|
|
84
|
+
* PURE — the env arrives on the argument.
|
|
85
|
+
*
|
|
86
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
87
|
+
* @returns {{enabled:boolean, recognised:boolean, raw:string}}
|
|
88
|
+
*/
|
|
89
|
+
export function contextCompilerSetting(env = process.env) {
|
|
90
|
+
const raw = String((env && env.DAEMON_CONTEXT_COMPILER) ?? "").trim().toLowerCase();
|
|
91
|
+
if (!raw) return { enabled: true, recognised: true, raw };
|
|
92
|
+
if (CONTEXT_COMPILER_OFF.includes(raw)) return { enabled: false, recognised: true, raw };
|
|
93
|
+
if (CONTEXT_COMPILER_ON.includes(raw)) return { enabled: true, recognised: true, raw };
|
|
94
|
+
return { enabled: true, recognised: false, raw };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Warn once per process about a DAEMON_CONTEXT_COMPILER value we do not know. */
|
|
98
|
+
let warnedAboutContextCompiler = false;
|
|
99
|
+
function warnIfContextCompilerUnrecognised(env = process.env) {
|
|
100
|
+
if (warnedAboutContextCompiler) return;
|
|
101
|
+
const setting = contextCompilerSetting(env);
|
|
102
|
+
if (setting.recognised) return;
|
|
103
|
+
warnedAboutContextCompiler = true;
|
|
104
|
+
console.warn(
|
|
105
|
+
`[prompt-builder] DAEMON_CONTEXT_COMPILER="${setting.raw}" is not a value this reads. ` +
|
|
106
|
+
`Using the compiled context path. Set it to 0 to use the legacy path.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** For tests: allow the one-time warning to fire again. */
|
|
111
|
+
export function _resetContextCompilerWarning() {
|
|
112
|
+
warnedAboutContextCompiler = false;
|
|
113
|
+
}
|
|
114
|
+
|
|
41
115
|
// Org shared-knowledge injection (org enrollment). Bounded so a large recall can
|
|
42
116
|
// never blow up the prompt: at most this many facts, each truncated.
|
|
43
117
|
const ORG_RECALL_MAX_FACTS = 6;
|
|
@@ -603,8 +677,22 @@ function today() {
|
|
|
603
677
|
|
|
604
678
|
/**
|
|
605
679
|
* Load recent conversation history for a sender/channel from interaction logs.
|
|
606
|
-
*
|
|
607
|
-
*
|
|
680
|
+
*
|
|
681
|
+
* IT READS THE ITEM'S OWN SERVICE. `responder.mjs#logInteraction` files every
|
|
682
|
+
* exchange under `memory/interactions/<service>/…` and has done since the
|
|
683
|
+
* write-half was fixed; this read looked only under `…/slack/…`, so a Cohort,
|
|
684
|
+
* Telegram or WhatsApp conversation was written down and then never found
|
|
685
|
+
* (design §3 R12). The agent had the record and could not see it.
|
|
686
|
+
*
|
|
687
|
+
* Slack keeps its legacy `<sender-slug>` directory alongside the `dm-` one —
|
|
688
|
+
* historical logs live there and must not go dark on this change.
|
|
689
|
+
*
|
|
690
|
+
* AND IT READS ONLY WHAT THIS ROOM MAY SEE. `logInteraction` files every
|
|
691
|
+
* exchange under BOTH the room and `dm-<sender-slug>`; merging all of them into
|
|
692
|
+
* one block put a person's private DM sentence into the prompt built for a
|
|
693
|
+
* public channel the moment this read stopped being Slack-only. The gate is
|
|
694
|
+
* `lib/context/history-scope.mjs`, shared with the writer and with the compiled
|
|
695
|
+
* path so the three cannot drift (design §5.5, resource scope).
|
|
608
696
|
*/
|
|
609
697
|
function loadConversationHistory(item) {
|
|
610
698
|
if (!item) return null;
|
|
@@ -613,13 +701,14 @@ function loadConversationHistory(item) {
|
|
|
613
701
|
// Try multiple paths where interaction logs may live
|
|
614
702
|
const channelId = extractChannelId(item);
|
|
615
703
|
const senderSlug = item.sender ? item.sender.replace(/\s+/g, "-").toLowerCase() : null;
|
|
704
|
+
const service = String(item.service || "slack");
|
|
616
705
|
|
|
617
|
-
const candidateDirs =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
706
|
+
const candidateDirs = historyDirNames({
|
|
707
|
+
channelId,
|
|
708
|
+
senderSlug,
|
|
709
|
+
isPrivate: isPrivateConversation(item),
|
|
710
|
+
service,
|
|
711
|
+
}).map((name) => join(AGENT_REPO_DIR, "memory", "interactions", service, name));
|
|
623
712
|
|
|
624
713
|
const entries = [];
|
|
625
714
|
|
|
@@ -681,7 +770,7 @@ function loadConversationHistory(item) {
|
|
|
681
770
|
*/
|
|
682
771
|
function extractChannelId(item) {
|
|
683
772
|
if (!item) return null;
|
|
684
|
-
// Direct channel ID field
|
|
773
|
+
// Direct channel ID field — every non-Slack service sets this.
|
|
685
774
|
if (item.channel_id) return item.channel_id;
|
|
686
775
|
// From raw_ref format: "slack:D099N1JGKRQ:1775331885.690669"
|
|
687
776
|
if (item.raw_ref) {
|
|
@@ -984,6 +1073,25 @@ export async function buildPrompt(item, classResult, options = {}) {
|
|
|
984
1073
|
parts.push("");
|
|
985
1074
|
}
|
|
986
1075
|
|
|
1076
|
+
// 1d. COLLECTIVE shaping — a human addressed the whole room and every seat
|
|
1077
|
+
// answers for itself. Placed with the contact blocks above because it is the
|
|
1078
|
+
// same kind of instruction: what may and may not be SAID, before any work.
|
|
1079
|
+
//
|
|
1080
|
+
// The intent (roll-call / work / ambient) comes from `collective.mjs`, the
|
|
1081
|
+
// twin of hq's `work/collective.ts`, so the same message gets the same block
|
|
1082
|
+
// whether the seat is driven from here or from hq. A WORK broadcast is told
|
|
1083
|
+
// its board row already exists — otherwise the session's first instinct is to
|
|
1084
|
+
// open a second one.
|
|
1085
|
+
if (type === "inbox" && item && isRollCallItem(item)) {
|
|
1086
|
+
parts.push(
|
|
1087
|
+
collectiveInstructions({
|
|
1088
|
+
intent: itemCollectiveIntent(item, isRollCallItem),
|
|
1089
|
+
respondents: item.respondents,
|
|
1090
|
+
}),
|
|
1091
|
+
);
|
|
1092
|
+
parts.push("");
|
|
1093
|
+
}
|
|
1094
|
+
|
|
987
1095
|
// 2. Session context
|
|
988
1096
|
parts.push(`Date: ${date}`);
|
|
989
1097
|
parts.push(`Classification: priority=${classResult.priority}, action=${action}, category=${classResult.category || "unclassified"}`);
|
|
@@ -1007,7 +1115,8 @@ export async function buildPrompt(item, classResult, options = {}) {
|
|
|
1007
1115
|
// (context-compiler.mjs → loadOrgMemory), so skip here to avoid a duplicate
|
|
1008
1116
|
// recall + duplicated context in the same prompt. Backlog items pass their
|
|
1009
1117
|
// queueItem into compileContext (below) so entity coverage is preserved.
|
|
1010
|
-
|
|
1118
|
+
warnIfContextCompilerUnrecognised();
|
|
1119
|
+
if (!contextCompilerEnabled()) {
|
|
1011
1120
|
const orgBlock = await buildOrgKnowledgeBlock(item, classResult, queueItem);
|
|
1012
1121
|
if (orgBlock) {
|
|
1013
1122
|
parts.push(orgBlock);
|
|
@@ -1016,7 +1125,7 @@ export async function buildPrompt(item, classResult, options = {}) {
|
|
|
1016
1125
|
}
|
|
1017
1126
|
|
|
1018
1127
|
// 4. Compiled context (profile + history + disclosure boundaries + active sessions + sent messages)
|
|
1019
|
-
if (
|
|
1128
|
+
if (contextCompilerEnabled() && (type === "inbox" ? item : true)) {
|
|
1020
1129
|
try {
|
|
1021
1130
|
const { contextBlock } = await compileContext(
|
|
1022
1131
|
item || {},
|
|
@@ -1056,7 +1165,7 @@ export async function buildPrompt(item, classResult, options = {}) {
|
|
|
1056
1165
|
parts.push("");
|
|
1057
1166
|
|
|
1058
1167
|
// 6. User profile lookup — legacy only (when feature flag is off, profile is in compiled context)
|
|
1059
|
-
if (
|
|
1168
|
+
if (!contextCompilerEnabled() && type === "inbox" && item && item.sender) {
|
|
1060
1169
|
const profileName = item.sender.replace(/\s+/g, "-").toLowerCase();
|
|
1061
1170
|
parts.push(`Before responding, read memory/profiles/users/${profileName}.yaml for sender preferences, standing instructions, and relationship context. If the file does not exist, proceed without it.`);
|
|
1062
1171
|
parts.push("");
|