@cohortapp/agent-sdk 2.3.0
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/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- package/workflows/weekly/strategic-memo.yaml +79 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/diagnostics/counters.mjs — loud, durable counters for guard fail-opens.
|
|
3
|
+
*
|
|
4
|
+
* The audit's silent-failure epidemic (gaps-product-quality P0-6) is not that
|
|
5
|
+
* the guards fail open — failing open is the deliberate, correct policy ("a
|
|
6
|
+
* duplicate is cheaper than a missed cadence"; "a breaker we can't persist
|
|
7
|
+
* degrades to in-memory only"). The bug is that they fail open *silently*: a
|
|
8
|
+
* rate-guard whose state dir is unwritable never opens for anyone, a budget-guard
|
|
9
|
+
* that reads a stale ledger as $0 spends forever, and NOTHING anywhere counts it,
|
|
10
|
+
* so doctor and a human stay blind to the exact failure that matters.
|
|
11
|
+
*
|
|
12
|
+
* This module gives every fail-open a place to be *counted somewhere a human or
|
|
13
|
+
* doctor looks*. `bump(name, attrs)` increments an in-memory tally AND appends a
|
|
14
|
+
* JSON line to `logs/diagnostics/counters/<YYYY-MM-DD>.jsonl` (via the shared
|
|
15
|
+
* atomic appender), so the count survives a restart and a crash mid-write can
|
|
16
|
+
* never corrupt the stream. `snapshot()` reads back today's totals for doctor.
|
|
17
|
+
*
|
|
18
|
+
* Invariant: NEITHER `bump` NOR `snapshot` EVER throws. A counter that can take
|
|
19
|
+
* down the path it observes is worse than no counter at all — so a bad name, an
|
|
20
|
+
* un-serializable attr, an unwritable disk all degrade to a return value, never
|
|
21
|
+
* an exception. (This mirrors lib/diagnostics/events.mjs and the fail-open idiom
|
|
22
|
+
* in lib/cadence-bus.mjs.)
|
|
23
|
+
*
|
|
24
|
+
* The in-memory tally is process-local and is the fast path doctor/tests read
|
|
25
|
+
* when they share the process; the JSONL stream is the cross-process / cross-
|
|
26
|
+
* restart source of truth that `snapshot()` reconstructs from disk when asked.
|
|
27
|
+
*
|
|
28
|
+
* Constraints (CLAUDE.md): ESM, Node built-ins + lib/fs-atomic only, injectable
|
|
29
|
+
* clock + log dir + append + read so tests are hermetic and need no real disk.
|
|
30
|
+
*
|
|
31
|
+
* @module lib/diagnostics/counters
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
35
|
+
import { join, resolve } from "node:path";
|
|
36
|
+
|
|
37
|
+
import { appendJsonl } from "../fs-atomic.mjs";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Process-local running tally, keyed by counter name. This is a best-effort
|
|
41
|
+
* accelerator: it lets a long-lived daemon (and same-process tests) read totals
|
|
42
|
+
* without re-parsing the JSONL, but it is NOT the source of truth — `snapshot()`
|
|
43
|
+
* reconstructs from disk so counts survive a restart and span processes.
|
|
44
|
+
* @type {Map<string, number>}
|
|
45
|
+
*/
|
|
46
|
+
const MEM = new Map();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the counters log directory: `<agentRoot>/logs/diagnostics/counters`.
|
|
50
|
+
* An explicit `deps.dir` wins (tests point it at a tmp dir); otherwise we derive
|
|
51
|
+
* it from the agent root the same way every other maestro module does.
|
|
52
|
+
* @param {object} [deps]
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function countersDir(deps = {}) {
|
|
56
|
+
if (deps.dir) return resolve(deps.dir);
|
|
57
|
+
const root = resolve(
|
|
58
|
+
deps.agentRoot ||
|
|
59
|
+
process.env.AGENT_ROOT ||
|
|
60
|
+
process.env.AGENT_DIR ||
|
|
61
|
+
process.cwd()
|
|
62
|
+
);
|
|
63
|
+
return join(root, "logs", "diagnostics", "counters");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* UTC date stamp (YYYY-MM-DD) for an epoch-ms value. The stream rotates by UTC
|
|
68
|
+
* day to match the diagnostics events / cost ledger / budget files, so "today"
|
|
69
|
+
* means the same thing across every accounting and observability path.
|
|
70
|
+
* @param {number} ms
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function dateStamp(ms) {
|
|
74
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Coerce a counter name to a stable, non-empty string. A nameless bump would
|
|
79
|
+
* make the stream un-queryable, so we fold the empty/odd cases to "unknown"
|
|
80
|
+
* rather than dropping the row (a dropped fail-open is the bug we're fixing).
|
|
81
|
+
* @param {unknown} name
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function safeName(name) {
|
|
85
|
+
if (typeof name === "string") {
|
|
86
|
+
const trimmed = name.trim();
|
|
87
|
+
if (trimmed) return trimmed;
|
|
88
|
+
}
|
|
89
|
+
if (name != null) {
|
|
90
|
+
const s = String(name).trim();
|
|
91
|
+
if (s) return s;
|
|
92
|
+
}
|
|
93
|
+
return "unknown";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Best-effort plain-object coercion of `attrs` — never let a caller's odd value
|
|
98
|
+
* (a BigInt, a circular object, a function) make the append throw and violate
|
|
99
|
+
* the never-throw contract. Non-objects are wrapped under `{value}`.
|
|
100
|
+
* @param {unknown} attrs
|
|
101
|
+
* @returns {Record<string, unknown>}
|
|
102
|
+
*/
|
|
103
|
+
function safeAttrs(attrs) {
|
|
104
|
+
if (attrs == null) return {};
|
|
105
|
+
if (typeof attrs !== "object" || Array.isArray(attrs)) {
|
|
106
|
+
return { value: typeof attrs === "bigint" ? attrs.toString() : attrs };
|
|
107
|
+
}
|
|
108
|
+
return attrs;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Increment counter `name` by `by` (default 1), recording the bump both in the
|
|
113
|
+
* in-memory tally and on the date-stamped JSONL stream so a fail-open is counted
|
|
114
|
+
* somewhere durable. Returns the new in-memory total, or `null` if anything went
|
|
115
|
+
* wrong (this function NEVER throws).
|
|
116
|
+
*
|
|
117
|
+
* The persisted row is `{ ts, name, by, attrs }`; `snapshot()` sums `by` per
|
|
118
|
+
* name. We persist each bump as its own row rather than a rolling total so two
|
|
119
|
+
* processes appending concurrently can never clobber each other's count — whole
|
|
120
|
+
* lines interleave (the appendJsonl atomic-append contract) and the read side
|
|
121
|
+
* sums them.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} name counter name (e.g. "rate_guard.persist_failed")
|
|
124
|
+
* @param {Record<string, unknown>} [attrs] structured context (provider, reason)
|
|
125
|
+
* @param {object} [deps]
|
|
126
|
+
* @param {number} [deps.by] increment amount (default 1; non-finite → 1)
|
|
127
|
+
* @param {() => number} [deps.now] clock (default Date.now)
|
|
128
|
+
* @param {string} [deps.dir] explicit counters dir (overrides agentRoot)
|
|
129
|
+
* @param {string} [deps.agentRoot] agent root used to derive the counters dir
|
|
130
|
+
* @param {(path: string, record: unknown) => boolean} [deps.append] writer
|
|
131
|
+
* (default lib/fs-atomic appendJsonl) — injectable for tests
|
|
132
|
+
* @returns {number|null} new in-memory total for `name`, or null on failure
|
|
133
|
+
*/
|
|
134
|
+
export function bump(name, attrs = {}, deps = {}) {
|
|
135
|
+
try {
|
|
136
|
+
const key = safeName(name);
|
|
137
|
+
const byRaw = Number(deps.by);
|
|
138
|
+
const by = Number.isFinite(byRaw) && byRaw !== 0 ? byRaw : 1;
|
|
139
|
+
|
|
140
|
+
// In-memory tally first — cheap, can't fail, and is the fast path for a
|
|
141
|
+
// same-process reader even if the disk append below degrades.
|
|
142
|
+
const total = (MEM.get(key) || 0) + by;
|
|
143
|
+
MEM.set(key, total);
|
|
144
|
+
|
|
145
|
+
// Persist as its own JSONL row. The append is best-effort and INDEPENDENTLY
|
|
146
|
+
// guarded: even if an injected/odd writer throws (not just returns false),
|
|
147
|
+
// the in-memory tally has already moved, so the count isn't lost within this
|
|
148
|
+
// process and we still return the durable-intent total.
|
|
149
|
+
try {
|
|
150
|
+
const now = typeof deps.now === "function" ? deps.now : Date.now;
|
|
151
|
+
const append = typeof deps.append === "function" ? deps.append : appendJsonl;
|
|
152
|
+
const ms = now();
|
|
153
|
+
const row = {
|
|
154
|
+
ts: new Date(ms).toISOString(),
|
|
155
|
+
name: key,
|
|
156
|
+
by,
|
|
157
|
+
attrs: safeAttrs(attrs),
|
|
158
|
+
};
|
|
159
|
+
const path = join(countersDir(deps), `${dateStamp(ms)}.jsonl`);
|
|
160
|
+
append(path, row);
|
|
161
|
+
} catch {
|
|
162
|
+
/* disk/append failure must not lose the in-memory count or throw */
|
|
163
|
+
}
|
|
164
|
+
return total;
|
|
165
|
+
} catch {
|
|
166
|
+
// A counter must never crash the observed path.
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Read today's persisted counter totals, summed per name from the date-stamped
|
|
173
|
+
* JSONL stream. This is the cross-process / cross-restart view doctor reports;
|
|
174
|
+
* it does NOT read the in-memory tally so it reflects what actually landed on
|
|
175
|
+
* disk (the durable record a second process or a fresh boot can see).
|
|
176
|
+
*
|
|
177
|
+
* NEVER throws: a missing dir, an unreadable file, or a malformed line all
|
|
178
|
+
* degrade — a bad line is skipped, a bad file yields {} — so doctor can always
|
|
179
|
+
* call it.
|
|
180
|
+
*
|
|
181
|
+
* @param {object} [deps]
|
|
182
|
+
* @param {() => number} [deps.now] clock (default Date.now) — selects the day
|
|
183
|
+
* @param {string} [deps.dir] explicit counters dir (overrides agentRoot)
|
|
184
|
+
* @param {string} [deps.agentRoot] agent root used to derive the counters dir
|
|
185
|
+
* @param {(path: string) => string} [deps.read] reader (default readFileSync)
|
|
186
|
+
* @returns {Record<string, number>} { [name]: total } for today; {} if none
|
|
187
|
+
*/
|
|
188
|
+
export function snapshot(deps = {}) {
|
|
189
|
+
const totals = {};
|
|
190
|
+
try {
|
|
191
|
+
const now = typeof deps.now === "function" ? deps.now : Date.now;
|
|
192
|
+
const path = join(countersDir(deps), `${dateStamp(now())}.jsonl`);
|
|
193
|
+
const read =
|
|
194
|
+
typeof deps.read === "function"
|
|
195
|
+
? deps.read
|
|
196
|
+
: (p) => readFileSync(p, "utf-8");
|
|
197
|
+
if (typeof deps.read !== "function" && !existsSync(path)) return totals;
|
|
198
|
+
let raw;
|
|
199
|
+
try {
|
|
200
|
+
raw = read(path);
|
|
201
|
+
} catch {
|
|
202
|
+
return totals; // file vanished / unreadable → no counts, never throw
|
|
203
|
+
}
|
|
204
|
+
if (typeof raw !== "string" || raw.length === 0) return totals;
|
|
205
|
+
for (const line of raw.split("\n")) {
|
|
206
|
+
if (!line) continue;
|
|
207
|
+
let rec;
|
|
208
|
+
try {
|
|
209
|
+
rec = JSON.parse(line);
|
|
210
|
+
} catch {
|
|
211
|
+
continue; // skip a corrupt/partial line, keep summing the rest
|
|
212
|
+
}
|
|
213
|
+
if (!rec || typeof rec.name !== "string") continue;
|
|
214
|
+
const by = Number(rec.by);
|
|
215
|
+
totals[rec.name] = (totals[rec.name] || 0) + (Number.isFinite(by) ? by : 1);
|
|
216
|
+
}
|
|
217
|
+
return totals;
|
|
218
|
+
} catch {
|
|
219
|
+
return totals;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Read the process-local in-memory tally — the fast, exact view for a reader
|
|
225
|
+
* that shares this process (doctor running in the daemon, a test). Returns a
|
|
226
|
+
* copy so callers can't mutate the live map. Never throws.
|
|
227
|
+
* @returns {Record<string, number>}
|
|
228
|
+
*/
|
|
229
|
+
export function memSnapshot() {
|
|
230
|
+
try {
|
|
231
|
+
return Object.fromEntries(MEM.entries());
|
|
232
|
+
} catch {
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Reset the in-memory tally. For tests only — the durable JSONL stream is the
|
|
239
|
+
* cross-restart source of truth and is never touched here. Never throws.
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
export function resetMem() {
|
|
243
|
+
try {
|
|
244
|
+
MEM.clear();
|
|
245
|
+
} catch {
|
|
246
|
+
/* never throws */
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* List the JSONL stream paths under the counters dir (most-recent day last),
|
|
252
|
+
* for doctor / retention to enumerate. Best-effort: returns [] on any error.
|
|
253
|
+
* @param {object} [deps] @param {string} [deps.dir] @param {string} [deps.agentRoot]
|
|
254
|
+
* @returns {string[]} absolute file paths
|
|
255
|
+
*/
|
|
256
|
+
export function listStreams(deps = {}) {
|
|
257
|
+
try {
|
|
258
|
+
const dir = countersDir(deps);
|
|
259
|
+
if (!existsSync(dir)) return [];
|
|
260
|
+
return readdirSync(dir)
|
|
261
|
+
.filter((n) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(n))
|
|
262
|
+
.sort()
|
|
263
|
+
.map((n) => join(dir, n));
|
|
264
|
+
} catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default { bump, snapshot, memSnapshot, resetMem, listStreams };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/diagnostics/counters.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* node:test coverage for the loud guard-fail-open counters:
|
|
5
|
+
* - bump increments the in-memory tally and persists a JSONL row
|
|
6
|
+
* - bump persists to the date-stamped path under the counters dir
|
|
7
|
+
* - snapshot sums today's rows per name from disk (cross-restart view)
|
|
8
|
+
* - snapshot is isolated to "today" by the injected clock
|
|
9
|
+
* - bump never throws on an un-serializable attr / bad name / failing append
|
|
10
|
+
* - snapshot never throws on a missing dir / corrupt line / unreadable file
|
|
11
|
+
* - concurrent-style appends (multiple rows) sum correctly
|
|
12
|
+
*
|
|
13
|
+
* Run: cd /Users/layla/maestro && node --test lib/diagnostics/counters.test.mjs
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import test from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { mkdtempSync, rmSync, readdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
bump,
|
|
24
|
+
snapshot,
|
|
25
|
+
memSnapshot,
|
|
26
|
+
resetMem,
|
|
27
|
+
listStreams,
|
|
28
|
+
} from "./counters.mjs";
|
|
29
|
+
|
|
30
|
+
function tmpDir() {
|
|
31
|
+
return mkdtempSync(join(tmpdir(), "maestro-counters-"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Fixed clock at 2026-06-11T12:00:00Z. */
|
|
35
|
+
const FIXED = Date.parse("2026-06-11T12:00:00.000Z");
|
|
36
|
+
const fixedNow = () => FIXED;
|
|
37
|
+
|
|
38
|
+
test("bump increments the in-memory tally and returns the new total", () => {
|
|
39
|
+
resetMem();
|
|
40
|
+
const captured = [];
|
|
41
|
+
const append = (p, row) => { captured.push({ p, row }); return true; };
|
|
42
|
+
|
|
43
|
+
const t1 = bump("rate_guard.persist_failed", { provider: "anthropic" }, { append, now: fixedNow });
|
|
44
|
+
const t2 = bump("rate_guard.persist_failed", { provider: "anthropic" }, { append, now: fixedNow });
|
|
45
|
+
assert.equal(t1, 1);
|
|
46
|
+
assert.equal(t2, 2);
|
|
47
|
+
assert.equal(memSnapshot()["rate_guard.persist_failed"], 2);
|
|
48
|
+
assert.equal(captured.length, 2);
|
|
49
|
+
assert.equal(captured[0].row.name, "rate_guard.persist_failed");
|
|
50
|
+
assert.equal(captured[0].row.by, 1);
|
|
51
|
+
assert.equal(captured[0].row.attrs.provider, "anthropic");
|
|
52
|
+
assert.equal(typeof captured[0].row.ts, "string");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("bump honours an explicit increment amount", () => {
|
|
56
|
+
resetMem();
|
|
57
|
+
const append = () => true;
|
|
58
|
+
assert.equal(bump("budget.stale_ledger", {}, { append, by: 5, now: fixedNow }), 5);
|
|
59
|
+
assert.equal(bump("budget.stale_ledger", {}, { append, by: 3, now: fixedNow }), 8);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("bump persists to a date-stamped path under the counters dir", () => {
|
|
63
|
+
resetMem();
|
|
64
|
+
const dir = tmpDir();
|
|
65
|
+
try {
|
|
66
|
+
bump("g.fail", { reason: "x" }, { dir, now: fixedNow });
|
|
67
|
+
const files = readdirSync(dir);
|
|
68
|
+
assert.deepEqual(files, ["2026-06-11.jsonl"]);
|
|
69
|
+
const raw = readFileSync(join(dir, "2026-06-11.jsonl"), "utf-8").trim();
|
|
70
|
+
const row = JSON.parse(raw);
|
|
71
|
+
assert.equal(row.name, "g.fail");
|
|
72
|
+
assert.equal(row.by, 1);
|
|
73
|
+
assert.equal(row.attrs.reason, "x");
|
|
74
|
+
} finally {
|
|
75
|
+
rmSync(dir, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("snapshot sums today's rows per name from disk", () => {
|
|
80
|
+
resetMem();
|
|
81
|
+
const dir = tmpDir();
|
|
82
|
+
try {
|
|
83
|
+
bump("a", {}, { dir, now: fixedNow });
|
|
84
|
+
bump("a", {}, { dir, now: fixedNow });
|
|
85
|
+
bump("b", {}, { dir, now: fixedNow, by: 4 });
|
|
86
|
+
const snap = snapshot({ dir, now: fixedNow });
|
|
87
|
+
assert.equal(snap.a, 2);
|
|
88
|
+
assert.equal(snap.b, 4);
|
|
89
|
+
} finally {
|
|
90
|
+
rmSync(dir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("snapshot is isolated to the clock's day", () => {
|
|
95
|
+
resetMem();
|
|
96
|
+
const dir = tmpDir();
|
|
97
|
+
try {
|
|
98
|
+
const day1 = () => Date.parse("2026-06-11T01:00:00.000Z");
|
|
99
|
+
const day2 = () => Date.parse("2026-06-12T01:00:00.000Z");
|
|
100
|
+
bump("x", {}, { dir, now: day1 });
|
|
101
|
+
bump("x", {}, { dir, now: day1 });
|
|
102
|
+
bump("x", {}, { dir, now: day2 });
|
|
103
|
+
// Two distinct day files exist.
|
|
104
|
+
assert.deepEqual(readdirSync(dir).sort(), ["2026-06-11.jsonl", "2026-06-12.jsonl"]);
|
|
105
|
+
// Each day's snapshot only sees its own rows.
|
|
106
|
+
assert.equal(snapshot({ dir, now: day1 }).x, 2);
|
|
107
|
+
assert.equal(snapshot({ dir, now: day2 }).x, 1);
|
|
108
|
+
} finally {
|
|
109
|
+
rmSync(dir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("snapshot survives across a simulated restart (reads disk, not memory)", () => {
|
|
114
|
+
resetMem();
|
|
115
|
+
const dir = tmpDir();
|
|
116
|
+
try {
|
|
117
|
+
bump("survives", {}, { dir, now: fixedNow });
|
|
118
|
+
bump("survives", {}, { dir, now: fixedNow });
|
|
119
|
+
// Simulate a process restart: the in-memory tally is gone, the disk isn't.
|
|
120
|
+
resetMem();
|
|
121
|
+
assert.deepEqual(memSnapshot(), {});
|
|
122
|
+
assert.equal(snapshot({ dir, now: fixedNow }).survives, 2);
|
|
123
|
+
} finally {
|
|
124
|
+
rmSync(dir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("bump never throws and still moves the in-memory tally when append fails", () => {
|
|
129
|
+
resetMem();
|
|
130
|
+
const failingAppend = () => { throw new Error("disk full"); };
|
|
131
|
+
// append throwing must NOT propagate; the in-memory tally must still advance.
|
|
132
|
+
const total = bump("resilient", {}, { append: failingAppend, now: fixedNow });
|
|
133
|
+
// append threw before returning, but the in-memory bump happened first.
|
|
134
|
+
assert.equal(total, 1);
|
|
135
|
+
assert.equal(memSnapshot().resilient, 1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("bump tolerates an un-serializable attr and a non-string name", () => {
|
|
139
|
+
resetMem();
|
|
140
|
+
const captured = [];
|
|
141
|
+
const append = (p, row) => { captured.push(row); return true; };
|
|
142
|
+
const circular = {};
|
|
143
|
+
circular.self = circular;
|
|
144
|
+
// A circular attrs object would break naive JSON.stringify; bump must not throw.
|
|
145
|
+
assert.doesNotThrow(() => bump("circ", circular, { append, now: fixedNow }));
|
|
146
|
+
// A non-string name folds to a stable string rather than dropping the row.
|
|
147
|
+
const total = bump(12345, {}, { append, now: fixedNow });
|
|
148
|
+
assert.equal(total, 1);
|
|
149
|
+
assert.equal(captured.at(-1).name, "12345");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("an empty/whitespace name folds to 'unknown'", () => {
|
|
153
|
+
resetMem();
|
|
154
|
+
const captured = [];
|
|
155
|
+
const append = (p, row) => { captured.push(row); return true; };
|
|
156
|
+
bump(" ", {}, { append, now: fixedNow });
|
|
157
|
+
assert.equal(captured.at(-1).name, "unknown");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("snapshot returns {} for a missing dir and never throws", () => {
|
|
161
|
+
resetMem();
|
|
162
|
+
const dir = join(tmpDir(), "does-not-exist");
|
|
163
|
+
assert.deepEqual(snapshot({ dir, now: fixedNow }), {});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("snapshot skips corrupt lines but sums the valid ones", () => {
|
|
167
|
+
resetMem();
|
|
168
|
+
const good = JSON.stringify({ name: "ok", by: 2 });
|
|
169
|
+
const good2 = JSON.stringify({ name: "ok", by: 3 });
|
|
170
|
+
const raw = `${good}\nnot json at all\n${good2}\n{"name":123}\n`;
|
|
171
|
+
const read = () => raw;
|
|
172
|
+
const snap = snapshot({ now: fixedNow, read });
|
|
173
|
+
assert.equal(snap.ok, 5); // 2 + 3; the garbage line and the bad-name line skip
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("snapshot treats a row with a missing/NaN by as 1", () => {
|
|
177
|
+
resetMem();
|
|
178
|
+
const raw = `${JSON.stringify({ name: "n" })}\n${JSON.stringify({ name: "n", by: "x" })}\n`;
|
|
179
|
+
const snap = snapshot({ now: fixedNow, read: () => raw });
|
|
180
|
+
assert.equal(snap.n, 2); // both default to +1
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("snapshot returns {} when the reader throws (unreadable file)", () => {
|
|
184
|
+
resetMem();
|
|
185
|
+
const read = () => { throw new Error("EACCES"); };
|
|
186
|
+
assert.deepEqual(snapshot({ now: fixedNow, read }), {});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("listStreams enumerates the date-stamped streams in order", () => {
|
|
190
|
+
resetMem();
|
|
191
|
+
const dir = tmpDir();
|
|
192
|
+
try {
|
|
193
|
+
bump("s", {}, { dir, now: () => Date.parse("2026-06-11T00:00:00Z") });
|
|
194
|
+
bump("s", {}, { dir, now: () => Date.parse("2026-06-09T00:00:00Z") });
|
|
195
|
+
const streams = listStreams({ dir });
|
|
196
|
+
assert.equal(streams.length, 2);
|
|
197
|
+
assert.ok(streams[0].endsWith("2026-06-09.jsonl"));
|
|
198
|
+
assert.ok(streams[1].endsWith("2026-06-11.jsonl"));
|
|
199
|
+
} finally {
|
|
200
|
+
rmSync(dir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("listStreams returns [] for a missing dir", () => {
|
|
205
|
+
assert.deepEqual(listStreams({ dir: join(tmpdir(), "nope-counters-xyz") }), []);
|
|
206
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/diagnostics/events.mjs — the typed diagnostic-event spine.
|
|
3
|
+
*
|
|
4
|
+
* One in-process event contract that every hot path can emit onto, so a single
|
|
5
|
+
* user interaction is reconstructable end-to-end (gaps-enterprise-ops G7). Each
|
|
6
|
+
* event is appended as one JSON line to a date-stamped stream at
|
|
7
|
+
* `logs/diagnostics/<YYYY-MM-DD>.jsonl` via the shared atomic appender
|
|
8
|
+
* (lib/fs-atomic `appendJsonl`), matching maestro's logging idiom (date-stamped
|
|
9
|
+
* JSONL, fail-open append that can never crash the daemon).
|
|
10
|
+
*
|
|
11
|
+
* The event-type set is CLOSED — an unknown type is coerced to `error` (with the
|
|
12
|
+
* offending type preserved under `attrs._invalid_type`) rather than silently
|
|
13
|
+
* widening the schema. The eight types trace the canonical interaction hops:
|
|
14
|
+
*
|
|
15
|
+
* item_received inbound message/tick admitted (interaction start)
|
|
16
|
+
* classified classifier verdict produced
|
|
17
|
+
* dispatched session spawn decided/launched
|
|
18
|
+
* session_opened claude sub-session started
|
|
19
|
+
* session_closed claude sub-session ended
|
|
20
|
+
* sent an outbound message left the agent (interaction end)
|
|
21
|
+
* guard_fail a governor/breaker/budget/permission gate refused work
|
|
22
|
+
* error an unexpected failure on the path
|
|
23
|
+
*
|
|
24
|
+
* Every row carries `{ts, type, trace_id, span, attrs}`. The `trace_id` is the
|
|
25
|
+
* correlation key (mint with lib/diagnostics/trace.mjs); when omitted, the
|
|
26
|
+
* ambient `withTrace` context fills it in, so an instrumented call site need not
|
|
27
|
+
* thread the id by hand.
|
|
28
|
+
*
|
|
29
|
+
* Invariant: `emitEvent` NEVER throws. A bad directory, an unwritable disk, a
|
|
30
|
+
* non-serializable attr — all degrade to a `false` return, never an exception.
|
|
31
|
+
* Diagnostics must not be able to take down the path they observe.
|
|
32
|
+
*
|
|
33
|
+
* Constraints (CLAUDE.md): ESM, Node built-ins + lib/fs-atomic only, injectable
|
|
34
|
+
* clock + log dir so tests are hermetic.
|
|
35
|
+
*
|
|
36
|
+
* @module lib/diagnostics/events
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { join, resolve } from "node:path";
|
|
40
|
+
|
|
41
|
+
import { appendJsonl } from "../fs-atomic.mjs";
|
|
42
|
+
import { currentTrace } from "./trace.mjs";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The closed set of diagnostic event types. Frozen so callers can reference it
|
|
46
|
+
* (e.g. a dashboard legend) without being able to mutate the contract.
|
|
47
|
+
* @type {Readonly<Record<string, string>>}
|
|
48
|
+
*/
|
|
49
|
+
export const EVENT_TYPES = Object.freeze({
|
|
50
|
+
ITEM_RECEIVED: "item_received",
|
|
51
|
+
CLASSIFIED: "classified",
|
|
52
|
+
DISPATCHED: "dispatched",
|
|
53
|
+
SESSION_OPENED: "session_opened",
|
|
54
|
+
SESSION_CLOSED: "session_closed",
|
|
55
|
+
SENT: "sent",
|
|
56
|
+
GUARD_FAIL: "guard_fail",
|
|
57
|
+
ERROR: "error",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** Fast membership set over the closed type vocabulary. */
|
|
61
|
+
const VALID_TYPES = new Set(Object.values(EVENT_TYPES));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* True if `type` is one of the eight contract event types.
|
|
65
|
+
* @param {unknown} type
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
export function isValidEventType(type) {
|
|
69
|
+
return typeof type === "string" && VALID_TYPES.has(type);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the diagnostics log directory for the current agent.
|
|
74
|
+
* @param {object} deps
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function diagnosticsDir(deps = {}) {
|
|
78
|
+
if (deps.logDir) return resolve(deps.logDir);
|
|
79
|
+
const root = resolve(
|
|
80
|
+
deps.agentRoot ||
|
|
81
|
+
process.env.AGENT_ROOT ||
|
|
82
|
+
process.env.AGENT_DIR ||
|
|
83
|
+
process.cwd()
|
|
84
|
+
);
|
|
85
|
+
return join(root, "logs", "diagnostics");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* UTC date stamp (YYYY-MM-DD) for the given epoch ms — the stream is rotated by
|
|
90
|
+
* UTC day, matching the cost ledger and budget files so "today" is consistent
|
|
91
|
+
* across the diagnostics and accounting paths.
|
|
92
|
+
* @param {number} ms
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function dateStamp(ms) {
|
|
96
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Best-effort plain-object coercion of `attrs`. We never let a caller's odd
|
|
101
|
+
* value (a BigInt, a circular object, a function) make the append throw — that
|
|
102
|
+
* would violate the never-throw contract. Non-objects are wrapped under
|
|
103
|
+
* `{value}`; an un-serializable object is replaced by a marker.
|
|
104
|
+
* @param {unknown} attrs
|
|
105
|
+
* @returns {Record<string, unknown>}
|
|
106
|
+
*/
|
|
107
|
+
function safeAttrs(attrs) {
|
|
108
|
+
if (attrs == null) return {};
|
|
109
|
+
if (typeof attrs !== "object" || Array.isArray(attrs)) {
|
|
110
|
+
return { value: stringifySafe(attrs) };
|
|
111
|
+
}
|
|
112
|
+
return attrs;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Stringify a scalar defensively (BigInt → string, etc.). Used only for the
|
|
117
|
+
* wrapped-value fallback above; the row as a whole is serialized by appendJsonl.
|
|
118
|
+
* @param {unknown} v
|
|
119
|
+
* @returns {unknown}
|
|
120
|
+
*/
|
|
121
|
+
function stringifySafe(v) {
|
|
122
|
+
if (typeof v === "bigint") return v.toString();
|
|
123
|
+
return v;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Emit one diagnostic event onto the date-stamped JSONL stream.
|
|
128
|
+
*
|
|
129
|
+
* The row shape is `{ ts, type, trace_id, span, attrs }`:
|
|
130
|
+
* - `ts` ISO-8601 timestamp (from the injected/default clock)
|
|
131
|
+
* - `type` one of EVENT_TYPES; an unknown type is coerced to `error`
|
|
132
|
+
* and the original preserved at `attrs._invalid_type`
|
|
133
|
+
* - `trace_id` the supplied id, else the ambient `withTrace` id, else null
|
|
134
|
+
* - `span` the supplied span, else the ambient span, else "root"
|
|
135
|
+
* - `attrs` caller payload (defensively coerced to a plain object)
|
|
136
|
+
*
|
|
137
|
+
* @param {object} event
|
|
138
|
+
* @param {string} event.type one of EVENT_TYPES
|
|
139
|
+
* @param {string} [event.trace_id] correlation id (defaults to ambient trace)
|
|
140
|
+
* @param {string} [event.span] span path (defaults to ambient span / "root")
|
|
141
|
+
* @param {Record<string, unknown>} [event.attrs] structured payload
|
|
142
|
+
* @param {object} [deps]
|
|
143
|
+
* @param {() => number} [deps.now] clock (default Date.now)
|
|
144
|
+
* @param {string} [deps.logDir] explicit diagnostics dir (overrides agentRoot)
|
|
145
|
+
* @param {string} [deps.agentRoot] agent root used to derive logs/diagnostics
|
|
146
|
+
* @param {(path: string, record: unknown) => boolean} [deps.append] writer
|
|
147
|
+
* (default lib/fs-atomic appendJsonl) — injectable for tests
|
|
148
|
+
* @returns {boolean} true if the line was written, false on any failure
|
|
149
|
+
*/
|
|
150
|
+
export function emitEvent(event, deps = {}) {
|
|
151
|
+
try {
|
|
152
|
+
const now = typeof deps.now === "function" ? deps.now : Date.now;
|
|
153
|
+
const append = typeof deps.append === "function" ? deps.append : appendJsonl;
|
|
154
|
+
|
|
155
|
+
const ev = event && typeof event === "object" ? event : {};
|
|
156
|
+
const ambient = currentTrace();
|
|
157
|
+
|
|
158
|
+
const validType = isValidEventType(ev.type);
|
|
159
|
+
const type = validType ? ev.type : EVENT_TYPES.ERROR;
|
|
160
|
+
|
|
161
|
+
const trace_id =
|
|
162
|
+
(typeof ev.trace_id === "string" && ev.trace_id) ||
|
|
163
|
+
(ambient && ambient.trace_id) ||
|
|
164
|
+
null;
|
|
165
|
+
const span =
|
|
166
|
+
(typeof ev.span === "string" && ev.span) ||
|
|
167
|
+
(ambient && ambient.span) ||
|
|
168
|
+
"root";
|
|
169
|
+
|
|
170
|
+
const attrs = { ...safeAttrs(ev.attrs) };
|
|
171
|
+
if (!validType) attrs._invalid_type = ev.type === undefined ? null : ev.type;
|
|
172
|
+
|
|
173
|
+
const ms = now();
|
|
174
|
+
const row = {
|
|
175
|
+
ts: new Date(ms).toISOString(),
|
|
176
|
+
type,
|
|
177
|
+
trace_id,
|
|
178
|
+
span,
|
|
179
|
+
attrs,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const path = join(diagnosticsDir(deps), `${dateStamp(ms)}.jsonl`);
|
|
183
|
+
return append(path, row) === true;
|
|
184
|
+
} catch {
|
|
185
|
+
// Diagnostics must never crash the observed path.
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|