@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/context/real-path.mjs — where a path really leads (CF-19, W5-B).
|
|
3
|
+
*
|
|
4
|
+
* Permission path rules and the engine-config protection judge a file by the
|
|
5
|
+
* place a tool call would really touch, not by the name the model gave it: a
|
|
6
|
+
* symlink inside the working directory that points at ~/.ssh, /etc or
|
|
7
|
+
* ~/.claude/settings.json is judged as that target (lib/engine/permissions.mjs).
|
|
8
|
+
*
|
|
9
|
+
* `resolveRealPath(target)`:
|
|
10
|
+
* · an existing path → its realpath (every symlink on the way resolved);
|
|
11
|
+
* · a dangling symlink → the path its link text names, resolved again (a
|
|
12
|
+
* Write through it creates that file);
|
|
13
|
+
* · a path that does not exist yet → the realpath of its nearest existing
|
|
14
|
+
* ancestor joined with the rest (a new file under a symlinked directory);
|
|
15
|
+
* · a symlink loop, or a chain longer than the hop budget → null (nothing
|
|
16
|
+
* can be opened through it). Never throws.
|
|
17
|
+
*
|
|
18
|
+
* The file-system calls are injectable, so the walk is testable with fakes.
|
|
19
|
+
* Checking is not locking: a link swapped between the check and the tool's
|
|
20
|
+
* open is not caught (docs/engine/README.md, Permissions).
|
|
21
|
+
*
|
|
22
|
+
* @module lib/engine/context/real-path
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { lstatSync, readlinkSync, realpathSync } from "node:fs";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
|
|
28
|
+
/** How many path steps (links followed and ancestors walked) one resolution may take. */
|
|
29
|
+
export const MAX_RESOLVE_STEPS = 160;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} RealPathFs
|
|
33
|
+
* @property {(p:string)=>string} [realpath]
|
|
34
|
+
* @property {(p:string)=>{isSymbolicLink:()=>boolean}} [lstat]
|
|
35
|
+
* @property {(p:string)=>string} [readlink]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} target absolute (a relative path is resolved against process.cwd())
|
|
40
|
+
* @param {RealPathFs} [fs]
|
|
41
|
+
* @returns {string|null}
|
|
42
|
+
*/
|
|
43
|
+
export function resolveRealPath(target, fs = {}) {
|
|
44
|
+
const realpath = fs.realpath ?? ((/** @type string */ p) => realpathSync.native(p));
|
|
45
|
+
const lstat = fs.lstat ?? lstatSync;
|
|
46
|
+
const readlink = fs.readlink ?? ((/** @type string */ p) => readlinkSync(p, "utf8"));
|
|
47
|
+
let steps = 0;
|
|
48
|
+
/** @param {string} p @returns {string|null} */
|
|
49
|
+
const walk = (p) => {
|
|
50
|
+
if (++steps > MAX_RESOLVE_STEPS) return null;
|
|
51
|
+
try {
|
|
52
|
+
return realpath(p);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
if (/** @type {NodeJS.ErrnoException} */ (e)?.code === "ELOOP") return null;
|
|
55
|
+
/* ENOENT, ENOTDIR, EACCES: look at the path itself, then at its parent */
|
|
56
|
+
}
|
|
57
|
+
let isLink = false;
|
|
58
|
+
try {
|
|
59
|
+
isLink = lstat(p).isSymbolicLink();
|
|
60
|
+
} catch {
|
|
61
|
+
isLink = false;
|
|
62
|
+
}
|
|
63
|
+
if (isLink) {
|
|
64
|
+
let text;
|
|
65
|
+
try {
|
|
66
|
+
text = readlink(p);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return walk(path.resolve(path.dirname(p), text));
|
|
71
|
+
}
|
|
72
|
+
const parent = path.dirname(p);
|
|
73
|
+
if (parent === p) return p;
|
|
74
|
+
const real = walk(parent);
|
|
75
|
+
return real === null ? null : path.join(real, path.basename(p));
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
return walk(path.resolve(String(target)));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A resolver for the permission engine (`evaluatePermission({resolvePath})`).
|
|
86
|
+
* @param {RealPathFs} [fs]
|
|
87
|
+
* @returns {(p:string)=>string|null}
|
|
88
|
+
*/
|
|
89
|
+
export function createPathResolver(fs = {}) {
|
|
90
|
+
return (p) => resolveRealPath(p, fs);
|
|
91
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/context/secret-values.mjs — does an environment VALUE look like a
|
|
3
|
+
* credential, whatever its variable is called (CF-118, W5-B)?
|
|
4
|
+
*
|
|
5
|
+
* The name scrub (tools/bash.mjs SECRET_ENV and EXTENDED_SECRET_ENV) misses a
|
|
6
|
+
* credential whose name says nothing: `DATABASE_URL=postgres://app:pw@db/x`,
|
|
7
|
+
* `REDIS_URL`, `AMQP_URL`, `SENTRY_DSN`, a token stored as `UPSTREAM`. This
|
|
8
|
+
* module judges the value. `credentialValueKind(value, {name})` returns the
|
|
9
|
+
* first rule that matches, or null:
|
|
10
|
+
*
|
|
11
|
+
* url-userinfo scheme://user:PASSWORD@host… anywhere in the value — a
|
|
12
|
+
* non-empty password is required (`https://user@host` and
|
|
13
|
+
* `http://host:8080/` are not credentials)
|
|
14
|
+
* url-secret a URL carrying a signing or token query parameter
|
|
15
|
+
* (`?token=`, `?access_token=`, `?key=`, `?sig=`,
|
|
16
|
+
* `X-Amz-Signature=`, … with a value of 16+ characters), or a
|
|
17
|
+
* Slack or Discord webhook URL
|
|
18
|
+
* private-key a PEM private key block (`-----BEGIN … PRIVATE KEY-----`)
|
|
19
|
+
* token-prefix a vendor token shape: `sk-…`, `sk_live_…`, `ghp_…`/`gho_…`/
|
|
20
|
+
* `ghu_…`/`ghs_…`/`ghr_…`, `github_pat_…`, `glpat-…`,
|
|
21
|
+
* `xox[abpr]-…`, `xapp-…`, `AKIA…`/`ASIA…` (16 more), `AIza…`,
|
|
22
|
+
* `npm_…`, `hf_…`, and Cohort's own `cst_…` / `nlk_…`
|
|
23
|
+
* jwt three base64url parts, the first `eyJ…`
|
|
24
|
+
* high-entropy a run of 32+ characters of the base64/base64url/token
|
|
25
|
+
* alphabet holding lower case, upper case AND digits, with a
|
|
26
|
+
* Shannon entropy of at least 4.0 bits per character; a
|
|
27
|
+
* leading `word_` (≤ 12 letters: `session_`, `msg_`) is not
|
|
28
|
+
* counted in the length, so prefixed ids stay short
|
|
29
|
+
* hex the whole value is 32+ hex digits
|
|
30
|
+
*
|
|
31
|
+
* The thresholds, and why they are conservative. Random base64 of 24+ bytes
|
|
32
|
+
* (32 characters) measures ~4.4–5.0 bits per character; the ordinary values
|
|
33
|
+
* found in a seat's environment measure less or fail the character-class test:
|
|
34
|
+
* paths (`/`, `~` or `.` first; searched segment by segment), PATH-like lists
|
|
35
|
+
* (`:` separates), `NODE_OPTIONS` (no upper case), UUIDs (excluded by shape),
|
|
36
|
+
* subresource-integrity strings (`sha512-…`, excluded by prefix), prefixed ids
|
|
37
|
+
* (`session_01…`, prefix not counted). Hex is judged apart: a 40-character git
|
|
38
|
+
* SHA and a 32-character hex API key look the same, so a hex value is withheld
|
|
39
|
+
* unless its NAME says it is a hash or an id (`GIT_COMMIT`, `GITHUB_SHA`,
|
|
40
|
+
* `*_DIGEST`, `*_CHECKSUM`, `*_REV`, `*_ID`, `npm_package_gitHead`, …;
|
|
41
|
+
* {@link HASH_OR_ID_NAME_RE}). The same name exemption applies to the
|
|
42
|
+
* high-entropy rule — never to the URL, key, vendor-prefix or JWT rules, which
|
|
43
|
+
* name a credential by their shape alone.
|
|
44
|
+
*
|
|
45
|
+
* Not caught (a narrowing, not a boundary): a short or low-entropy password
|
|
46
|
+
* under an ordinary name (`DB_PW=hunter2`), a secret split across two variables
|
|
47
|
+
* each below the length threshold, a hex or random secret under a hash- or
|
|
48
|
+
* id-shaped name, a value encoded with an alphabet outside the token one
|
|
49
|
+
* (percent-encoding with `%`), and anything a command reads from a file.
|
|
50
|
+
* Base64- or hex-wrapped secrets of 24+ bytes ARE caught (by the high-entropy
|
|
51
|
+
* and hex rules).
|
|
52
|
+
*
|
|
53
|
+
* Pure: no I/O, no clock, no randomness.
|
|
54
|
+
*
|
|
55
|
+
* @module lib/engine/context/secret-values
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/** A value this long, of the token alphabet, with all three character classes, is measured. */
|
|
59
|
+
export const HIGH_ENTROPY_MIN_LENGTH = 32;
|
|
60
|
+
/** Shannon entropy per character at or above which such a run is a credential. */
|
|
61
|
+
export const HIGH_ENTROPY_MIN_BITS = 4.0;
|
|
62
|
+
/** Such a run reads as random, not as words, when its letters hold at most this share of vowels … */
|
|
63
|
+
export const MAX_RANDOM_VOWEL_SHARE = 0.3;
|
|
64
|
+
/** … or its digits fall in at least this many separate runs. */
|
|
65
|
+
export const MIN_RANDOM_DIGIT_RUNS = 3;
|
|
66
|
+
/** A whole value of this many hex digits or more is a credential unless its name is hash- or id-shaped. */
|
|
67
|
+
export const HEX_MIN_LENGTH = 32;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Names whose hex or high-entropy value is an identifier, not a credential:
|
|
71
|
+
* a whole `_`-separated word (`GIT_COMMIT`, `BUILD_ID`, `IMAGE_DIGEST`) or a
|
|
72
|
+
* camel-case tail (`npm_package_gitHead`).
|
|
73
|
+
*/
|
|
74
|
+
export const HASH_OR_ID_NAME_RE = /(?:^|_)(?:SHA\d*|COMMIT|REV|REVISION|HASH|DIGEST|CHECKSUM|FINGERPRINT|INTEGRITY|ETAG|ID|IDS|UUID|GUID|NONCE|TRACE|SPAN|VERSION|BUILD|HEAD|REF)(?:_|$)|(?:[a-z](?:Sha|Hash|Head|Commit|Id|Digest))$/;
|
|
75
|
+
|
|
76
|
+
const URL_USERINFO_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#@:]*:[^\s/?#@]+@[^\s/?#@]/i;
|
|
77
|
+
const URL_SECRET_PARAM_RE = /[?&](?:access_token|refresh_token|id_token|token|auth|api_?key|key|secret|client_secret|password|passwd|sig|signature|x-amz-signature|x-amz-credential|x-amz-security-token|x-goog-signature|x-goog-credential)=([^&#\s]{16,})/i;
|
|
78
|
+
const WEBHOOK_RE = /https:\/\/(?:hooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9/_-]{20,}|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_-]{20,})/i;
|
|
79
|
+
const PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
80
|
+
const JWT_RE = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/;
|
|
81
|
+
/** Vendor token shapes. Each is bounded on the left so a longer word containing it does not match. */
|
|
82
|
+
const TOKEN_PREFIX_RES = Object.freeze([
|
|
83
|
+
/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9_-]{20,}/, // OpenAI, Anthropic (sk-ant-…), DeepSeek, …
|
|
84
|
+
/(?<![A-Za-z0-9_])(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}/, // Stripe
|
|
85
|
+
/(?<![A-Za-z0-9_])gh[pousr]_[A-Za-z0-9]{30,}/, // GitHub
|
|
86
|
+
/(?<![A-Za-z0-9_])github_pat_[A-Za-z0-9_]{22,}/,
|
|
87
|
+
/(?<![A-Za-z0-9_-])glpat-[A-Za-z0-9_-]{20,}/, // GitLab
|
|
88
|
+
/(?<![A-Za-z0-9_-])xox[abpr]-[A-Za-z0-9-]{10,}/, // Slack
|
|
89
|
+
/(?<![A-Za-z0-9_-])xapp-\d-[A-Za-z0-9-]{10,}/,
|
|
90
|
+
/(?<![A-Za-z0-9])(?:AKIA|ASIA)[0-9A-Z]{16}(?![0-9A-Za-z])/, // AWS access key id
|
|
91
|
+
/(?<![A-Za-z0-9_-])AIza[0-9A-Za-z_-]{35}/, // Google API key
|
|
92
|
+
/(?<![A-Za-z0-9_])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/,
|
|
93
|
+
/(?<![A-Za-z0-9_])hf_[A-Za-z0-9]{30,}/, // Hugging Face
|
|
94
|
+
/(?<![A-Za-z0-9_])(?:cst|nlk)_[A-Za-z0-9_-]{16,}/, // Cohort gateway and org keys
|
|
95
|
+
]);
|
|
96
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
97
|
+
const HEX_RE = /^[0-9a-f]+$/i;
|
|
98
|
+
const TOKEN_ALPHABET_RE = /^[A-Za-z0-9+/=_.~-]+$/;
|
|
99
|
+
const INTEGRITY_PREFIX_RE = /^(?:sha1|sha224|sha256|sha384|sha512|md5)[-:]/i;
|
|
100
|
+
/** Separators between the candidate runs of a value (a `Bearer x`, `a=b;c=d`, a path's segments). */
|
|
101
|
+
const RUN_SPLIT_RE = /[\s,;:=&"'<>()[\]{}|@\\/]+/;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Shannon entropy of a string, in bits per character. Pure.
|
|
105
|
+
* @param {string} s
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
export function shannonBitsPerChar(s) {
|
|
109
|
+
if (s.length === 0) return 0;
|
|
110
|
+
/** @type {Map<string, number>} */
|
|
111
|
+
const counts = new Map();
|
|
112
|
+
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
113
|
+
let bits = 0;
|
|
114
|
+
for (const n of counts.values()) {
|
|
115
|
+
const p = n / s.length;
|
|
116
|
+
bits -= p * Math.log2(p);
|
|
117
|
+
}
|
|
118
|
+
return bits;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* One candidate run: long, of the token alphabet, all three classes, random enough.
|
|
123
|
+
* @param {string} run
|
|
124
|
+
*/
|
|
125
|
+
function looksRandom(run) {
|
|
126
|
+
if (!TOKEN_ALPHABET_RE.test(run) || UUID_RE.test(run) || INTEGRITY_PREFIX_RE.test(run)) return false;
|
|
127
|
+
const body = run.replace(/^[a-z]{1,12}_/, "");
|
|
128
|
+
if (body.length < HIGH_ENTROPY_MIN_LENGTH) return false;
|
|
129
|
+
if (!/[a-z]/.test(body) || !/[A-Z]/.test(body) || !/[0-9]/.test(body)) return false;
|
|
130
|
+
if (shannonBitsPerChar(body) < HIGH_ENTROPY_MIN_BITS) return false;
|
|
131
|
+
// Words, not noise: a camel-case identifier (`MyLongClassName2024Build`) has English's vowel share
|
|
132
|
+
// and its digits in one or two runs; random letters are ~19% vowels and random digits scatter.
|
|
133
|
+
const letters = body.replace(/[^A-Za-z]/g, "");
|
|
134
|
+
const vowelShare = (letters.match(/[aeiou]/gi)?.length ?? 0) / letters.length;
|
|
135
|
+
const digitRuns = body.match(/[0-9]+/g)?.length ?? 0;
|
|
136
|
+
return vowelShare <= MAX_RANDOM_VOWEL_SHARE || digitRuns >= MIN_RANDOM_DIGIT_RUNS;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @typedef {'url-userinfo'|'url-secret'|'private-key'|'token-prefix'|'jwt'|'high-entropy'|'hex'} CredentialValueKind
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Which credential shape a value has, if any. Pure.
|
|
145
|
+
* @param {unknown} value
|
|
146
|
+
* @param {{name?:string}} [o] the variable's name: a hash- or id-shaped name exempts the hex and high-entropy rules
|
|
147
|
+
* @returns {CredentialValueKind|null}
|
|
148
|
+
*/
|
|
149
|
+
export function credentialValueKind(value, { name = "" } = {}) {
|
|
150
|
+
if (typeof value !== "string" || value.length < 8) return null;
|
|
151
|
+
if (URL_USERINFO_RE.test(value)) return "url-userinfo";
|
|
152
|
+
if (value.includes("://") && (URL_SECRET_PARAM_RE.test(value) || WEBHOOK_RE.test(value))) return "url-secret";
|
|
153
|
+
if (PRIVATE_KEY_RE.test(value)) return "private-key";
|
|
154
|
+
if (TOKEN_PREFIX_RES.some((re) => re.test(value))) return "token-prefix";
|
|
155
|
+
if (JWT_RE.test(value)) return "jwt";
|
|
156
|
+
if (HASH_OR_ID_NAME_RE.test(name)) return null;
|
|
157
|
+
const trimmed = value.trim();
|
|
158
|
+
if (trimmed.length >= HEX_MIN_LENGTH && HEX_RE.test(trimmed) && !UUID_RE.test(trimmed)) return "hex";
|
|
159
|
+
// The whole value (standard base64 may hold `/`), unless it reads as a path; then each run.
|
|
160
|
+
if (!/^[/~.]/.test(trimmed) && looksRandom(trimmed)) return "high-entropy";
|
|
161
|
+
if (trimmed.split(RUN_SPLIT_RE).some((run) => run.length >= HIGH_ENTROPY_MIN_LENGTH && looksRandom(run))) return "high-entropy";
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/context/settings.mjs — the settings layers a run obeys.
|
|
3
|
+
*
|
|
4
|
+
* Files (Claude-compatible layout), from highest precedence to lowest:
|
|
5
|
+
*
|
|
6
|
+
* managed /Library/Application Support/ClaudeCode/managed-settings.json (macOS)
|
|
7
|
+
* /etc/claude-code/managed-settings.json (Linux)
|
|
8
|
+
* cli --settings <file|json>, then the permission flags
|
|
9
|
+
* local <cwd>/.claude/settings.local.json
|
|
10
|
+
* project <cwd>/.claude/settings.json
|
|
11
|
+
* user ~/.claude/settings.json ($CLAUDE_CONFIG_DIR replaces ~/.claude)
|
|
12
|
+
*
|
|
13
|
+
* Merging:
|
|
14
|
+
* · permissions.allow / deny / ask and additionalDirectories concatenate
|
|
15
|
+
* across layers (rule evaluation is by category, so order within a
|
|
16
|
+
* category never changes a decision);
|
|
17
|
+
* · hooks concatenate per event, each entry remembering its layer;
|
|
18
|
+
* · permissions.defaultMode takes the highest-precedence layer that sets it;
|
|
19
|
+
* a managed defaultMode also beats --permission-mode (the managed layer
|
|
20
|
+
* is the organisation's, the flag is the caller's);
|
|
21
|
+
* · disableBypassPermissionsMode: "disable" in any layer holds;
|
|
22
|
+
* · disableAllHooks is honoured only from the user and managed layers (a
|
|
23
|
+
* project cannot switch off a seat's hooks) and never removes managed
|
|
24
|
+
* hooks; set elsewhere it is ignored with a warning;
|
|
25
|
+
* · project MCP approval — enableAllProjectMcpServers and
|
|
26
|
+
* enabledMcpjsonServers — is honoured only from the managed, cli and user
|
|
27
|
+
* layers: a repository cannot approve its own .mcp.json.
|
|
28
|
+
* disabledMcpjsonServers (a restriction) is honoured from every layer;
|
|
29
|
+
* · `cohort.pluginDirs` (an engine extension, ignored by other tools)
|
|
30
|
+
* concatenates; each entry is a plugin root, `dir/*` expands to its
|
|
31
|
+
* subdirectories;
|
|
32
|
+
* · `env` merges key by key, a higher layer's value winning. It is applied
|
|
33
|
+
* to the environment of tool commands (Bash, subject to its credential
|
|
34
|
+
* scrub), hook commands and stdio MCP servers (subject to theirs) — never
|
|
35
|
+
* to the engine's own gateway configuration;
|
|
36
|
+
* · `model` takes the highest-precedence layer that sets it; it is the
|
|
37
|
+
* run's default tier when neither --model nor COHORT_LLM_MODEL is given;
|
|
38
|
+
* · `apiKeyHelper` is ignored with a warning: the engine's gateway
|
|
39
|
+
* credential is COHORT_LLM_TOKEN;
|
|
40
|
+
* · the engine's own keys under `cohort` — `contextWindows` and `compaction`
|
|
41
|
+
* (objects, merged key by key), `promptCacheKey` and `imageInput`
|
|
42
|
+
* (booleans, highest layer wins) and `webTools` (only `false` counts, from
|
|
43
|
+
* any layer) — are returned as `engine`.
|
|
44
|
+
*
|
|
45
|
+
* Relative paths in a layer resolve against that layer's root: the project
|
|
46
|
+
* directory for project/local, the home directory for user, the working
|
|
47
|
+
* directory for cli, and "/" for managed.
|
|
48
|
+
*
|
|
49
|
+
* `mergeSettings` is pure; `loadSettingsLayers` reads through injected fs.
|
|
50
|
+
*
|
|
51
|
+
* @module lib/engine/context/settings
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import path from "node:path";
|
|
55
|
+
import { compileRules, mergeRuleSets } from "../permissions.mjs";
|
|
56
|
+
|
|
57
|
+
/** @typedef {'managed'|'cli'|'local'|'project'|'user'} SettingsScope */
|
|
58
|
+
/** @typedef {{scope:SettingsScope, path:string, baseDir:string, settings:any}} SettingsLayer */
|
|
59
|
+
|
|
60
|
+
export const HOOK_EVENTS = Object.freeze(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "SubagentStop", "PreCompact", "Notification"]);
|
|
61
|
+
|
|
62
|
+
/** @param {string} platform */
|
|
63
|
+
export function defaultManagedSettingsPath(platform) {
|
|
64
|
+
if (platform === "darwin") return "/Library/Application Support/ClaudeCode/managed-settings.json";
|
|
65
|
+
if (platform === "linux") return "/etc/claude-code/managed-settings.json";
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {{homedir:string|null, cwd:string, env?:Record<string,string|undefined>}} p
|
|
71
|
+
* @returns {string|null} the per-user config directory (~/.claude)
|
|
72
|
+
*/
|
|
73
|
+
export function userConfigDir({ homedir, env = {} }) {
|
|
74
|
+
if (env.CLAUDE_CONFIG_DIR) return env.CLAUDE_CONFIG_DIR;
|
|
75
|
+
return homedir ? path.join(homedir, ".claude") : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {object} p
|
|
80
|
+
* @param {string|null} p.homedir
|
|
81
|
+
* @param {string} p.cwd
|
|
82
|
+
* @param {string|null} [p.managedPath]
|
|
83
|
+
* @param {string|null} [p.cliSettings] --settings value: a path, or inline JSON
|
|
84
|
+
* @param {Record<string,string|undefined>} [p.env]
|
|
85
|
+
* @param {(file:string) => string} p.readFile
|
|
86
|
+
* @param {(file:string) => boolean} p.exists
|
|
87
|
+
* @returns {{layers: SettingsLayer[], errors: string[]}} layers in precedence order, HIGHEST first
|
|
88
|
+
*/
|
|
89
|
+
export function loadSettingsLayers({ homedir, cwd, managedPath = null, cliSettings = null, env = {}, readFile, exists }) {
|
|
90
|
+
/** @type {SettingsLayer[]} */
|
|
91
|
+
const layers = [];
|
|
92
|
+
const errors = [];
|
|
93
|
+
const read = (/** @type {SettingsScope} */ scope, /** @type string */ file, /** @type string */ baseDir) => {
|
|
94
|
+
if (!exists(file)) return;
|
|
95
|
+
try {
|
|
96
|
+
const settings = JSON.parse(readFile(file));
|
|
97
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not a JSON object");
|
|
98
|
+
layers.push({ scope, path: file, baseDir, settings });
|
|
99
|
+
} catch (e) {
|
|
100
|
+
errors.push(`${scope} settings ${file}: ${e instanceof Error ? e.message : e}`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
if (managedPath) read("managed", managedPath, "/");
|
|
104
|
+
if (cliSettings !== null && cliSettings !== undefined) {
|
|
105
|
+
const v = String(cliSettings).trim();
|
|
106
|
+
if (v.startsWith("{")) {
|
|
107
|
+
try {
|
|
108
|
+
layers.push({ scope: "cli", path: "--settings (inline)", baseDir: cwd, settings: JSON.parse(v) });
|
|
109
|
+
} catch (e) {
|
|
110
|
+
errors.push(`--settings: not valid JSON (${e instanceof Error ? e.message : e})`);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
const file = path.resolve(cwd, v);
|
|
114
|
+
if (!exists(file)) errors.push(`--settings ${file}: file not found`);
|
|
115
|
+
else read("cli", file, cwd);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
read("local", path.join(cwd, ".claude", "settings.local.json"), cwd);
|
|
119
|
+
read("project", path.join(cwd, ".claude", "settings.json"), cwd);
|
|
120
|
+
const userDir = userConfigDir({ homedir, cwd, env });
|
|
121
|
+
if (userDir) read("user", path.join(userDir, "settings.json"), homedir ?? userDir);
|
|
122
|
+
return { layers, errors };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @typedef {Object} HookEntry
|
|
127
|
+
* @property {string} matcher
|
|
128
|
+
* @property {Array<{type:string, command?:string, timeout?:number}>} hooks
|
|
129
|
+
* @property {string} source
|
|
130
|
+
* @property {string} baseDir
|
|
131
|
+
* @property {SettingsScope} [scope]
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/** Layers whose word approves project MCP servers or switches off hooks. */
|
|
135
|
+
const APPROVING_SCOPES = new Set(["managed", "cli", "user"]);
|
|
136
|
+
const HOOK_SWITCH_SCOPES = new Set(["managed", "user"]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {SettingsLayer[]} layers HIGHEST precedence first
|
|
140
|
+
* @param {{allowedTools?:string[], disallowedTools?:string[], permissionMode?:string|null, cwd:string}} flags
|
|
141
|
+
*/
|
|
142
|
+
export function mergeSettings(layers, flags) {
|
|
143
|
+
/** Problems that would silently drop a restriction: the run must not start. */
|
|
144
|
+
const errors = [];
|
|
145
|
+
/** Problems that lose nothing protective (an unknown hook event name). */
|
|
146
|
+
const warnings = [];
|
|
147
|
+
const ruleSets = [];
|
|
148
|
+
const additionalDirectories = [];
|
|
149
|
+
/** @type {Record<string, HookEntry[]>} */
|
|
150
|
+
const hooks = Object.fromEntries(HOOK_EVENTS.map((e) => [e, []]));
|
|
151
|
+
const pluginDirs = [];
|
|
152
|
+
let defaultMode = null;
|
|
153
|
+
/** @type {string|null} */
|
|
154
|
+
let managedDefaultMode = null;
|
|
155
|
+
let disableBypass = false;
|
|
156
|
+
let disableAllHooks = null;
|
|
157
|
+
/** @type {{enableAll:boolean|null, enabled:string[], disabled:string[]}} */
|
|
158
|
+
const mcpApproval = { enableAll: null, enabled: [], disabled: [] };
|
|
159
|
+
const names = (/** @type unknown */ v) => (Array.isArray(v) ? v.map(String) : []);
|
|
160
|
+
/** @type {Record<string,string>} */
|
|
161
|
+
const env = {};
|
|
162
|
+
/** @type {string|null} */
|
|
163
|
+
let model = null;
|
|
164
|
+
/** @type {{contextWindows:Record<string,unknown>, compaction:Record<string,unknown>, promptCacheKey?:boolean, imageInput?:boolean, webTools?:boolean}} */
|
|
165
|
+
const engine = { contextWindows: {}, compaction: {} };
|
|
166
|
+
|
|
167
|
+
// Lowest precedence first, so a scalar set later wins.
|
|
168
|
+
for (const layer of [...layers].reverse()) {
|
|
169
|
+
const s = layer.settings || {};
|
|
170
|
+
const label = `${layer.scope} ${layer.path}`;
|
|
171
|
+
const perms = s.permissions;
|
|
172
|
+
if (perms !== undefined) {
|
|
173
|
+
if (!perms || typeof perms !== "object") errors.push(`${label}: "permissions" must be an object`);
|
|
174
|
+
else {
|
|
175
|
+
const compiled = compileRules(perms, { source: label, baseDir: layer.baseDir });
|
|
176
|
+
ruleSets.push(compiled.rules);
|
|
177
|
+
errors.push(...compiled.errors);
|
|
178
|
+
if (typeof perms.defaultMode === "string") {
|
|
179
|
+
defaultMode = perms.defaultMode;
|
|
180
|
+
if (layer.scope === "managed") managedDefaultMode = perms.defaultMode;
|
|
181
|
+
}
|
|
182
|
+
if (perms.disableBypassPermissionsMode === "disable") disableBypass = true;
|
|
183
|
+
if (Array.isArray(perms.additionalDirectories)) {
|
|
184
|
+
for (const d of perms.additionalDirectories) additionalDirectories.push(path.resolve(layer.baseDir, String(d)));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (typeof s.disableAllHooks === "boolean") {
|
|
189
|
+
if (HOOK_SWITCH_SCOPES.has(layer.scope)) disableAllHooks = s.disableAllHooks;
|
|
190
|
+
else warnings.push(`${label}: disableAllHooks is honoured only in user or managed settings (ignored)`);
|
|
191
|
+
}
|
|
192
|
+
mcpApproval.disabled.push(...names(s.disabledMcpjsonServers));
|
|
193
|
+
if (s.enableAllProjectMcpServers !== undefined || s.enabledMcpjsonServers !== undefined) {
|
|
194
|
+
if (APPROVING_SCOPES.has(layer.scope)) {
|
|
195
|
+
if (typeof s.enableAllProjectMcpServers === "boolean") mcpApproval.enableAll = s.enableAllProjectMcpServers;
|
|
196
|
+
mcpApproval.enabled.push(...names(s.enabledMcpjsonServers));
|
|
197
|
+
} else {
|
|
198
|
+
warnings.push(`${label}: project MCP servers cannot be approved from ${layer.scope} settings (ignored); approve them in ~/.claude/settings.json or pass --mcp-config`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (s.hooks !== undefined) {
|
|
202
|
+
if (!s.hooks || typeof s.hooks !== "object") errors.push(`${label}: "hooks" must be an object`);
|
|
203
|
+
else {
|
|
204
|
+
for (const [event, entries] of Object.entries(s.hooks)) {
|
|
205
|
+
if (!HOOK_EVENTS.includes(event)) {
|
|
206
|
+
warnings.push(`${label}: unknown hook event "${event}" (ignored)`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (!Array.isArray(entries)) {
|
|
210
|
+
errors.push(`${label}: hooks.${event} must be an array`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
for (const entry of entries) {
|
|
214
|
+
if (!entry || !Array.isArray(entry.hooks)) {
|
|
215
|
+
errors.push(`${label}: hooks.${event} entries need a "hooks" array`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
hooks[event].push({ matcher: typeof entry.matcher === "string" ? entry.matcher : "", hooks: entry.hooks, source: label, baseDir: layer.baseDir, scope: layer.scope });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const dirs = s.cohort?.pluginDirs;
|
|
224
|
+
if (Array.isArray(dirs)) for (const d of dirs) pluginDirs.push({ dir: path.resolve(layer.baseDir, String(d)), source: label });
|
|
225
|
+
// CF-21: env, model, apiKeyHelper; the engine's own `cohort` keys.
|
|
226
|
+
if (s.env !== undefined) {
|
|
227
|
+
if (!s.env || typeof s.env !== "object" || Array.isArray(s.env)) warnings.push(`${label}: "env" must be an object (ignored)`);
|
|
228
|
+
else {
|
|
229
|
+
for (const [k, v] of Object.entries(s.env)) {
|
|
230
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") env[k] = String(v);
|
|
231
|
+
else warnings.push(`${label}: env.${k} must be a string (ignored)`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (typeof s.model === "string" && s.model.trim() !== "") model = s.model.trim();
|
|
236
|
+
if (s.apiKeyHelper !== undefined) warnings.push(`${label}: apiKeyHelper is ignored by the Cohort Engine; its gateway credential comes from COHORT_LLM_TOKEN`);
|
|
237
|
+
const c = s.cohort && typeof s.cohort === "object" ? s.cohort : {};
|
|
238
|
+
for (const key of /** @type {const} */ (["contextWindows", "compaction"])) {
|
|
239
|
+
if (c[key] && typeof c[key] === "object" && !Array.isArray(c[key])) Object.assign(engine[key], c[key]);
|
|
240
|
+
}
|
|
241
|
+
if (typeof c.promptCacheKey === "boolean") engine.promptCacheKey = c.promptCacheKey;
|
|
242
|
+
if (typeof c.imageInput === "boolean") engine.imageInput = c.imageInput;
|
|
243
|
+
// A restriction: `webTools: false` in any layer holds (a repository cannot switch back on what the seat switched off).
|
|
244
|
+
if (c.webTools === false) engine.webTools = false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const cli = compileRules({ allow: flags.allowedTools ?? [], deny: flags.disallowedTools ?? [] }, { source: "command line", baseDir: flags.cwd });
|
|
248
|
+
errors.push(...cli.errors);
|
|
249
|
+
// Managed rules are part of `layers`; CLI rules add to them (rules only ever
|
|
250
|
+
// add restrictions or permissions, and deny/ask win by category).
|
|
251
|
+
ruleSets.push(cli.rules);
|
|
252
|
+
|
|
253
|
+
if (managedDefaultMode !== null && flags.permissionMode && flags.permissionMode !== managedDefaultMode) {
|
|
254
|
+
warnings.push(`managed settings set permissions.defaultMode "${managedDefaultMode}", which overrides the requested permission mode "${flags.permissionMode}"`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
rules: mergeRuleSets(...ruleSets),
|
|
259
|
+
mode: managedDefaultMode ?? flags.permissionMode ?? defaultMode ?? "default",
|
|
260
|
+
defaultMode,
|
|
261
|
+
disableBypassPermissionsMode: disableBypass,
|
|
262
|
+
additionalDirectories,
|
|
263
|
+
// Switched off, only the managed layer's own hooks still run.
|
|
264
|
+
hooks: disableAllHooks === true ? Object.fromEntries(HOOK_EVENTS.map((e) => [e, hooks[e].filter((h) => h.scope === "managed")])) : hooks,
|
|
265
|
+
hooksDisabled: disableAllHooks === true,
|
|
266
|
+
mcpApproval,
|
|
267
|
+
pluginDirs,
|
|
268
|
+
env,
|
|
269
|
+
model,
|
|
270
|
+
engine,
|
|
271
|
+
errors,
|
|
272
|
+
warnings,
|
|
273
|
+
};
|
|
274
|
+
}
|