@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,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/daemon/session-outcomes.mjs — OUTCOME DISCIPLINE for daemon sessions.
|
|
3
|
+
*
|
|
4
|
+
* hq already guarantees this for LIVE CALLS (src/server/call-voice/chair.ts +
|
|
5
|
+
* outcomes.ts): a meeting's action points become real board rows, and a teardown
|
|
6
|
+
* SWEEP lands anything the chair captured but never wrote. The avatar worker is
|
|
7
|
+
* deliberately brainless, so an agent-sdk colleague on a call inherits that for
|
|
8
|
+
* free — this module does NOT duplicate any of it.
|
|
9
|
+
*
|
|
10
|
+
* What had NO equivalent is maestro's OWN daemon turns. A spawned session can
|
|
11
|
+
* decide things and commit to next steps ("I'll send the revised deck to Alice
|
|
12
|
+
* on Friday") that then exist only in `state/sessions/logs/<id>.log` — nobody
|
|
13
|
+
* reads a session log, so the commitment is lost and the next turn re-decides it.
|
|
14
|
+
* That is the exact failure the hq chair removes, and this closes it for the
|
|
15
|
+
* daemon.
|
|
16
|
+
*
|
|
17
|
+
* THE SHAPE (mirroring the chair's DISCIPLINE, not its code):
|
|
18
|
+
*
|
|
19
|
+
* CAPTURE The prompt asks the session to end with an `OUTCOMES:` block (see
|
|
20
|
+
* prompt-builder's OUTCOME_DISCIPLINE). `parseOutcomes` reads that
|
|
21
|
+
* block off the session's final text — deterministic, bounded, and
|
|
22
|
+
* matched ONLY on the literal `OUTCOMES` heading, which is the exact
|
|
23
|
+
* contract the prompt establishes. It is deliberately NOT tolerant of
|
|
24
|
+
* "Next steps" / "Action items": a turn that merely wrote an ordinary
|
|
25
|
+
* prose "Next steps:" section never agreed to a machine-read contract,
|
|
26
|
+
* and must not silently mint org-visible board rows because of it.
|
|
27
|
+
* GATE `outcomeSourceShareable` decides whether this turn's source is one
|
|
28
|
+
* whose commitments may become ORG-VISIBLE. Fail-CLOSED: only a Slack
|
|
29
|
+
* conversation POSITIVELY established as public (`channel_type` /
|
|
30
|
+
* `is_private` off the event — never the id prefix, which Slack does
|
|
31
|
+
* not guarantee) qualifies. A DM, a private channel, email, an unknown
|
|
32
|
+
* source or no source at all lands nothing — see the gate's own note.
|
|
33
|
+
* LAND `landOutcomes` writes each one through the EXISTING org protocol:
|
|
34
|
+
* `board.create` for the task (lib/org/board.mjs → createItem) plus a
|
|
35
|
+
* single `knowledge.append` episode (lib/org/knowledge.mjs → remember)
|
|
36
|
+
* recording the commitment set. NO new protocol methods — the protocol
|
|
37
|
+
* is checksum-pinned and cross-repo-synced.
|
|
38
|
+
* SWEEP `sweepSessionOutcomes` is the post-session hook, called from the
|
|
39
|
+
* dispatcher's onClose. It is DRIVEN, not left to the model to
|
|
40
|
+
* remember: whatever the turn captured is landed at session end.
|
|
41
|
+
*
|
|
42
|
+
* IDEMPOTENT, TWO WAYS. In-process via the per-session `landed` flag (so a double
|
|
43
|
+
* close is a no-op), and — the one that survives a restart — via a DETERMINISTIC
|
|
44
|
+
* `itemId` derived from (sessionId, normalised title). `board.create` treats a
|
|
45
|
+
* caller-supplied id that already exists as a replay, so re-landing the same
|
|
46
|
+
* commitment never doubles a board row.
|
|
47
|
+
*
|
|
48
|
+
* FAIL-OPEN, ALWAYS. Disabled, unconfigured, unreachable, unparseable ⇒ a benign
|
|
49
|
+
* `{created:0, skipped:true}`. Nothing here may ever throw into the dispatch path:
|
|
50
|
+
* an outcome write must never cost the user their reply.
|
|
51
|
+
*
|
|
52
|
+
* OFF BY DEFAULT behind `DAEMON_SESSION_OUTCOMES=1` (same opt-in shape as
|
|
53
|
+
* `DAEMON_CONTEXT_COMPILER`). This writes REAL rows to the live org board from
|
|
54
|
+
* every clean daemon turn; on a live daemon the safe default is a machine that
|
|
55
|
+
* does nothing until it is switched on deliberately.
|
|
56
|
+
*
|
|
57
|
+
* Pure-core + injectable IO: every collaborator (createItemImpl / rememberImpl /
|
|
58
|
+
* cfg / enabled) can be injected, so the tests drive the real logic without a
|
|
59
|
+
* server. ESM, Node builtins only.
|
|
60
|
+
*
|
|
61
|
+
* @module scripts/daemon/session-outcomes
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
"use strict";
|
|
65
|
+
|
|
66
|
+
import { createHash } from "node:crypto";
|
|
67
|
+
|
|
68
|
+
import { createItem as orgCreateItem } from "../../lib/org/board.mjs";
|
|
69
|
+
import { isEnabled as orgEnabled, configFromAgent } from "../../lib/org/client.mjs";
|
|
70
|
+
import { remember as orgRemember } from "../../lib/org/knowledge.mjs";
|
|
71
|
+
|
|
72
|
+
/** Cap what ONE turn can write. A runaway summary must not flood the board. */
|
|
73
|
+
export const MAX_OUTCOMES = 8;
|
|
74
|
+
|
|
75
|
+
const MAX_TITLE_CHARS = 200;
|
|
76
|
+
const MAX_PARTY_CHARS = 80;
|
|
77
|
+
/** Only ever scan the tail of a long transcript for the outcomes block. */
|
|
78
|
+
const MAX_SCAN_CHARS = 20_000;
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Flag
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Is the outcome discipline switched on? OFF unless `DAEMON_SESSION_OUTCOMES=1`.
|
|
86
|
+
* @param {object} [env] injectable environment (defaults to process.env)
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
export function outcomesEnabled(env = process.env) {
|
|
90
|
+
return String((env && env.DAEMON_SESSION_OUTCOMES) || "") === "1";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Source gate — may THIS turn's commitments become ORG-VISIBLE?
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* CONFIDENTIALITY GATE. A landed outcome is not a private note: `board.create`
|
|
99
|
+
* is sent with no `workstreamId` (so the server gives the row no `channelId`,
|
|
100
|
+
* and hq's channel-less board query applies no channel filter) and the episode's
|
|
101
|
+
* `knowledge.append` carries no group (so hq's `normalizeGroupId(undefined)`
|
|
102
|
+
* resolves to the `org` group). Both artifacts are therefore readable by EVERY
|
|
103
|
+
* member of the org — and they carry the commitment text PLUS the topic line
|
|
104
|
+
* (`classResult.summary`, or the item's own subject).
|
|
105
|
+
*
|
|
106
|
+
* That is correct for a public channel message. It is NOT correct for a private
|
|
107
|
+
* DM: the daemon would be republishing a one-to-one conversation to the whole
|
|
108
|
+
* org. The protocol gives us no way to derive a narrower audience here —
|
|
109
|
+
* `board.create` accepts only a `workstreamId`, and a Slack DM maps to no
|
|
110
|
+
* workstream — so the gate is the answer: restrict the sweep to sources that are
|
|
111
|
+
* already org-visible.
|
|
112
|
+
*
|
|
113
|
+
* FAIL-CLOSED, unlike everything else in this module. Every other failure mode
|
|
114
|
+
* here is fail-open because the cost is a lost board row; the cost of getting
|
|
115
|
+
* THIS wrong is a leaked private conversation, so anything we cannot positively
|
|
116
|
+
* establish as public lands nothing. Email is one-to-one correspondence, and a
|
|
117
|
+
* source we do not recognise is by definition not established as public.
|
|
118
|
+
*
|
|
119
|
+
* THE SIGNAL IS `channel_type`/`is_private`, NEVER THE ID PREFIX. Slack is
|
|
120
|
+
* explicit that a conversation ID's leading letter is NOT a reliable type
|
|
121
|
+
* indicator: a PRIVATE channel can and does carry a `C…` id (workspaces created
|
|
122
|
+
* after the `G…`-for-groups era mint `C…` ids for private channels too), and an
|
|
123
|
+
* mpim may carry either. Only `D` for a DM is dependable. Admitting on `/^C/`
|
|
124
|
+
* therefore admitted private channels — the exact leak this gate exists to
|
|
125
|
+
* prevent. So admission now requires a POSITIVE public signal carried on the
|
|
126
|
+
* item by the ingest layer straight off Slack's event envelope:
|
|
127
|
+
*
|
|
128
|
+
* `channel_type === "channel"` Slack's message-event conversation type
|
|
129
|
+
* ("channel" public | "group" private |
|
|
130
|
+
* "im" DM | "mpim" group DM), or
|
|
131
|
+
* `is_private === false` an explicit privacy verdict (conversations.list
|
|
132
|
+
* / conversations.info).
|
|
133
|
+
*
|
|
134
|
+
* Anything else — a missing signal, an unrecognised type, an id we can only
|
|
135
|
+
* guess about — lands nothing. Prefixes are still consulted, but only in the
|
|
136
|
+
* DENYING direction (`D…` ⇒ DM, `G…` ⇒ private group), where a false positive
|
|
137
|
+
* costs a board row rather than a confidence.
|
|
138
|
+
*
|
|
139
|
+
* Producers: scripts/poller/slack-socket-mode.mjs (`event.channel_type` off the
|
|
140
|
+
* Socket Mode envelope), scripts/poller/slack-poller.mjs (`is_private` off
|
|
141
|
+
* conversations.list), and the YAML round-trip that carries them to the daemon
|
|
142
|
+
* (scripts/poller/utils.mjs `inboxItemToYaml` ⇄
|
|
143
|
+
* scripts/poller/inbox-scan-poller.mjs `parseInboxItemYaml`, which decodes
|
|
144
|
+
* `is_private` as a TRI-STATE so "absent" never reads as "public").
|
|
145
|
+
*
|
|
146
|
+
* @param {object} [item] the inbox item the turn was about
|
|
147
|
+
* @returns {{shareable:boolean, reason:string}}
|
|
148
|
+
*/
|
|
149
|
+
export function outcomeSourceShareable(item) {
|
|
150
|
+
if (!item || typeof item !== "object") return { shareable: false, reason: "no-source" };
|
|
151
|
+
|
|
152
|
+
// Explicit privacy signals win outright, whatever the ids say.
|
|
153
|
+
if (item.is_dm === true) return { shareable: false, reason: "dm" };
|
|
154
|
+
if (item.is_private === true) return { shareable: false, reason: "private-channel" };
|
|
155
|
+
|
|
156
|
+
const channel = String(item.channel || "").trim();
|
|
157
|
+
if (/^dm\//i.test(channel)) return { shareable: false, reason: "dm" };
|
|
158
|
+
if (/^email\//i.test(channel)) return { shareable: false, reason: "private-correspondence" };
|
|
159
|
+
|
|
160
|
+
const service = String(item.service || "").trim().toLowerCase();
|
|
161
|
+
if (!service) return { shareable: false, reason: "unknown-source" };
|
|
162
|
+
if (service !== "slack") return { shareable: false, reason: "non-channel-source" };
|
|
163
|
+
|
|
164
|
+
const channelId = String(item.channel_id || "").trim();
|
|
165
|
+
if (!channelId) return { shareable: false, reason: "unknown-channel" };
|
|
166
|
+
// Prefixes may only DENY (see the note above): `D` is Slack's one dependable
|
|
167
|
+
// prefix, and a `G` id is never a public channel.
|
|
168
|
+
if (/^D/.test(channelId)) return { shareable: false, reason: "dm" };
|
|
169
|
+
if (/^G/.test(channelId)) return { shareable: false, reason: "private-channel" };
|
|
170
|
+
|
|
171
|
+
// Now the positive signal. Unrecognised types fail closed rather than falling
|
|
172
|
+
// through to the is_private check — a type we cannot interpret is not a type
|
|
173
|
+
// we may publish from.
|
|
174
|
+
const channelType = String(item.channel_type || "").trim().toLowerCase();
|
|
175
|
+
if (channelType) {
|
|
176
|
+
if (channelType === "im" || channelType === "mpim") return { shareable: false, reason: "dm" };
|
|
177
|
+
if (channelType === "group") return { shareable: false, reason: "private-channel" };
|
|
178
|
+
if (channelType !== "channel") return { shareable: false, reason: "unknown-channel-type" };
|
|
179
|
+
return { shareable: true, reason: "public-channel" };
|
|
180
|
+
}
|
|
181
|
+
if (item.is_private === false) return { shareable: true, reason: "public-channel" };
|
|
182
|
+
|
|
183
|
+
// No `channel_type`, no `is_private`: the id alone cannot establish privacy.
|
|
184
|
+
return { shareable: false, reason: "privacy-unknown" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Capture — pure parsing of the session's final text
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The heading that introduces a turn's commitments.
|
|
193
|
+
*
|
|
194
|
+
* Generous about markdown decoration (`## `, `**…**`, trailing colon) because
|
|
195
|
+
* that is what a model actually emits — and STRICT about the word, which must be
|
|
196
|
+
* the literal `OUTCOMES` the prompt asked for.
|
|
197
|
+
*
|
|
198
|
+
* It used to also match "next steps" / "action items" / "action points" /
|
|
199
|
+
* "commitments". That made the capture surface WIDER THAN THE CONTRACT: a turn
|
|
200
|
+
* that merely wrote a prose "Next steps:" section — one whose prompt may never
|
|
201
|
+
* have carried OUTCOME_DISCIPLINE at all, because prompt-builder injects it only
|
|
202
|
+
* when the flag is already `1` at buildPrompt time while the sweep re-reads the
|
|
203
|
+
* flag at close time — would silently mint org-visible board rows it never
|
|
204
|
+
* agreed to. Capture is now exactly the block the prompt establishes.
|
|
205
|
+
*
|
|
206
|
+
* The PLURAL is required for the same reason: the contract is literally
|
|
207
|
+
* `OUTCOMES:`, while a singular "Outcome:" is an ordinary prose section header —
|
|
208
|
+
* accepting it would reopen the very gap this narrowing closed.
|
|
209
|
+
*/
|
|
210
|
+
const OUTCOME_HEADING =
|
|
211
|
+
/^\s*(?:#{1,6}\s*)?(?:\*\*|__)?\s*outcomes\s*(?:\*\*|__)?\s*(?::\s*(.*))?\s*$/i;
|
|
212
|
+
|
|
213
|
+
/** A markdown list item: `- x`, `* x`, `1. x`, `- [ ] x`. */
|
|
214
|
+
const LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)])\s+(?:\[[ xX]?\]\s*)?(.+)$/;
|
|
215
|
+
|
|
216
|
+
/** "nothing to land" answers, so an honest empty block stays empty. */
|
|
217
|
+
const EMPTY_ANSWER = /^(?:none|n\/?a|nothing|no (?:outcomes?|next steps?|action items?|commitments?))\b/i;
|
|
218
|
+
|
|
219
|
+
/** Strip markdown emphasis/code fences from a captured title. */
|
|
220
|
+
function plain(s) {
|
|
221
|
+
return String(s || "")
|
|
222
|
+
.replace(/[`*_]+/g, "")
|
|
223
|
+
.replace(/\s+/g, " ")
|
|
224
|
+
.trim();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Split a captured line into its title and any owner/timing the session stated.
|
|
229
|
+
* Segments are separated by an em/en dash, `--`, `|` or `;` and tagged
|
|
230
|
+
* `owner:`/`by:` and `due:`/`when:`. Untagged trailing segments stay in the title
|
|
231
|
+
* — guessing would put the wrong name on someone's task.
|
|
232
|
+
*/
|
|
233
|
+
function splitParts(line) {
|
|
234
|
+
const segments = String(line).split(/\s+(?:—|–|--|\||;)\s+/);
|
|
235
|
+
let title = segments.shift() || "";
|
|
236
|
+
let owner;
|
|
237
|
+
let due;
|
|
238
|
+
const kept = [];
|
|
239
|
+
for (const seg of segments) {
|
|
240
|
+
const m = /^\s*(owner|assignee|by|due|when|timing)\s*:\s*(.+)$/i.exec(seg);
|
|
241
|
+
if (!m) {
|
|
242
|
+
kept.push(seg);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const tag = m[1].toLowerCase();
|
|
246
|
+
const value = plain(m[2]);
|
|
247
|
+
if (!value) continue;
|
|
248
|
+
if ((tag === "owner" || tag === "assignee" || tag === "by") && !owner) owner = value;
|
|
249
|
+
else if (!due) due = value;
|
|
250
|
+
}
|
|
251
|
+
if (kept.length) title = [title, ...kept].join(" — ");
|
|
252
|
+
return { title: plain(title), owner, due };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Extract this turn's commitments from its final text. Pure, bounded, and
|
|
257
|
+
* order-preserving; returns `[]` when the turn committed to nothing (which is a
|
|
258
|
+
* perfectly good answer — most turns should).
|
|
259
|
+
*
|
|
260
|
+
* @param {string} text the session's final assistant text
|
|
261
|
+
* @returns {Array<{title:string, owner?:string, due?:string}>}
|
|
262
|
+
*/
|
|
263
|
+
export function parseOutcomes(text) {
|
|
264
|
+
const raw = String(text || "");
|
|
265
|
+
if (!raw.trim()) return [];
|
|
266
|
+
const body = raw.length > MAX_SCAN_CHARS ? raw.slice(-MAX_SCAN_CHARS) : raw;
|
|
267
|
+
const lines = body.split(/\r?\n/);
|
|
268
|
+
|
|
269
|
+
const out = [];
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
|
|
272
|
+
const push = (line) => {
|
|
273
|
+
if (out.length >= MAX_OUTCOMES) return;
|
|
274
|
+
const { title, owner, due } = splitParts(line);
|
|
275
|
+
if (!title || EMPTY_ANSWER.test(title)) return;
|
|
276
|
+
const key = title.toLowerCase();
|
|
277
|
+
if (seen.has(key)) return;
|
|
278
|
+
seen.add(key);
|
|
279
|
+
const entry = { title: title.slice(0, MAX_TITLE_CHARS) };
|
|
280
|
+
if (owner) entry.owner = owner.slice(0, MAX_PARTY_CHARS);
|
|
281
|
+
if (due) entry.due = due.slice(0, MAX_PARTY_CHARS);
|
|
282
|
+
out.push(entry);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < lines.length && out.length < MAX_OUTCOMES; i += 1) {
|
|
286
|
+
const heading = OUTCOME_HEADING.exec(lines[i]);
|
|
287
|
+
if (!heading) continue;
|
|
288
|
+
|
|
289
|
+
// `OUTCOMES: none` / `Next steps: ship the deck` — an inline single answer.
|
|
290
|
+
const inline = plain(heading[1] || "");
|
|
291
|
+
if (inline) {
|
|
292
|
+
if (!EMPTY_ANSWER.test(inline)) push(inline);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Otherwise consume the list that follows. Blank lines are allowed BEFORE the
|
|
297
|
+
// first item (models like a blank after a heading) and TERMINATE the block
|
|
298
|
+
// once items have started, so the paragraph after the list is never swallowed.
|
|
299
|
+
let started = false;
|
|
300
|
+
for (let j = i + 1; j < lines.length && out.length < MAX_OUTCOMES; j += 1) {
|
|
301
|
+
const line = lines[j];
|
|
302
|
+
if (!line.trim()) {
|
|
303
|
+
if (started) { i = j; break; }
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const item = LIST_ITEM.exec(line);
|
|
307
|
+
if (!item) { i = j - 1; break; }
|
|
308
|
+
started = true;
|
|
309
|
+
push(item[1]);
|
|
310
|
+
i = j;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Recover the final assistant text from a `claude --print --output-format json`
|
|
319
|
+
* stdout buffer. Tolerant of a leading banner line (same slice-the-first-balanced
|
|
320
|
+
* -object trick the dispatcher's usage parser uses) and of a plain-text run.
|
|
321
|
+
* Never throws.
|
|
322
|
+
*
|
|
323
|
+
* @param {string} stdout
|
|
324
|
+
* @returns {string} the result text, or "" when nothing usable was found
|
|
325
|
+
*/
|
|
326
|
+
export function resultTextFromStdout(stdout) {
|
|
327
|
+
const trimmed = String(stdout || "").trim();
|
|
328
|
+
if (!trimmed) return "";
|
|
329
|
+
let obj = null;
|
|
330
|
+
try {
|
|
331
|
+
obj = JSON.parse(trimmed);
|
|
332
|
+
} catch {
|
|
333
|
+
const start = trimmed.indexOf("{");
|
|
334
|
+
const end = trimmed.lastIndexOf("}");
|
|
335
|
+
if (start !== -1 && end > start) {
|
|
336
|
+
try { obj = JSON.parse(trimmed.slice(start, end + 1)); } catch { obj = null; }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (obj && typeof obj === "object") {
|
|
340
|
+
if (typeof obj.result === "string") return obj.result;
|
|
341
|
+
if (obj.result && typeof obj.result.text === "string") return obj.result.text;
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
// Not JSON at all (a --print text run): the whole buffer is the reply.
|
|
345
|
+
return trimmed;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// Per-session state — the daemon's small echo of the chair's ChairState
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
/** sessionId -> { outcomes: Array, landed: boolean } */
|
|
353
|
+
const sessions = new Map();
|
|
354
|
+
|
|
355
|
+
/** Bound the map so a long-lived daemon cannot accumulate session state. */
|
|
356
|
+
const MAX_TRACKED_SESSIONS = 200;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Record commitments captured for a session (deduped, bounded, order-preserving).
|
|
360
|
+
* Safe to call repeatedly; a later capture merges into the earlier one.
|
|
361
|
+
*
|
|
362
|
+
* @param {string} sessionId
|
|
363
|
+
* @param {Array<{title:string, owner?:string, due?:string}>} outcomes
|
|
364
|
+
* @returns {Array} the session's captured outcomes after the merge
|
|
365
|
+
*/
|
|
366
|
+
export function noteOutcomes(sessionId, outcomes) {
|
|
367
|
+
if (!sessionId) return [];
|
|
368
|
+
let s = sessions.get(sessionId);
|
|
369
|
+
if (!s) {
|
|
370
|
+
if (sessions.size >= MAX_TRACKED_SESSIONS) {
|
|
371
|
+
// Oldest-first eviction; insertion order is Map's iteration order.
|
|
372
|
+
const oldest = sessions.keys().next();
|
|
373
|
+
if (!oldest.done) sessions.delete(oldest.value);
|
|
374
|
+
}
|
|
375
|
+
s = { outcomes: [], landed: false };
|
|
376
|
+
sessions.set(sessionId, s);
|
|
377
|
+
}
|
|
378
|
+
for (const o of Array.isArray(outcomes) ? outcomes : []) {
|
|
379
|
+
const title = plain(o && o.title).slice(0, MAX_TITLE_CHARS);
|
|
380
|
+
if (!title) continue;
|
|
381
|
+
if (s.outcomes.some((x) => x.title.toLowerCase() === title.toLowerCase())) continue;
|
|
382
|
+
if (s.outcomes.length >= MAX_OUTCOMES) break;
|
|
383
|
+
const entry = { title };
|
|
384
|
+
if (o.owner) entry.owner = String(o.owner).slice(0, MAX_PARTY_CHARS);
|
|
385
|
+
if (o.due) entry.due = String(o.due).slice(0, MAX_PARTY_CHARS);
|
|
386
|
+
s.outcomes.push(entry);
|
|
387
|
+
}
|
|
388
|
+
return s.outcomes;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** What a session captured, or `[]`. */
|
|
392
|
+
export function getSessionOutcomes(sessionId) {
|
|
393
|
+
const s = sessions.get(sessionId);
|
|
394
|
+
return s ? s.outcomes : [];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Have this session's outcomes already been landed? */
|
|
398
|
+
export function outcomesLanded(sessionId) {
|
|
399
|
+
const s = sessions.get(sessionId);
|
|
400
|
+
return !!(s && s.landed);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Mark a session's outcomes landed (the in-process half of idempotency). */
|
|
404
|
+
export function markOutcomesLanded(sessionId) {
|
|
405
|
+
const s = sessions.get(sessionId);
|
|
406
|
+
if (s) s.landed = true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Drop a session's state (called after the sweep). */
|
|
410
|
+
export function clearSessionOutcomes(sessionId) {
|
|
411
|
+
sessions.delete(sessionId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Test seam. */
|
|
415
|
+
export function _resetSessionOutcomes() {
|
|
416
|
+
sessions.clear();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Land — through the EXISTING org protocol (board.create + knowledge.append)
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* A stable, replay-safe board id for one commitment of one session. `board.create`
|
|
425
|
+
* treats a caller-supplied id that already exists as an idempotent replay, so this
|
|
426
|
+
* is what makes re-landing safe across a daemon restart (where the in-process
|
|
427
|
+
* `landed` flag is gone).
|
|
428
|
+
*
|
|
429
|
+
* @param {string} sessionId
|
|
430
|
+
* @param {string} title
|
|
431
|
+
* @returns {string}
|
|
432
|
+
*/
|
|
433
|
+
export function outcomeItemId(sessionId, title) {
|
|
434
|
+
const norm = plain(title).toLowerCase();
|
|
435
|
+
const digest = createHash("sha1")
|
|
436
|
+
.update(`${String(sessionId || "")}${norm}`)
|
|
437
|
+
.digest("hex")
|
|
438
|
+
.slice(0, 24);
|
|
439
|
+
return `so-${digest}`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** The provenance line that lets a board row be traced back to its turn. */
|
|
443
|
+
function detailFor(o, a) {
|
|
444
|
+
const bits = [];
|
|
445
|
+
if (o.owner) bits.push(`Owner as stated: ${o.owner}.`);
|
|
446
|
+
if (o.due) bits.push(`Timing as stated: ${o.due}.`);
|
|
447
|
+
bits.push(
|
|
448
|
+
`Committed in ${a.who} daemon session ${a.sessionId || "unknown"}${a.topic ? ` — ${a.topic}` : ""}.`
|
|
449
|
+
);
|
|
450
|
+
return bits.join(" ").slice(0, 5000);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Write a turn's commitments through the org protocol: one `board.create` per
|
|
455
|
+
* commitment plus ONE `knowledge.append` episode recording the set (so the outcome
|
|
456
|
+
* is both actionable and recallable). Fail-open — every failure path returns a
|
|
457
|
+
* benign result rather than throwing.
|
|
458
|
+
*
|
|
459
|
+
* @param {object} a
|
|
460
|
+
* @param {string} a.sessionId
|
|
461
|
+
* @param {Array<{title:string, owner?:string, due?:string}>} a.outcomes
|
|
462
|
+
* @param {object} [a.cfg] agent org config (config/org.yaml shape)
|
|
463
|
+
* @param {object} [a.item] the inbox/queue item the turn was about. REQUIRED for
|
|
464
|
+
* anything to land: the confidentiality gate is fail-closed, so an absent or
|
|
465
|
+
* non-public source writes nothing (see `outcomeSourceShareable`).
|
|
466
|
+
* @param {object} [a.classResult] classifier output (for the topic line)
|
|
467
|
+
* @param {string} [a.agentName] the agent's own name, for the episode text
|
|
468
|
+
* @param {Function} [a.createItemImpl] injectable board.create
|
|
469
|
+
* @param {Function} [a.rememberImpl] injectable knowledge.append
|
|
470
|
+
* @param {Function} [a.fetchImpl] injectable fetch, forwarded to the client
|
|
471
|
+
* @returns {Promise<{created:number, remembered?:boolean, skipped?:boolean, reason?:string}>}
|
|
472
|
+
*/
|
|
473
|
+
export async function landOutcomes(a = {}) {
|
|
474
|
+
try {
|
|
475
|
+
const cfg = a.cfg || {};
|
|
476
|
+
if (!orgEnabled(cfg)) return { created: 0, skipped: true, reason: "org-disabled" };
|
|
477
|
+
|
|
478
|
+
// The confidentiality gate sits on the WRITER, not just the sweep, so every
|
|
479
|
+
// path into the org protocol is covered. Fail-closed by construction.
|
|
480
|
+
const gate = outcomeSourceShareable(a.item);
|
|
481
|
+
if (!gate.shareable) return { created: 0, skipped: true, reason: `source-${gate.reason}` };
|
|
482
|
+
|
|
483
|
+
const wanted = (Array.isArray(a.outcomes) ? a.outcomes : [])
|
|
484
|
+
.filter((o) => o && plain(o.title))
|
|
485
|
+
.slice(0, MAX_OUTCOMES);
|
|
486
|
+
if (wanted.length === 0) return { created: 0, skipped: true, reason: "nothing-to-land" };
|
|
487
|
+
|
|
488
|
+
const conn = configFromAgent(cfg);
|
|
489
|
+
const createImpl = a.createItemImpl || orgCreateItem;
|
|
490
|
+
const rememberImpl = a.rememberImpl || orgRemember;
|
|
491
|
+
const who = a.agentName || "the agent";
|
|
492
|
+
const topic =
|
|
493
|
+
(a.classResult && a.classResult.summary) ||
|
|
494
|
+
(a.item && (a.item.subject || a.item.title)) ||
|
|
495
|
+
null;
|
|
496
|
+
|
|
497
|
+
let created = 0;
|
|
498
|
+
const landedTitles = [];
|
|
499
|
+
for (const o of wanted) {
|
|
500
|
+
const title = plain(o.title).slice(0, MAX_TITLE_CHARS);
|
|
501
|
+
const itemId = outcomeItemId(a.sessionId, title);
|
|
502
|
+
try {
|
|
503
|
+
// board.create is `.strict()` server-side — send only its own fields.
|
|
504
|
+
const res = await createImpl(
|
|
505
|
+
{ itemId, title, detail: detailFor(o, { who, sessionId: a.sessionId, topic }), priority: "P2" },
|
|
506
|
+
{ base: conn.base, token: conn.token, fetchImpl: a.fetchImpl, idempotencyKey: `board.create:${itemId}` }
|
|
507
|
+
);
|
|
508
|
+
// The client never throws; a failure comes back as an error frame.
|
|
509
|
+
if (!res || res.error) continue;
|
|
510
|
+
created += 1;
|
|
511
|
+
landedTitles.push(title);
|
|
512
|
+
} catch {
|
|
513
|
+
// One bad row must not strand the rest.
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// One episode for the whole set, so the commitments are recallable even if the
|
|
518
|
+
// board is unreachable. The SERVER derives the fact's group/ACL from the
|
|
519
|
+
// caller's verified identity — we pass no group hint.
|
|
520
|
+
let remembered = false;
|
|
521
|
+
const forEpisode = landedTitles.length ? landedTitles : wanted.map((o) => plain(o.title));
|
|
522
|
+
try {
|
|
523
|
+
const text = [
|
|
524
|
+
`${who} committed to the following in a daemon session${topic ? ` re: ${topic}` : ""}:`,
|
|
525
|
+
...forEpisode.map((t) => `- ${t}`),
|
|
526
|
+
]
|
|
527
|
+
.join(" ")
|
|
528
|
+
.slice(0, 1200);
|
|
529
|
+
const r = await rememberImpl(text, {
|
|
530
|
+
cfg,
|
|
531
|
+
kind: "episode",
|
|
532
|
+
provenance: { source: "daemon-session-outcomes", sessionId: a.sessionId || null },
|
|
533
|
+
idempotencyKey: `knowledge.append:outcomes:${a.sessionId || "unknown"}`,
|
|
534
|
+
fetchImpl: a.fetchImpl,
|
|
535
|
+
});
|
|
536
|
+
remembered = !!(r && r.ok);
|
|
537
|
+
} catch {
|
|
538
|
+
// Fail-open.
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (created > 0 || remembered) markOutcomesLanded(a.sessionId);
|
|
542
|
+
return { created, remembered };
|
|
543
|
+
} catch {
|
|
544
|
+
return { created: 0, skipped: true };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
// Sweep — the post-session hook
|
|
550
|
+
// ---------------------------------------------------------------------------
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Post-session hook: capture this turn's commitments and LAND them. This is the
|
|
554
|
+
* daemon's answer to hq's `sweepUnlandedOutcomes` — the guarantee is DRIVEN by
|
|
555
|
+
* the close path rather than by the model remembering to write anything down.
|
|
556
|
+
*
|
|
557
|
+
* No-op when the flag is off, when the SOURCE IS NOT ORG-VISIBLE (a DM, a private
|
|
558
|
+
* channel, email, or a source we cannot establish as public — see
|
|
559
|
+
* `outcomeSourceShareable`), when the org integration is disabled, when nothing
|
|
560
|
+
* was committed, or when this session already landed. Never throws.
|
|
561
|
+
*
|
|
562
|
+
* @param {object} a
|
|
563
|
+
* @param {string} a.sessionId
|
|
564
|
+
* @param {string} [a.stdout] the session's raw stdout (--output-format json)
|
|
565
|
+
* @param {string} [a.resultText] pre-extracted final text (wins over stdout)
|
|
566
|
+
* @param {object} [a.item] the inbox item; REQUIRED — the fail-closed
|
|
567
|
+
* confidentiality gate lands nothing without a demonstrably public source
|
|
568
|
+
* @param {object} [a.classResult]
|
|
569
|
+
* @param {object} [a.cfg]
|
|
570
|
+
* @param {boolean} [a.enabled] override the env flag (tests)
|
|
571
|
+
* @param {string} [a.agentName]
|
|
572
|
+
* @param {Function} [a.createItemImpl]
|
|
573
|
+
* @param {Function} [a.rememberImpl]
|
|
574
|
+
* @param {Function} [a.fetchImpl]
|
|
575
|
+
* @returns {Promise<{created:number, captured?:number, skipped?:boolean, reason?:string}>}
|
|
576
|
+
*/
|
|
577
|
+
export async function sweepSessionOutcomes(a = {}) {
|
|
578
|
+
try {
|
|
579
|
+
const enabled = a.enabled !== undefined ? a.enabled : outcomesEnabled();
|
|
580
|
+
if (!enabled) return { created: 0, skipped: true, reason: "disabled" };
|
|
581
|
+
if (!a.sessionId) return { created: 0, skipped: true, reason: "no-session" };
|
|
582
|
+
|
|
583
|
+
// CONFIDENTIALITY: a landed outcome is org-visible, so only an already
|
|
584
|
+
// org-visible source may produce one. Checked BEFORE the text is even parsed
|
|
585
|
+
// — a private turn's commitments are never captured, let alone written.
|
|
586
|
+
const gate = outcomeSourceShareable(a.item);
|
|
587
|
+
if (!gate.shareable) {
|
|
588
|
+
clearSessionOutcomes(a.sessionId);
|
|
589
|
+
return { created: 0, captured: 0, skipped: true, reason: `source-${gate.reason}` };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (outcomesLanded(a.sessionId)) {
|
|
593
|
+
clearSessionOutcomes(a.sessionId);
|
|
594
|
+
return { created: 0, skipped: true, reason: "already-landed" };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const text =
|
|
598
|
+
typeof a.resultText === "string" && a.resultText
|
|
599
|
+
? a.resultText
|
|
600
|
+
: resultTextFromStdout(a.stdout);
|
|
601
|
+
const parsed = parseOutcomes(text);
|
|
602
|
+
const captured = noteOutcomes(a.sessionId, parsed);
|
|
603
|
+
if (captured.length === 0) {
|
|
604
|
+
clearSessionOutcomes(a.sessionId);
|
|
605
|
+
return { created: 0, captured: 0, skipped: true, reason: "nothing-captured" };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const res = await landOutcomes({
|
|
609
|
+
sessionId: a.sessionId,
|
|
610
|
+
outcomes: captured,
|
|
611
|
+
cfg: a.cfg,
|
|
612
|
+
item: a.item,
|
|
613
|
+
classResult: a.classResult,
|
|
614
|
+
agentName: a.agentName,
|
|
615
|
+
createItemImpl: a.createItemImpl,
|
|
616
|
+
rememberImpl: a.rememberImpl,
|
|
617
|
+
fetchImpl: a.fetchImpl,
|
|
618
|
+
});
|
|
619
|
+
clearSessionOutcomes(a.sessionId);
|
|
620
|
+
return { ...res, captured: captured.length };
|
|
621
|
+
} catch {
|
|
622
|
+
return { created: 0, skipped: true, reason: "error" };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export default {
|
|
627
|
+
MAX_OUTCOMES,
|
|
628
|
+
outcomesEnabled,
|
|
629
|
+
outcomeSourceShareable,
|
|
630
|
+
parseOutcomes,
|
|
631
|
+
resultTextFromStdout,
|
|
632
|
+
noteOutcomes,
|
|
633
|
+
getSessionOutcomes,
|
|
634
|
+
outcomesLanded,
|
|
635
|
+
markOutcomesLanded,
|
|
636
|
+
clearSessionOutcomes,
|
|
637
|
+
outcomeItemId,
|
|
638
|
+
landOutcomes,
|
|
639
|
+
sweepSessionOutcomes,
|
|
640
|
+
};
|