@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,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/util/unhandled.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* node:test coverage for the process-level unhandled-failure handlers. Every test
|
|
5
|
+
* drives an injected fake process (a real EventEmitter so listener registration /
|
|
6
|
+
* removal behaves exactly like Node's) and an injected exit + counter, so a
|
|
7
|
+
* simulated rejection/exception is captured WITHOUT touching the test runner's
|
|
8
|
+
* own process or actually exiting.
|
|
9
|
+
*
|
|
10
|
+
* Covered:
|
|
11
|
+
* - install captures a simulated unhandledRejection via onEvent (log + keep running)
|
|
12
|
+
* - the rejection is counted via the injected bump (loud, durable)
|
|
13
|
+
* - install captures a simulated uncaughtException and exits (fatal) by default
|
|
14
|
+
* - exitOnUncaught:false logs the exception but does NOT exit
|
|
15
|
+
* - install is idempotent: a second install does not double-register
|
|
16
|
+
* - uninstall removes the listeners; a later emit is not captured
|
|
17
|
+
* - a throwing onEvent sink is isolated (no second uncaught throw)
|
|
18
|
+
* - non-Error rejection values are described, not dropped
|
|
19
|
+
*
|
|
20
|
+
* Run: cd /Users/layla/maestro && node --test lib/util/unhandled.test.mjs
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import test from "node:test";
|
|
24
|
+
import assert from "node:assert/strict";
|
|
25
|
+
import { EventEmitter } from "node:events";
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
installUnhandledHandlers,
|
|
29
|
+
isInstalled,
|
|
30
|
+
UNHANDLED_COUNTERS,
|
|
31
|
+
} from "./unhandled.mjs";
|
|
32
|
+
|
|
33
|
+
/** A fake process: a real EventEmitter so on/removeListener semantics match. */
|
|
34
|
+
function fakeProcess() {
|
|
35
|
+
const ee = new EventEmitter();
|
|
36
|
+
ee.exited = [];
|
|
37
|
+
ee.exit = (code) => ee.exited.push(code);
|
|
38
|
+
return ee;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("captures a simulated unhandledRejection via onEvent and keeps running", () => {
|
|
42
|
+
const proc = fakeProcess();
|
|
43
|
+
const events = [];
|
|
44
|
+
const { uninstall, installed } = installUnhandledHandlers({
|
|
45
|
+
process: proc,
|
|
46
|
+
onEvent: (e) => events.push(e),
|
|
47
|
+
bump: () => {},
|
|
48
|
+
});
|
|
49
|
+
assert.equal(installed, true);
|
|
50
|
+
try {
|
|
51
|
+
// Simulate Node emitting an unhandled rejection.
|
|
52
|
+
proc.emit("unhandledRejection", new Error("boom-reject"));
|
|
53
|
+
assert.equal(events.length, 1);
|
|
54
|
+
assert.equal(events[0].kind, "unhandledRejection");
|
|
55
|
+
assert.equal(events[0].fatal, false);
|
|
56
|
+
assert.equal(events[0].error.message, "boom-reject");
|
|
57
|
+
// Log + keep running: a rejection must NEVER exit.
|
|
58
|
+
assert.deepEqual(proc.exited, []);
|
|
59
|
+
} finally {
|
|
60
|
+
uninstall();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("an unhandledRejection is counted via the injected bump", () => {
|
|
65
|
+
const proc = fakeProcess();
|
|
66
|
+
const counts = [];
|
|
67
|
+
const { uninstall } = installUnhandledHandlers({
|
|
68
|
+
process: proc,
|
|
69
|
+
bump: (name, attrs) => counts.push({ name, attrs }),
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
proc.emit("unhandledRejection", new Error("count-me"));
|
|
73
|
+
assert.equal(counts.length, 1);
|
|
74
|
+
assert.equal(counts[0].name, UNHANDLED_COUNTERS.REJECTION);
|
|
75
|
+
assert.equal(counts[0].attrs.message, "count-me");
|
|
76
|
+
assert.equal(counts[0].attrs.fatal, false);
|
|
77
|
+
} finally {
|
|
78
|
+
uninstall();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("captures an uncaughtException, counts it, and exits (fatal) by default", () => {
|
|
83
|
+
const proc = fakeProcess();
|
|
84
|
+
const events = [];
|
|
85
|
+
const counts = [];
|
|
86
|
+
const { uninstall } = installUnhandledHandlers({
|
|
87
|
+
process: proc,
|
|
88
|
+
onEvent: (e) => events.push(e),
|
|
89
|
+
bump: (name, attrs) => counts.push({ name, attrs }),
|
|
90
|
+
exit: (code) => proc.exit(code),
|
|
91
|
+
exitCode: 7,
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
proc.emit("uncaughtException", new Error("fatal-boom"));
|
|
95
|
+
assert.equal(events.length, 1);
|
|
96
|
+
assert.equal(events[0].kind, "uncaughtException");
|
|
97
|
+
assert.equal(events[0].fatal, true);
|
|
98
|
+
assert.equal(events[0].error.message, "fatal-boom");
|
|
99
|
+
assert.equal(counts[0].name, UNHANDLED_COUNTERS.EXCEPTION);
|
|
100
|
+
// Loud exit so launchd restarts a clean process.
|
|
101
|
+
assert.deepEqual(proc.exited, [7]);
|
|
102
|
+
} finally {
|
|
103
|
+
uninstall();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("exitOnUncaught:false logs the exception but does not exit", () => {
|
|
108
|
+
const proc = fakeProcess();
|
|
109
|
+
const events = [];
|
|
110
|
+
const { uninstall } = installUnhandledHandlers({
|
|
111
|
+
process: proc,
|
|
112
|
+
onEvent: (e) => events.push(e),
|
|
113
|
+
bump: () => {},
|
|
114
|
+
exitOnUncaught: false,
|
|
115
|
+
exit: (code) => proc.exit(code),
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
proc.emit("uncaughtException", new Error("non-fatal"));
|
|
119
|
+
assert.equal(events.length, 1);
|
|
120
|
+
assert.equal(events[0].kind, "uncaughtException");
|
|
121
|
+
assert.deepEqual(proc.exited, [], "must NOT exit when exitOnUncaught is false");
|
|
122
|
+
} finally {
|
|
123
|
+
uninstall();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("install is idempotent: a second install does not double-register", () => {
|
|
128
|
+
const proc = fakeProcess();
|
|
129
|
+
const events = [];
|
|
130
|
+
const first = installUnhandledHandlers({ process: proc, onEvent: (e) => events.push(e), bump: () => {} });
|
|
131
|
+
const second = installUnhandledHandlers({ process: proc, onEvent: (e) => events.push(e), bump: () => {} });
|
|
132
|
+
try {
|
|
133
|
+
assert.equal(first.installed, true);
|
|
134
|
+
assert.equal(second.installed, false, "second install must be a no-op");
|
|
135
|
+
assert.equal(proc.listenerCount("unhandledRejection"), 1, "exactly one listener");
|
|
136
|
+
assert.equal(proc.listenerCount("uncaughtException"), 1);
|
|
137
|
+
// One emit → one captured event (not two from a double-registration).
|
|
138
|
+
proc.emit("unhandledRejection", new Error("once"));
|
|
139
|
+
assert.equal(events.length, 1);
|
|
140
|
+
} finally {
|
|
141
|
+
first.uninstall();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("uninstall removes the listeners and isInstalled flips", () => {
|
|
146
|
+
const proc = fakeProcess();
|
|
147
|
+
const events = [];
|
|
148
|
+
const { uninstall } = installUnhandledHandlers({ process: proc, onEvent: (e) => events.push(e), bump: () => {} });
|
|
149
|
+
assert.equal(isInstalled(proc), true);
|
|
150
|
+
uninstall();
|
|
151
|
+
assert.equal(isInstalled(proc), false);
|
|
152
|
+
assert.equal(proc.listenerCount("unhandledRejection"), 0);
|
|
153
|
+
assert.equal(proc.listenerCount("uncaughtException"), 0);
|
|
154
|
+
// A later emit is no longer captured.
|
|
155
|
+
proc.emit("unhandledRejection", new Error("after-uninstall"));
|
|
156
|
+
assert.equal(events.length, 0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("a re-install after uninstall works (installed:true again)", () => {
|
|
160
|
+
const proc = fakeProcess();
|
|
161
|
+
const a = installUnhandledHandlers({ process: proc, bump: () => {} });
|
|
162
|
+
a.uninstall();
|
|
163
|
+
const b = installUnhandledHandlers({ process: proc, bump: () => {} });
|
|
164
|
+
try {
|
|
165
|
+
assert.equal(b.installed, true);
|
|
166
|
+
assert.equal(proc.listenerCount("unhandledRejection"), 1);
|
|
167
|
+
} finally {
|
|
168
|
+
b.uninstall();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a throwing onEvent sink is isolated (no second uncaught throw)", () => {
|
|
173
|
+
const proc = fakeProcess();
|
|
174
|
+
const { uninstall } = installUnhandledHandlers({
|
|
175
|
+
process: proc,
|
|
176
|
+
onEvent: () => { throw new Error("sink exploded"); },
|
|
177
|
+
bump: () => {},
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
// The handler must swallow the sink's throw rather than re-throwing.
|
|
181
|
+
assert.doesNotThrow(() => proc.emit("unhandledRejection", new Error("x")));
|
|
182
|
+
} finally {
|
|
183
|
+
uninstall();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("a throwing bump counter is isolated and onEvent still fires", () => {
|
|
188
|
+
const proc = fakeProcess();
|
|
189
|
+
const events = [];
|
|
190
|
+
const { uninstall } = installUnhandledHandlers({
|
|
191
|
+
process: proc,
|
|
192
|
+
onEvent: (e) => events.push(e),
|
|
193
|
+
bump: () => { throw new Error("counter blew up"); },
|
|
194
|
+
});
|
|
195
|
+
try {
|
|
196
|
+
assert.doesNotThrow(() => proc.emit("unhandledRejection", new Error("y")));
|
|
197
|
+
assert.equal(events.length, 1, "onEvent fires even if the counter throws");
|
|
198
|
+
} finally {
|
|
199
|
+
uninstall();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("describes non-Error rejection values rather than dropping them", () => {
|
|
204
|
+
const proc = fakeProcess();
|
|
205
|
+
const events = [];
|
|
206
|
+
const { uninstall } = installUnhandledHandlers({ process: proc, onEvent: (e) => events.push(e), bump: () => {} });
|
|
207
|
+
try {
|
|
208
|
+
proc.emit("unhandledRejection", "just a string");
|
|
209
|
+
proc.emit("unhandledRejection", { code: "ECUSTOM", detail: 1 });
|
|
210
|
+
assert.equal(events.length, 2);
|
|
211
|
+
assert.equal(events[0].error.message, "just a string");
|
|
212
|
+
assert.equal(typeof events[1].error.message, "string");
|
|
213
|
+
} finally {
|
|
214
|
+
uninstall();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — Voice session context loader.
|
|
3
|
+
*
|
|
4
|
+
* Builds a rich, agent-aware system prompt to inject into the
|
|
5
|
+
* `instructions` field of an OpenAI Realtime session, so the model
|
|
6
|
+
* walks into the call already knowing:
|
|
7
|
+
*
|
|
8
|
+
* - Who the agent is (name, role, principal, company, voice mode rules)
|
|
9
|
+
* - The agent's active strategic priorities
|
|
10
|
+
* - Top open queue items (action stack, decisions, blockers, awaiting reply)
|
|
11
|
+
* - Most recent decisions captured in knowledge/decisions/
|
|
12
|
+
* - Today's calendar (next ~4h of meetings)
|
|
13
|
+
* - Who the peer on the call is (if identifiable) + their recent history
|
|
14
|
+
*
|
|
15
|
+
* Why this matters: a Realtime session is otherwise stateless. Without
|
|
16
|
+
* pre-loaded context the agent ends up calling search_* tools mid-call,
|
|
17
|
+
* which adds 1-3s of latency and breaks the conversational rhythm. We
|
|
18
|
+
* pre-load ~2k tokens of frozen context at session start so the model
|
|
19
|
+
* can reference it natively without tool round-trips.
|
|
20
|
+
*
|
|
21
|
+
* Public API:
|
|
22
|
+
* buildContextualInstructions({agentRoot, identity, peer, callKind})
|
|
23
|
+
* → string (full instructions text to put in session config)
|
|
24
|
+
* loadAgentContextSnapshot({agentRoot}) → object (raw snapshot for audit)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
28
|
+
import { join, resolve } from "node:path";
|
|
29
|
+
|
|
30
|
+
// Optional YAML parser — loaded lazily so this module doesn't force
|
|
31
|
+
// js-yaml at import time. Returns null if unavailable.
|
|
32
|
+
let _yaml = undefined;
|
|
33
|
+
async function getYaml() {
|
|
34
|
+
if (_yaml !== undefined) return _yaml;
|
|
35
|
+
try { _yaml = (await import("js-yaml")); }
|
|
36
|
+
catch { _yaml = null; }
|
|
37
|
+
return _yaml;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Snapshot loaders (each defensive — missing files yield empty defaults)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function readYamlSync(path) {
|
|
45
|
+
if (!existsSync(path)) return null;
|
|
46
|
+
try {
|
|
47
|
+
// synchronous fallback parse: try plain JSON first (works for many
|
|
48
|
+
// generated yaml files since they're really JSON-shaped), then
|
|
49
|
+
// give up if no parser available. The async loader is preferred.
|
|
50
|
+
const body = readFileSync(path, "utf-8");
|
|
51
|
+
try { return JSON.parse(body); } catch { return body; }
|
|
52
|
+
} catch { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readYamlAsync(path) {
|
|
56
|
+
if (!existsSync(path)) return null;
|
|
57
|
+
const body = readFileSync(path, "utf-8");
|
|
58
|
+
const yaml = await getYaml();
|
|
59
|
+
if (yaml) {
|
|
60
|
+
try { return yaml.load(body); } catch { /* */ }
|
|
61
|
+
}
|
|
62
|
+
return body;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Read config/agent.json — single source of truth for identity. */
|
|
66
|
+
function loadIdentity(agentRoot) {
|
|
67
|
+
const path = join(agentRoot, "config/agent.json");
|
|
68
|
+
if (!existsSync(path)) return {};
|
|
69
|
+
try {
|
|
70
|
+
const j = JSON.parse(readFileSync(path, "utf-8"));
|
|
71
|
+
return {
|
|
72
|
+
name: j.firstName || j.fullName || "Agent",
|
|
73
|
+
fullName: j.fullName || j.firstName || "Agent",
|
|
74
|
+
role: j.title || "agent",
|
|
75
|
+
company: j.company || "the company",
|
|
76
|
+
principal: j.principal || null,
|
|
77
|
+
slackId: j.slackMemberId || null,
|
|
78
|
+
email: j.email || null,
|
|
79
|
+
timezone: j.timezone || null,
|
|
80
|
+
};
|
|
81
|
+
} catch { return {}; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Load top N open items from state/queues/<queue>.yaml. */
|
|
85
|
+
async function loadQueueTop(agentRoot, queueName, n = 3) {
|
|
86
|
+
const path = join(agentRoot, "state/queues", `${queueName}.yaml`);
|
|
87
|
+
const parsed = await readYamlAsync(path);
|
|
88
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
89
|
+
const items = (parsed.items || []).filter((x) => x && (x.status === "open" || x.status === "in_progress" || !x.status));
|
|
90
|
+
return items.slice(0, n);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Read the executive-summary dashboard (the canonical at-session-start view). */
|
|
94
|
+
async function loadExecutiveSummary(agentRoot) {
|
|
95
|
+
const path = join(agentRoot, "state/dashboards/executive-summary.yaml");
|
|
96
|
+
return await readYamlAsync(path);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Load strategic priorities (config/priorities.yaml). */
|
|
100
|
+
async function loadPriorities(agentRoot) {
|
|
101
|
+
const path = join(agentRoot, "config/priorities.yaml");
|
|
102
|
+
return await readYamlAsync(path);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Last N decision entries from knowledge/decisions/. */
|
|
106
|
+
function loadRecentDecisions(agentRoot, n = 5) {
|
|
107
|
+
const dir = join(agentRoot, "knowledge/decisions");
|
|
108
|
+
if (!existsSync(dir)) return [];
|
|
109
|
+
try {
|
|
110
|
+
const files = readdirSync(dir)
|
|
111
|
+
.filter((f) => f.endsWith(".yaml") || f.endsWith(".md") || f.endsWith(".json"))
|
|
112
|
+
.filter((f) => !f.startsWith("decision-schema"))
|
|
113
|
+
.map((f) => ({ f, mtime: statSync(join(dir, f)).mtimeMs }))
|
|
114
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
115
|
+
.slice(0, n);
|
|
116
|
+
return files.map(({ f }) => {
|
|
117
|
+
const path = join(dir, f);
|
|
118
|
+
const body = readFileSync(path, "utf-8");
|
|
119
|
+
// First 400 chars is plenty to identify the decision in voice context
|
|
120
|
+
const summary = body.split(/\n/).slice(0, 8).join(" / ").slice(0, 400);
|
|
121
|
+
return { file: f, summary };
|
|
122
|
+
});
|
|
123
|
+
} catch { return []; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Look up a peer in config/contacts.yaml by phone, slack id, or email. */
|
|
127
|
+
async function lookupPeer(agentRoot, peerIdentifier) {
|
|
128
|
+
if (!peerIdentifier) return null;
|
|
129
|
+
const path = join(agentRoot, "config/contacts.yaml");
|
|
130
|
+
if (!existsSync(path)) return null;
|
|
131
|
+
const body = readFileSync(path, "utf-8");
|
|
132
|
+
const raw = String(peerIdentifier).toLowerCase().trim();
|
|
133
|
+
if (!raw) return null;
|
|
134
|
+
// Try several normalized forms — phone numbers may be stored with or
|
|
135
|
+
// without "+", with spaces, with parentheses, etc.
|
|
136
|
+
const variants = new Set([
|
|
137
|
+
raw,
|
|
138
|
+
raw.replace(/^\+/, ""),
|
|
139
|
+
raw.replace(/[^a-z0-9]/g, ""),
|
|
140
|
+
raw.replace(/[^a-z0-9@._-]/g, ""),
|
|
141
|
+
]);
|
|
142
|
+
let idx = -1;
|
|
143
|
+
for (const v of variants) {
|
|
144
|
+
if (!v) continue;
|
|
145
|
+
idx = body.toLowerCase().indexOf(v);
|
|
146
|
+
if (idx >= 0) break;
|
|
147
|
+
}
|
|
148
|
+
if (idx < 0) return null;
|
|
149
|
+
const before = body.slice(Math.max(0, idx - 400), idx);
|
|
150
|
+
const after = body.slice(idx, Math.min(body.length, idx + 400));
|
|
151
|
+
return { found: true, block: (before + after).trim() };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Load the full team directory — humans from config/contacts.yaml + peer
|
|
156
|
+
* AI agents from config/known-agents.json. Pre-loaded into every voice
|
|
157
|
+
* session so the model has the org chart in context without needing a
|
|
158
|
+
* mid-call tool round-trip.
|
|
159
|
+
*
|
|
160
|
+
* Returns: {humans: [...], agents: [...]}
|
|
161
|
+
*/
|
|
162
|
+
async function loadTeamDirectory(agentRoot) {
|
|
163
|
+
const out = { humans: [], agents: [] };
|
|
164
|
+
|
|
165
|
+
const contactsPath = join(agentRoot, "config/contacts.yaml");
|
|
166
|
+
if (existsSync(contactsPath)) {
|
|
167
|
+
const body = readFileSync(contactsPath, "utf-8");
|
|
168
|
+
const yaml = await getYaml();
|
|
169
|
+
if (yaml) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = yaml.load(body);
|
|
172
|
+
const list = (parsed && parsed.contacts) || [];
|
|
173
|
+
out.humans = list.map((c) => ({
|
|
174
|
+
name: c.name || "(unknown)",
|
|
175
|
+
role: c.role || "",
|
|
176
|
+
email: c.email || "",
|
|
177
|
+
slack_id: c.slack_id || "",
|
|
178
|
+
classification: c.classification || "",
|
|
179
|
+
notes: c.notes || "",
|
|
180
|
+
}));
|
|
181
|
+
} catch { /* fall through to text scan */ }
|
|
182
|
+
}
|
|
183
|
+
if (!out.humans.length) {
|
|
184
|
+
// Fallback parser when js-yaml is unavailable
|
|
185
|
+
const blockRe = /-\s*name:\s*([^\n]+)\s*\n\s*role:\s*([^\n]+)\s*\n\s*company:\s*[^\n]+\s*\n\s*email:\s*([^\n]+)/g;
|
|
186
|
+
for (const match of body.matchAll(blockRe)) {
|
|
187
|
+
out.humans.push({
|
|
188
|
+
name: match[1].trim(),
|
|
189
|
+
role: match[2].trim(),
|
|
190
|
+
email: match[3].trim(),
|
|
191
|
+
slack_id: "",
|
|
192
|
+
classification: "",
|
|
193
|
+
notes: "",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const agentsPath = join(agentRoot, "config/known-agents.json");
|
|
200
|
+
if (existsSync(agentsPath)) {
|
|
201
|
+
try {
|
|
202
|
+
const j = JSON.parse(readFileSync(agentsPath, "utf-8"));
|
|
203
|
+
out.agents = (j.agents || []).map((a) => ({
|
|
204
|
+
name: a.name || "",
|
|
205
|
+
fullName: a.fullName || a.name || "",
|
|
206
|
+
role: a.role || "",
|
|
207
|
+
slackId: a.slackId || "",
|
|
208
|
+
repo: a.repo || "",
|
|
209
|
+
}));
|
|
210
|
+
} catch { /* leave empty */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Today's calendar items from state/inbox/calendar/. */
|
|
217
|
+
function loadTodayCalendar(agentRoot, maxItems = 5) {
|
|
218
|
+
const dir = join(agentRoot, "state/inbox/calendar");
|
|
219
|
+
if (!existsSync(dir)) return [];
|
|
220
|
+
try {
|
|
221
|
+
const todayPrefix = new Date().toISOString().slice(0, 10);
|
|
222
|
+
const files = readdirSync(dir)
|
|
223
|
+
.filter((f) => f.endsWith(".json") || f.endsWith(".yaml"))
|
|
224
|
+
.filter((f) => !f.endsWith(".processed"))
|
|
225
|
+
.slice(0, 50)
|
|
226
|
+
.map((f) => ({ f, mtime: statSync(join(dir, f)).mtimeMs }))
|
|
227
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
228
|
+
.slice(0, maxItems);
|
|
229
|
+
return files.map(({ f }) => {
|
|
230
|
+
try {
|
|
231
|
+
const body = readFileSync(join(dir, f), "utf-8");
|
|
232
|
+
return body.slice(0, 300);
|
|
233
|
+
} catch { return null; }
|
|
234
|
+
}).filter(Boolean);
|
|
235
|
+
} catch { return []; }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Public API
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Load a structured snapshot of the agent's context. Used by
|
|
244
|
+
* buildContextualInstructions() to compose the prompt, but exposed so
|
|
245
|
+
* the daemon can audit / serialize the same data.
|
|
246
|
+
*/
|
|
247
|
+
export async function loadAgentContextSnapshot({ agentRoot, peer = null } = {}) {
|
|
248
|
+
const root = resolve(agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd());
|
|
249
|
+
const [actionStack, decisionQueue, awaitingReply, blockers, priorities, execSummary, peerInfo, team] = await Promise.all([
|
|
250
|
+
loadQueueTop(root, "action-stack", 5),
|
|
251
|
+
loadQueueTop(root, "decision-queue", 3),
|
|
252
|
+
loadQueueTop(root, "awaiting-reply", 3),
|
|
253
|
+
loadQueueTop(root, "blocked-initiatives", 3),
|
|
254
|
+
loadPriorities(root),
|
|
255
|
+
loadExecutiveSummary(root),
|
|
256
|
+
lookupPeer(root, peer),
|
|
257
|
+
loadTeamDirectory(root),
|
|
258
|
+
]);
|
|
259
|
+
return {
|
|
260
|
+
identity: loadIdentity(root),
|
|
261
|
+
priorities,
|
|
262
|
+
executive_summary: execSummary,
|
|
263
|
+
queues: {
|
|
264
|
+
action_stack: actionStack,
|
|
265
|
+
decision_queue: decisionQueue,
|
|
266
|
+
awaiting_reply: awaitingReply,
|
|
267
|
+
blockers: blockers,
|
|
268
|
+
},
|
|
269
|
+
recent_decisions: loadRecentDecisions(root, 5),
|
|
270
|
+
today_calendar: loadTodayCalendar(root, 5),
|
|
271
|
+
peer: peerInfo,
|
|
272
|
+
team_directory: team,
|
|
273
|
+
snapshot_taken_at: new Date().toISOString(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Build the full `instructions` text to pass to OpenAI Realtime's
|
|
279
|
+
* session.update. Composes identity + frozen context snapshot in a way
|
|
280
|
+
* that's voice-friendly (short, bulleted, scannable for the model).
|
|
281
|
+
*
|
|
282
|
+
* @param {object} opts
|
|
283
|
+
* @param {string} opts.agentRoot
|
|
284
|
+
* @param {string} [opts.peer] Peer identifier (phone / slack id / email)
|
|
285
|
+
* @param {string} [opts.callKind] "inbound" | "outbound" | "browser_test"
|
|
286
|
+
* @param {string} [opts.brief] For outbound: what the agent should accomplish
|
|
287
|
+
* @returns {Promise<string>}
|
|
288
|
+
*/
|
|
289
|
+
export async function buildContextualInstructions(opts = {}) {
|
|
290
|
+
const snapshot = await loadAgentContextSnapshot({ agentRoot: opts.agentRoot, peer: opts.peer });
|
|
291
|
+
const id = snapshot.identity || {};
|
|
292
|
+
const principal = id.principal?.firstName || id.principal?.fullName || "the principal";
|
|
293
|
+
|
|
294
|
+
const lines = [];
|
|
295
|
+
|
|
296
|
+
// ── Identity + voice-mode rules
|
|
297
|
+
lines.push(`# Identity`);
|
|
298
|
+
lines.push(`You are ${id.fullName || id.name || "the agent"}, ${id.role || "an executive operator"} at ${id.company || "the company"}.`);
|
|
299
|
+
if (id.principal) {
|
|
300
|
+
lines.push(`You serve as autonomous executive command for ${principal} (${id.principal.title || "principal"}).`);
|
|
301
|
+
}
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(`# How to talk on this call`);
|
|
304
|
+
lines.push(`Sound like a thoughtful colleague, not an assistant. Full style guide: policies/communication-style.md.`);
|
|
305
|
+
lines.push(`- One or two sentences per turn, then pause. Don't monologue.`);
|
|
306
|
+
lines.push(`- Use contractions (I'll, we're, doesn't). Match the other person's register — terse if they're terse, warm if they're warm.`);
|
|
307
|
+
lines.push(`- Don't open with "Certainly!" / "Absolutely!" / "Great question!" / "I'd be happy to…" — start with the answer.`);
|
|
308
|
+
lines.push(`- Banned phrases: "I'm here to help," "Let me know if you need anything else," "I hope this helps," "As an AI," "Here's a comprehensive overview of…"`);
|
|
309
|
+
lines.push(`- Don't introduce yourself by listing your role. "Hi, this is ${id.name || "the agent"}" is enough unless asked.`);
|
|
310
|
+
lines.push(`- Disagree when warranted. Don't soften real concerns into pleasantries.`);
|
|
311
|
+
lines.push(`- For high-risk actions (sending external messages, financial commitments, deletions), read the parameters back out loud and get verbal confirmation in the SAME turn before invoking the tool.`);
|
|
312
|
+
lines.push(`- If you need a slow tool lookup, say "one second" — don't go silent for 3 seconds.`);
|
|
313
|
+
lines.push(`- If you're not sure who's on the call, ask before assuming.`);
|
|
314
|
+
lines.push("");
|
|
315
|
+
|
|
316
|
+
// ── Outbound brief (only for outbound calls)
|
|
317
|
+
if (opts.callKind === "outbound" && opts.brief) {
|
|
318
|
+
lines.push(`# Your task on this call`);
|
|
319
|
+
lines.push(opts.brief);
|
|
320
|
+
lines.push("");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── Strategic priorities
|
|
324
|
+
if (snapshot.priorities) {
|
|
325
|
+
lines.push(`# Current strategic priorities`);
|
|
326
|
+
const pBody = typeof snapshot.priorities === "string"
|
|
327
|
+
? snapshot.priorities.slice(0, 500)
|
|
328
|
+
: JSON.stringify(snapshot.priorities).slice(0, 500);
|
|
329
|
+
lines.push(pBody);
|
|
330
|
+
lines.push("");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Top open items (most-likely-mentioned-on-the-call snapshot)
|
|
334
|
+
const renderItems = (label, items) => {
|
|
335
|
+
if (!items?.length) return;
|
|
336
|
+
lines.push(`# ${label}`);
|
|
337
|
+
for (const it of items.slice(0, 5)) {
|
|
338
|
+
const title = it.title || it.summary || it.subject || it.description || "(untitled)";
|
|
339
|
+
const status = it.status || "open";
|
|
340
|
+
const due = it.due_date || it.due || it.deadline || "";
|
|
341
|
+
lines.push(`- [${status}] ${title}${due ? ` (due ${due})` : ""}`);
|
|
342
|
+
}
|
|
343
|
+
lines.push("");
|
|
344
|
+
};
|
|
345
|
+
renderItems("Top action items on your desk", snapshot.queues.action_stack);
|
|
346
|
+
renderItems("Decisions awaiting input", snapshot.queues.decision_queue);
|
|
347
|
+
renderItems("Messages awaiting your reply", snapshot.queues.awaiting_reply);
|
|
348
|
+
renderItems("Active blockers", snapshot.queues.blockers);
|
|
349
|
+
|
|
350
|
+
// ── Recent decisions (institutional context the model may need to cite)
|
|
351
|
+
if (snapshot.recent_decisions?.length) {
|
|
352
|
+
lines.push(`# Recent decisions (last ${snapshot.recent_decisions.length})`);
|
|
353
|
+
for (const d of snapshot.recent_decisions) {
|
|
354
|
+
lines.push(`- ${d.file}: ${d.summary.replace(/\s+/g, " ").slice(0, 180)}`);
|
|
355
|
+
}
|
|
356
|
+
lines.push("");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── Today's calendar
|
|
360
|
+
if (snapshot.today_calendar?.length) {
|
|
361
|
+
lines.push(`# Today's calendar (next ~4h)`);
|
|
362
|
+
for (const c of snapshot.today_calendar.slice(0, 4)) {
|
|
363
|
+
lines.push(`- ${c.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
364
|
+
}
|
|
365
|
+
lines.push("");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── Peer context (if identified)
|
|
369
|
+
if (snapshot.peer?.found) {
|
|
370
|
+
lines.push(`# Caller context`);
|
|
371
|
+
lines.push(snapshot.peer.block.slice(0, 600));
|
|
372
|
+
lines.push("");
|
|
373
|
+
} else if (opts.peer) {
|
|
374
|
+
lines.push(`# Caller`);
|
|
375
|
+
lines.push(`Identifier: ${opts.peer}. Not in your contacts roster — ask who they are if you don't already know.`);
|
|
376
|
+
lines.push("");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ── Team directory (org chart pre-loaded so the model can reference any
|
|
380
|
+
// colleague mid-call without a tool round-trip).
|
|
381
|
+
const team = snapshot.team_directory;
|
|
382
|
+
if (team && (team.humans?.length || team.agents?.length)) {
|
|
383
|
+
lines.push(`# Team directory (your org chart — refer to anyone here without needing to look them up)`);
|
|
384
|
+
if (team.humans?.length) {
|
|
385
|
+
lines.push(`## Humans (${team.humans.length})`);
|
|
386
|
+
for (const h of team.humans) {
|
|
387
|
+
const cls = h.classification ? ` [${h.classification}]` : "";
|
|
388
|
+
const slack = h.slack_id ? ` slack:${h.slack_id}` : "";
|
|
389
|
+
const note = h.notes ? ` — ${h.notes.slice(0, 80)}` : "";
|
|
390
|
+
lines.push(`- ${h.name} — ${h.role} (${h.email})${slack}${cls}${note}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (team.agents?.length) {
|
|
394
|
+
lines.push(`## Peer AI agents (${team.agents.length}) — colleagues with their own dedicated agent presence`);
|
|
395
|
+
for (const a of team.agents) {
|
|
396
|
+
const slack = a.slackId ? ` slack:${a.slackId}` : ` slack:not-yet-provisioned`;
|
|
397
|
+
lines.push(`- ${a.name} (${a.fullName}) — ${a.role}, repo:${a.repo}${slack}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
lines.push("");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ── RAG pre-load: when we have a brief (outbound call) or an identified
|
|
404
|
+
// peer (inbound), seed the prompt with top-3 hits from the agent's
|
|
405
|
+
// indexed corpus. This catches the "model walks in needing a specific
|
|
406
|
+
// fact from a decision / brief / agent definition" case without a
|
|
407
|
+
// mid-call tool round-trip.
|
|
408
|
+
try {
|
|
409
|
+
// Probe selection:
|
|
410
|
+
// - Outbound call: use the brief (what the agent is calling about).
|
|
411
|
+
// - Inbound with known peer: combine peer + agent identity.
|
|
412
|
+
// - Anything else (browser test, unknown peer): use a generic
|
|
413
|
+
// "what's hot right now" query so the session still gets some
|
|
414
|
+
// RAG seeding from the agent's own active concerns.
|
|
415
|
+
let probe;
|
|
416
|
+
if (opts.callKind === "outbound" && opts.brief) {
|
|
417
|
+
probe = opts.brief;
|
|
418
|
+
} else if (snapshot.peer?.found && opts.peer) {
|
|
419
|
+
probe = `${opts.peer} ${id.name || ""} ${id.role || ""}`;
|
|
420
|
+
} else {
|
|
421
|
+
// Synthesize a probe from the agent's current top queue + recent decisions
|
|
422
|
+
// so even cold-start sessions get *some* RAG context.
|
|
423
|
+
const top = [
|
|
424
|
+
snapshot.queues?.action_stack?.[0]?.title,
|
|
425
|
+
snapshot.queues?.decision_queue?.[0]?.title,
|
|
426
|
+
snapshot.queues?.awaiting_reply?.[0]?.title,
|
|
427
|
+
].filter(Boolean).join(" ");
|
|
428
|
+
probe = top || null;
|
|
429
|
+
}
|
|
430
|
+
if (probe) {
|
|
431
|
+
// search module is lib/rag/search.mjs — go up one from lib/voice/.
|
|
432
|
+
let searchFn = null;
|
|
433
|
+
try {
|
|
434
|
+
const { search: s } = await import("../rag/search.mjs");
|
|
435
|
+
searchFn = s;
|
|
436
|
+
} catch { /* RAG not built yet — silent skip */ }
|
|
437
|
+
if (searchFn) {
|
|
438
|
+
const hits = await searchFn({ query: probe, topN: 3, agentRoot: opts.agentRoot });
|
|
439
|
+
if (hits?.length) {
|
|
440
|
+
lines.push(`# Pre-loaded RAG context (top ${hits.length} hits from your indexed corpus)`);
|
|
441
|
+
for (const h of hits) {
|
|
442
|
+
lines.push(`- ${h.source_path} [${h.kind}] — ${h.title}`);
|
|
443
|
+
lines.push(` ${h.snippet.replace(/\s+/g, " ").slice(0, 280)}`);
|
|
444
|
+
}
|
|
445
|
+
lines.push("");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} catch { /* RAG pre-load failure is non-fatal */ }
|
|
450
|
+
|
|
451
|
+
// ── Tool guidance
|
|
452
|
+
lines.push(`# Tools available to you`);
|
|
453
|
+
lines.push(`You have function tools to send Slack messages, email, WhatsApp, SMS, voice messages, search the agent's memory (decisions, email, Slack, calendar), look up people, update operational queues, and run \`rag_search\` against the agent's full indexed corpus (memory, knowledge, decisions, queues, briefs, policies). Each tool call adds ~1-2s of silence — use them when needed but don't burn turns on speculative lookups.`);
|
|
454
|
+
lines.push("");
|
|
455
|
+
|
|
456
|
+
// ── Final
|
|
457
|
+
lines.push(`Remain in ${id.name || "the agent"}'s voice. Honour the action-classification policy: high-risk actions require verbal confirmation in the same turn.`);
|
|
458
|
+
|
|
459
|
+
// Hard cap at ~8k chars so we don't blow the instructions limit (which is
|
|
460
|
+
// generous but we want to keep call setup fast). Bumped from 6k after
|
|
461
|
+
// adding the team directory section.
|
|
462
|
+
const text = lines.join("\n");
|
|
463
|
+
return text.length > 8000 ? text.slice(0, 7980) + "\n…(truncated)" : text;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export default { buildContextualInstructions, loadAgentContextSnapshot };
|