@adaptic/maestro 1.0.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 +99 -0
- package/.claude/commands/init-maestro.md +565 -0
- package/.claude/settings.json +114 -0
- package/.env.example +152 -0
- package/README.md +491 -0
- package/agents/board-prep/agent.md +80 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/capital-raising/agent.md +69 -0
- package/agents/ceo-briefing/agent.md +72 -0
- package/agents/communications/agent.md +69 -0
- package/agents/competitive-intelligence/agent.md +49 -0
- package/agents/corporate-development/agent.md +66 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/engineering-coordination/agent.md +51 -0
- package/agents/founder-voice/agent.md +72 -0
- package/agents/fund-ops/agent.md +52 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/hiring-org-design/agent.md +119 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/legal-structuring/agent.md +65 -0
- package/agents/market-research/agent.md +48 -0
- package/agents/partnerships/agent.md +59 -0
- package/agents/platform-architecture/agent.md +57 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/product-strategy/agent.md +50 -0
- package/agents/regulatory-dfsa/agent.md +96 -0
- package/agents/risk-register/agent.md +62 -0
- package/agents/rollup-target-sourcing/agent.md +59 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/sophie-chief-of-staff/agent.md +134 -0
- package/agents/strategic-planning/agent.md +54 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/bin/maestro.mjs +388 -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 +2222 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +560 -0
- package/docs/architecture/system-architecture.md +1273 -0
- package/docs/business-synthesis/ADAPTIC-GROUP-FINAL-OWNERSHIP-STRUCTURE.pdf +13667 -10
- package/docs/business-synthesis/adaptic-overview.md +296 -0
- package/docs/business-synthesis/executive-operating-model.md +261 -0
- package/docs/governance/action-approval-model.md +331 -0
- package/docs/governance/communications-policy.md +410 -0
- package/docs/governance/desktop-control-safety.md +499 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/operating-charter.md +87 -0
- package/docs/prompts/board-pack-cover-template.md +37 -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/runbooks/mac-mini-bootstrap.md +404 -0
- package/docs/runbooks/perpetual-operations.md +505 -0
- package/docs/runbooks/recovery-and-failover.md +588 -0
- package/docs/superpowers/plans/2026-04-02-phase0-operational-foundation.md +2550 -0
- package/docs/superpowers/plans/2026-04-03-phase1-executive-core.md +1085 -0
- package/docs/superpowers/plans/2026-04-03-phase2-people-product-commercial.md +739 -0
- package/docs/superpowers/plans/2026-04-05-information-barrier-implementation.md +926 -0
- package/docs/superpowers/plans/2026-04-06-session-context-dedup.md +1994 -0
- package/docs/superpowers/specs/2026-04-02-phase0-operational-foundation-design.md +842 -0
- package/docs/superpowers/specs/2026-04-03-phase1-executive-core-design.md +516 -0
- package/docs/superpowers/specs/2026-04-03-phase2-people-product-commercial-design.md +452 -0
- package/docs/superpowers/specs/2026-04-03-phase3-4-final-towers-design.md +129 -0
- package/docs/superpowers/specs/2026-04-05-information-barrier-design.md +678 -0
- package/docs/superpowers/specs/2026-04-05-reactive-daemon-design.md +237 -0
- package/docs/superpowers/specs/2026-04-06-session-context-dedup-design.md +369 -0
- package/docs/workflows/executive-cadence.md +218 -0
- package/ingest/README.md +87 -0
- package/mcp/README.md +51 -0
- package/package.json +48 -0
- package/plugins/maestro-skills/plugin.json +55 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -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/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -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/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +110 -0
- package/policies/information-barriers.yaml +119 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/adaptic-icon-dark.png +0 -0
- package/public/assets/adaptic-icon-dark.svg +4 -0
- package/public/assets/adaptic-icon-light.svg +4 -0
- package/public/assets/adaptic-logo-dark.svg +17 -0
- package/public/assets/adaptic-logo-light.svg +17 -0
- package/scaffold/CLAUDE.md +21 -0
- package/scaffold/config/agent.ts +69 -0
- package/scaffold/config/agent.ts.example +218 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/daily-evening-wrap.md +142 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -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/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +67 -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/__pycache__/disclosure_assessment.cpython-313.pyc +0 -0
- package/scripts/__pycache__/disclosure_boundaries.cpython-313.pyc +0 -0
- package/scripts/__pycache__/email_quote_thread.cpython-313.pyc +0 -0
- package/scripts/__pycache__/email_thread_dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/mehran-inbox-poller.cpython-313.pyc +0 -0
- package/scripts/__pycache__/outbound_dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/pre-draft-context.cpython-313.pyc +0 -0
- package/scripts/__pycache__/pre_draft_lookup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-as-mehran.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-threaded.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-with-attachment.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate_outbound.cpython-313.pyc +0 -0
- package/scripts/airtable-crm-populate.md +99 -0
- package/scripts/archive-email.sh +41 -0
- package/scripts/comms-monitor.sh +285 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +86 -0
- package/scripts/daemon/classifier.mjs +355 -0
- package/scripts/daemon/context-compiler.mjs +490 -0
- package/scripts/daemon/dispatcher.mjs +385 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/prompt-builder.mjs +426 -0
- package/scripts/daemon/responder.mjs +547 -0
- package/scripts/daemon/session-lock.mjs +520 -0
- package/scripts/daemon/sophie-daemon.mjs +521 -0
- package/scripts/daemon/test-context-compiler.mjs +238 -0
- package/scripts/daemon/test-integration.mjs +130 -0
- package/scripts/daemon/test-session-lock.mjs +215 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-mehran.html +52 -0
- package/scripts/email-signature.html +44 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +361 -0
- package/scripts/emergency-stop.sh +41 -0
- package/scripts/healthcheck.sh +110 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +17 -0
- package/scripts/hooks/pre-send-audit.sh +57 -0
- package/scripts/hooks/session-end-log.sh +27 -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 +1059 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/node_modules/.package-lock.json +50 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/CHANGELOG.md +1677 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/LICENSE +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/README.md +674 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js +226 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs +223 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/cli +53 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/migration-config.json +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts +225 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts +225 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js +536 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs +531 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts +49 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts +49 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js +76 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts +47 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts +47 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs +98 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts +63 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts +63 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js +123 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs +117 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js +282 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs +277 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts +73 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts +73 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs +12 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js +39 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js +113 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs +108 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js +162 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs +157 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js +41 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs +36 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js +79 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js +55 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs +51 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts +34 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts +34 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js +14 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.ts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs +85 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js +38 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js +96 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs +93 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.js +81 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.mjs +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js +146 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs +136 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js +38 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs +33 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs +26 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs +18 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts +37 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts +37 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js +86 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs +80 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js +58 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs +53 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js +19 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts +16 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts +16 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js +109 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js +553 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs +549 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js +553 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs +549 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/package.json +185 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts +61 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts +61 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +25 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts +151 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts +151 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js +122 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +118 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js +13 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts +343 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts +343 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js +204 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs +200 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts +1561 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts +1561 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js +86 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs +81 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js +60 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs +56 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts +183 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts +183 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js +23 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs +19 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts +304 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts +304 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js +153 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs +149 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts +1264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts +1264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts +59 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts +59 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs +41 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/parser.ts +264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/api-promise.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/client.ts +1070 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/api-promise.ts +101 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/error.ts +133 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/pagination.ts +201 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/resource.ts +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/streaming.ts +331 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/uploads.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/error.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/index.ts +23 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/builtin-types.ts +93 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/constants.ts +12 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/jsonl.ts +48 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/line.ts +135 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/detect-platform.ts +196 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/errors.ts +33 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/headers.ts +99 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/parse.ts +84 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/request-options.ts +39 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shim-types.d.ts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shims.ts +107 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/stream-utils.ts +32 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/to-file.ts +159 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/types.ts +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/uploads.ts +193 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/base64.ts +40 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/bytes.ts +32 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/env.ts +18 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/log.ts +127 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/path.ts +65 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/sleep.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/uuid.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/values.ts +102 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils.ts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/.keep +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/BetaMessageStream.ts +683 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/MessageStream.ts +684 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/pagination.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resource.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/beta.ts +380 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/files.ts +258 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/index.ts +148 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/batches.ts +502 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/index.ts +135 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/messages.ts +2249 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/models.ts +118 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/completions.ts +231 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/index.ts +121 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/batches.ts +396 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/index.ts +110 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/messages.ts +1783 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/models.ts +103 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/shared.ts +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/top-level.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources.ts +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/streaming.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/tsconfig.json +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/uploads.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/version.ts +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs.map +1 -0
- package/scripts/huddle/node_modules/dotenv/CHANGELOG.md +520 -0
- package/scripts/huddle/node_modules/dotenv/LICENSE +23 -0
- package/scripts/huddle/node_modules/dotenv/README-es.md +411 -0
- package/scripts/huddle/node_modules/dotenv/README.md +645 -0
- package/scripts/huddle/node_modules/dotenv/SECURITY.md +1 -0
- package/scripts/huddle/node_modules/dotenv/config.d.ts +1 -0
- package/scripts/huddle/node_modules/dotenv/config.js +9 -0
- package/scripts/huddle/node_modules/dotenv/lib/cli-options.js +17 -0
- package/scripts/huddle/node_modules/dotenv/lib/env-options.js +28 -0
- package/scripts/huddle/node_modules/dotenv/lib/main.d.ts +162 -0
- package/scripts/huddle/node_modules/dotenv/lib/main.js +386 -0
- package/scripts/huddle/node_modules/dotenv/package.json +62 -0
- package/scripts/huddle/node_modules/ws/LICENSE +20 -0
- package/scripts/huddle/node_modules/ws/README.md +548 -0
- package/scripts/huddle/node_modules/ws/browser.js +8 -0
- package/scripts/huddle/node_modules/ws/index.js +22 -0
- package/scripts/huddle/node_modules/ws/lib/buffer-util.js +131 -0
- package/scripts/huddle/node_modules/ws/lib/constants.js +19 -0
- package/scripts/huddle/node_modules/ws/lib/event-target.js +292 -0
- package/scripts/huddle/node_modules/ws/lib/extension.js +203 -0
- package/scripts/huddle/node_modules/ws/lib/limiter.js +55 -0
- package/scripts/huddle/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/scripts/huddle/node_modules/ws/lib/receiver.js +706 -0
- package/scripts/huddle/node_modules/ws/lib/sender.js +602 -0
- package/scripts/huddle/node_modules/ws/lib/stream.js +161 -0
- package/scripts/huddle/node_modules/ws/lib/subprotocol.js +62 -0
- package/scripts/huddle/node_modules/ws/lib/validation.js +152 -0
- package/scripts/huddle/node_modules/ws/lib/websocket-server.js +554 -0
- package/scripts/huddle/node_modules/ws/lib/websocket.js +1393 -0
- package/scripts/huddle/node_modules/ws/package.json +70 -0
- package/scripts/huddle/node_modules/ws/wrapper.mjs +21 -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/llm_email_dedup.py +434 -0
- package/scripts/local-triggers/install-all.sh +43 -0
- package/scripts/local-triggers/plists/ai.adaptic.slack-events-server.plist +45 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-backlog-executor.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-daemon.plist +32 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-inbox-processor.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-action-capture.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-prep.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-midday-sweep.plist +26 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-quarterly-self-assessment.plist +62 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-engineering-health.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-execution.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-hiring.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-priorities.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-strategic-memo.plist +28 -0
- package/scripts/local-triggers/run-trigger.sh +44 -0
- package/scripts/media-generation/README.md +103 -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/mehran-inbox-poller.py +437 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +476 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +486 -0
- package/scripts/pdf-generation/README.md +61 -0
- package/scripts/pdf-generation/build-document.mjs +230 -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 +33 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +156 -0
- package/scripts/poller/imap-client.mjs +286 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +267 -0
- package/scripts/poller/mehran-gmail-poller.mjs +98 -0
- package/scripts/poller/slack-poller.mjs +716 -0
- package/scripts/poller/trigger.mjs +47 -0
- package/scripts/poller/utils.mjs +253 -0
- package/scripts/poller-launchd/ai.adaptic.sophie-poller.plist +40 -0
- package/scripts/poller-launchd/ai.adaptic.sophie-whatsapp-handler.plist +39 -0
- package/scripts/poller-launchd/install.sh +38 -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-indexer.py +629 -0
- package/scripts/resume-operations.sh +40 -0
- package/scripts/search-mehran-inbox.py +181 -0
- package/scripts/self-optimization/compute-metrics.py +377 -0
- package/scripts/send-difc-rfp.sh +98 -0
- package/scripts/send-email-as-mehran.py +369 -0
- package/scripts/send-email-threaded.py +336 -0
- package/scripts/send-email-with-attachment.py +360 -0
- package/scripts/send-email.sh +93 -0
- package/scripts/send-sms.sh +151 -0
- package/scripts/send-whatsapp.sh +261 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/configure-macos.sh +508 -0
- package/scripts/setup/init-agent.sh +450 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +989 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +185 -0
- package/scripts/slack-send.sh +190 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +436 -0
- package/scripts/sophie-inbox-poller.py +406 -0
- package/scripts/spawn-session.sh +85 -0
- package/scripts/system-verify.sh +171 -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 +660 -0
- package/scripts/validate-outbound.py +1493 -0
- package/scripts/whatsapp-handler.mjs +525 -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/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 +65 -0
- package/workflows/daily/morning-brief.yaml +147 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -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/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,1994 @@
|
|
|
1
|
+
# Session Context & Dedup Overhaul — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Eliminate duplicate responses and inject pre-compiled conversational context into every daemon-spawned session, with information barrier filtering.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Two new modules (`session-lock.mjs` for file-based dedup/locking, `context-compiler.mjs` for pre-compiling context), plus integration changes to the four existing daemon modules. Feature-flagged via `DAEMON_CONTEXT_COMPILER=1`.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js ESM, `node:test` + `node:assert` for tests, file-based persistence (JSON/JSONL), YAML parsing via string matching (no new deps).
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-04-06-session-context-dedup-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
### New Files
|
|
18
|
+
|
|
19
|
+
| File | Responsibility |
|
|
20
|
+
|------|---------------|
|
|
21
|
+
| `scripts/daemon/session-lock.mjs` | File-based item locks + sent-message registry. Exports: `acquireLock`, `releaseLock`, `updateLock`, `readLock`, `scanStaleLocks`, `checkRecentlySent`, `registerSent`, `sanitiseItemId` |
|
|
22
|
+
| `scripts/daemon/context-compiler.mjs` | Pre-compiles sender profile, disclosure boundaries, conversation history, active sessions, and sent-messages into a single context block. Exports: `compileContext` |
|
|
23
|
+
| `scripts/daemon/test-session-lock.mjs` | Unit tests for session-lock module |
|
|
24
|
+
| `scripts/daemon/test-context-compiler.mjs` | Unit tests for context-compiler module |
|
|
25
|
+
| `scripts/daemon/test-integration.mjs` | Integration tests for the full pipeline |
|
|
26
|
+
| `state/sessions/active.json` | Live manifest of currently running sessions |
|
|
27
|
+
| `state/sessions/locks/` | Directory for per-item lock files |
|
|
28
|
+
| `state/sessions/sent-registry.jsonl` | Append-only log of all sent messages |
|
|
29
|
+
|
|
30
|
+
### Modified Files
|
|
31
|
+
|
|
32
|
+
| File | Change Summary |
|
|
33
|
+
|------|---------------|
|
|
34
|
+
| `scripts/daemon/sophie-daemon.mjs` | Remove in-memory dedup, use `acquireLock`/`releaseLock`, call `scanStaleLocks` on startup |
|
|
35
|
+
| `scripts/daemon/dispatcher.mjs` | Write/remove `active.json` entries, call `releaseLock` on session close, stabilise `backlogKey` |
|
|
36
|
+
| `scripts/daemon/prompt-builder.mjs` | Replace `loadConversationHistory` and profile-read instruction with `compileContext` output, feature flag |
|
|
37
|
+
| `scripts/daemon/responder.mjs` | Call `checkRecentlySent` before sending, `registerSent` after sending |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Task 1: Create State Directory Structure
|
|
42
|
+
|
|
43
|
+
**Files:**
|
|
44
|
+
- Create: `state/sessions/active.json`
|
|
45
|
+
- Create: `state/sessions/locks/.gitkeep`
|
|
46
|
+
- Create: `state/sessions/sent-registry.jsonl`
|
|
47
|
+
|
|
48
|
+
- [ ] **Step 1: Create the directories and seed files**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mkdir -p state/sessions/locks
|
|
52
|
+
echo '{}' > state/sessions/active.json
|
|
53
|
+
touch state/sessions/sent-registry.jsonl
|
|
54
|
+
touch state/sessions/locks/.gitkeep
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- [ ] **Step 2: Add to .gitignore (lock files and registry are ephemeral)**
|
|
58
|
+
|
|
59
|
+
Add to the project `.gitignore`:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
# Session state (ephemeral — regenerated at runtime)
|
|
63
|
+
state/sessions/locks/*.lock
|
|
64
|
+
state/sessions/sent-registry.jsonl
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Keep `active.json` tracked (seed file) and `locks/.gitkeep` tracked (preserves directory).
|
|
68
|
+
|
|
69
|
+
- [ ] **Step 3: Commit**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git add state/sessions/active.json state/sessions/locks/.gitkeep .gitignore
|
|
73
|
+
git commit -m "chore: create state/sessions directory structure for session locks and context"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Task 2: Implement Session Lock Module
|
|
79
|
+
|
|
80
|
+
**Files:**
|
|
81
|
+
- Create: `scripts/daemon/session-lock.mjs`
|
|
82
|
+
- Test: `scripts/daemon/test-session-lock.mjs`
|
|
83
|
+
|
|
84
|
+
- [ ] **Step 1: Write failing tests for `sanitiseItemId`**
|
|
85
|
+
|
|
86
|
+
Create `scripts/daemon/test-session-lock.mjs`:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
90
|
+
import assert from "node:assert/strict";
|
|
91
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
92
|
+
import { join } from "path";
|
|
93
|
+
|
|
94
|
+
// Tests use a temporary directory to avoid polluting real state
|
|
95
|
+
const TEST_DIR = join(import.meta.dirname, "../../.test-session-locks");
|
|
96
|
+
const LOCKS_DIR = join(TEST_DIR, "locks");
|
|
97
|
+
const REGISTRY_PATH = join(TEST_DIR, "sent-registry.jsonl");
|
|
98
|
+
|
|
99
|
+
// We'll set these env vars so the module uses our test paths
|
|
100
|
+
process.env.__TEST_SESSION_DIR = TEST_DIR;
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
sanitiseItemId,
|
|
104
|
+
acquireLock,
|
|
105
|
+
releaseLock,
|
|
106
|
+
updateLock,
|
|
107
|
+
readLock,
|
|
108
|
+
scanStaleLocks,
|
|
109
|
+
checkRecentlySent,
|
|
110
|
+
registerSent,
|
|
111
|
+
} = await import("./session-lock.mjs");
|
|
112
|
+
|
|
113
|
+
describe("sanitiseItemId", () => {
|
|
114
|
+
it("replaces colons and dots with hyphens", () => {
|
|
115
|
+
assert.equal(
|
|
116
|
+
sanitiseItemId("slack:D099N1JGKRQ:1775331885.690669"),
|
|
117
|
+
"slack-D099N1JGKRQ-1775331885-690669"
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("handles simple alphanumeric IDs unchanged", () => {
|
|
122
|
+
assert.equal(sanitiseItemId("abc123"), "abc123");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("replaces spaces and special chars", () => {
|
|
126
|
+
assert.equal(sanitiseItemId("foo bar/baz@qux"), "foo-bar-baz-qux");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Expected: FAIL — `session-lock.mjs` does not exist.
|
|
138
|
+
|
|
139
|
+
- [ ] **Step 3: Write the session-lock module skeleton with `sanitiseItemId`**
|
|
140
|
+
|
|
141
|
+
Create `scripts/daemon/session-lock.mjs`:
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
#!/usr/bin/env node
|
|
145
|
+
/**
|
|
146
|
+
* session-lock.mjs — File-based session locks and sent-message registry
|
|
147
|
+
*
|
|
148
|
+
* Replaces the in-memory recentlyProcessed Map in sophie-daemon.mjs with
|
|
149
|
+
* persistent, atomic file locks that survive daemon restarts and prevent
|
|
150
|
+
* race conditions between concurrent polls.
|
|
151
|
+
*
|
|
152
|
+
* Also provides a sent-message registry to prevent duplicate sends even
|
|
153
|
+
* when locking fails (belt + suspenders).
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
import {
|
|
157
|
+
writeFileSync,
|
|
158
|
+
readFileSync,
|
|
159
|
+
unlinkSync,
|
|
160
|
+
readdirSync,
|
|
161
|
+
appendFileSync,
|
|
162
|
+
mkdirSync,
|
|
163
|
+
existsSync,
|
|
164
|
+
statSync,
|
|
165
|
+
} from "fs";
|
|
166
|
+
import { join } from "path";
|
|
167
|
+
|
|
168
|
+
// Allow test override of state directory
|
|
169
|
+
const SOPHIE_AI_DIR = join(new URL(".", import.meta.url).pathname, "../..");
|
|
170
|
+
const SESSION_DIR = process.env.__TEST_SESSION_DIR || join(SOPHIE_AI_DIR, "state", "sessions");
|
|
171
|
+
const LOCKS_DIR = join(SESSION_DIR, "locks");
|
|
172
|
+
const REGISTRY_PATH = join(SESSION_DIR, "sent-registry.jsonl");
|
|
173
|
+
|
|
174
|
+
const STALE_LOCK_MS = 15 * 60 * 1000; // 15 min — matches OPUS_TIMEOUT
|
|
175
|
+
const SENT_DEDUP_WINDOW_MS = 5 * 60 * 1000; // 5 min
|
|
176
|
+
const SENT_SUBSTANTIVE_WINDOW_MS = 2 * 60 * 1000; // 2 min for same-person substantive
|
|
177
|
+
const MAX_REGISTRY_ENTRIES = 500; // Keep registry file manageable
|
|
178
|
+
|
|
179
|
+
// Ensure directories exist
|
|
180
|
+
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Sanitise an item ID for use as a filename.
|
|
184
|
+
* Replaces all non-alphanumeric characters with hyphens.
|
|
185
|
+
*/
|
|
186
|
+
export function sanitiseItemId(itemId) {
|
|
187
|
+
return itemId.replace(/[^a-zA-Z0-9]/g, "-");
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- [ ] **Step 4: Run test to verify `sanitiseItemId` passes**
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Expected: 3 tests pass.
|
|
198
|
+
|
|
199
|
+
- [ ] **Step 5: Write failing tests for `acquireLock` / `releaseLock` / `readLock`**
|
|
200
|
+
|
|
201
|
+
Append to `scripts/daemon/test-session-lock.mjs`:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
describe("acquireLock / releaseLock / readLock", () => {
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
afterEach(() => {
|
|
210
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("acquires a lock on first call", () => {
|
|
214
|
+
const result = acquireLock("test-item-1", { sender: "Alice", channel: "C001" });
|
|
215
|
+
assert.equal(result.acquired, true);
|
|
216
|
+
assert.equal(existsSync(join(LOCKS_DIR, "test-item-1.lock")), true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("rejects second acquire for same item", () => {
|
|
220
|
+
acquireLock("test-item-2", { sender: "Alice", channel: "C001" });
|
|
221
|
+
const result = acquireLock("test-item-2", { sender: "Bob", channel: "C002" });
|
|
222
|
+
assert.equal(result.acquired, false);
|
|
223
|
+
assert.ok(result.existing);
|
|
224
|
+
assert.equal(result.existing.sender, "Alice");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("allows acquire after release", () => {
|
|
228
|
+
acquireLock("test-item-3", { sender: "Alice", channel: "C001" });
|
|
229
|
+
releaseLock("test-item-3");
|
|
230
|
+
const result = acquireLock("test-item-3", { sender: "Bob", channel: "C002" });
|
|
231
|
+
assert.equal(result.acquired, true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("readLock returns lock data when locked", () => {
|
|
235
|
+
acquireLock("test-item-4", { sender: "Alice", channel: "C001" });
|
|
236
|
+
const data = readLock("test-item-4");
|
|
237
|
+
assert.equal(data.sender, "Alice");
|
|
238
|
+
assert.equal(data.channel, "C001");
|
|
239
|
+
assert.equal(data.holdingSent, false);
|
|
240
|
+
assert.equal(data.substantiveSent, false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("readLock returns null when no lock", () => {
|
|
244
|
+
const data = readLock("nonexistent-item");
|
|
245
|
+
assert.equal(data, null);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("overwrites stale lock (>15 min old)", () => {
|
|
249
|
+
// Write a lock with an old timestamp
|
|
250
|
+
const lockPath = join(LOCKS_DIR, "test-item-5.lock");
|
|
251
|
+
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
252
|
+
const staleLock = {
|
|
253
|
+
sessionId: "old-session",
|
|
254
|
+
itemId: "test-item-5",
|
|
255
|
+
sender: "Alice",
|
|
256
|
+
channel: "C001",
|
|
257
|
+
acquiredAt: new Date(Date.now() - 20 * 60 * 1000).toISOString(),
|
|
258
|
+
holdingSent: false,
|
|
259
|
+
substantiveSent: false,
|
|
260
|
+
};
|
|
261
|
+
writeFileSync(lockPath, JSON.stringify(staleLock));
|
|
262
|
+
|
|
263
|
+
const result = acquireLock("test-item-5", { sender: "Bob", channel: "C002" });
|
|
264
|
+
assert.equal(result.acquired, true);
|
|
265
|
+
const data = readLock("test-item-5");
|
|
266
|
+
assert.equal(data.sender, "Bob");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
- [ ] **Step 6: Run test to verify new tests fail**
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Expected: `sanitiseItemId` tests pass, lock tests fail (`acquireLock` not exported).
|
|
278
|
+
|
|
279
|
+
- [ ] **Step 7: Implement `acquireLock`, `releaseLock`, `readLock`**
|
|
280
|
+
|
|
281
|
+
Append to `scripts/daemon/session-lock.mjs`:
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
/**
|
|
285
|
+
* Attempt to acquire a lock for an item.
|
|
286
|
+
* Uses exclusive file creation (wx flag) for atomicity.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} itemId - Raw item ID (will be sanitised)
|
|
289
|
+
* @param {object} metadata - { sender, channel, sessionId? }
|
|
290
|
+
* @returns {{ acquired: boolean, existing?: object }}
|
|
291
|
+
*/
|
|
292
|
+
export function acquireLock(itemId, metadata = {}) {
|
|
293
|
+
const safeId = sanitiseItemId(itemId);
|
|
294
|
+
const lockPath = join(LOCKS_DIR, `${safeId}.lock`);
|
|
295
|
+
|
|
296
|
+
// Check for existing lock
|
|
297
|
+
if (existsSync(lockPath)) {
|
|
298
|
+
try {
|
|
299
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
300
|
+
const age = Date.now() - new Date(existing.acquiredAt).getTime();
|
|
301
|
+
|
|
302
|
+
// If lock is fresh (< STALE_LOCK_MS), reject
|
|
303
|
+
if (age < STALE_LOCK_MS) {
|
|
304
|
+
return { acquired: false, existing };
|
|
305
|
+
}
|
|
306
|
+
// Stale lock — fall through to overwrite
|
|
307
|
+
} catch {
|
|
308
|
+
// Corrupted lock file — overwrite
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lockData = {
|
|
313
|
+
sessionId: metadata.sessionId || `pending-${Date.now()}`,
|
|
314
|
+
itemId,
|
|
315
|
+
channel: metadata.channel || null,
|
|
316
|
+
sender: metadata.sender || null,
|
|
317
|
+
acquiredAt: new Date().toISOString(),
|
|
318
|
+
holdingSent: false,
|
|
319
|
+
substantiveSent: false,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
// Try exclusive create first (atomic)
|
|
324
|
+
writeFileSync(lockPath, JSON.stringify(lockData, null, 2), { flag: "wx" });
|
|
325
|
+
return { acquired: true };
|
|
326
|
+
} catch (err) {
|
|
327
|
+
if (err.code === "EEXIST") {
|
|
328
|
+
// Another process beat us — check if it's stale
|
|
329
|
+
try {
|
|
330
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
331
|
+
const age = Date.now() - new Date(existing.acquiredAt).getTime();
|
|
332
|
+
if (age >= STALE_LOCK_MS) {
|
|
333
|
+
// Stale — overwrite (non-atomic but acceptable for stale locks)
|
|
334
|
+
writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
335
|
+
return { acquired: true };
|
|
336
|
+
}
|
|
337
|
+
return { acquired: false, existing };
|
|
338
|
+
} catch {
|
|
339
|
+
return { acquired: false };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Release a lock for an item.
|
|
348
|
+
* @param {string} itemId - Raw item ID (will be sanitised)
|
|
349
|
+
*/
|
|
350
|
+
export function releaseLock(itemId) {
|
|
351
|
+
const safeId = sanitiseItemId(itemId);
|
|
352
|
+
const lockPath = join(LOCKS_DIR, `${safeId}.lock`);
|
|
353
|
+
try {
|
|
354
|
+
unlinkSync(lockPath);
|
|
355
|
+
} catch {
|
|
356
|
+
// Lock already released or never existed — fine
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Read the lock data for an item without modifying it.
|
|
362
|
+
* @param {string} itemId - Raw item ID (will be sanitised)
|
|
363
|
+
* @returns {object|null} Lock data or null if no lock
|
|
364
|
+
*/
|
|
365
|
+
export function readLock(itemId) {
|
|
366
|
+
const safeId = sanitiseItemId(itemId);
|
|
367
|
+
const lockPath = join(LOCKS_DIR, `${safeId}.lock`);
|
|
368
|
+
try {
|
|
369
|
+
return JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
370
|
+
} catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
- [ ] **Step 8: Run tests to verify lock tests pass**
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Expected: All tests pass.
|
|
383
|
+
|
|
384
|
+
- [ ] **Step 9: Write failing tests for `updateLock` and `scanStaleLocks`**
|
|
385
|
+
|
|
386
|
+
Append to `scripts/daemon/test-session-lock.mjs`:
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
describe("updateLock", () => {
|
|
390
|
+
beforeEach(() => {
|
|
391
|
+
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
afterEach(() => {
|
|
395
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("updates holdingSent flag on existing lock", () => {
|
|
399
|
+
acquireLock("test-item-6", { sender: "Alice", channel: "C001" });
|
|
400
|
+
updateLock("test-item-6", { holdingSent: true });
|
|
401
|
+
const data = readLock("test-item-6");
|
|
402
|
+
assert.equal(data.holdingSent, true);
|
|
403
|
+
assert.equal(data.substantiveSent, false);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("no-ops when lock does not exist", () => {
|
|
407
|
+
// Should not throw
|
|
408
|
+
updateLock("nonexistent", { holdingSent: true });
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe("scanStaleLocks", () => {
|
|
413
|
+
beforeEach(() => {
|
|
414
|
+
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
afterEach(() => {
|
|
418
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("clears stale locks and leaves fresh ones", () => {
|
|
422
|
+
// Fresh lock
|
|
423
|
+
acquireLock("fresh-item", { sender: "Alice", channel: "C001" });
|
|
424
|
+
|
|
425
|
+
// Stale lock (manually written with old timestamp)
|
|
426
|
+
const stalePath = join(LOCKS_DIR, "stale-item.lock");
|
|
427
|
+
writeFileSync(stalePath, JSON.stringify({
|
|
428
|
+
sessionId: "old", itemId: "stale-item", sender: "Bob", channel: "C002",
|
|
429
|
+
acquiredAt: new Date(Date.now() - 20 * 60 * 1000).toISOString(),
|
|
430
|
+
holdingSent: false, substantiveSent: false,
|
|
431
|
+
}));
|
|
432
|
+
|
|
433
|
+
const cleared = scanStaleLocks();
|
|
434
|
+
assert.equal(cleared, 1);
|
|
435
|
+
assert.equal(existsSync(join(LOCKS_DIR, "fresh-item.lock")), true);
|
|
436
|
+
assert.equal(existsSync(stalePath), false);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
- [ ] **Step 10: Implement `updateLock` and `scanStaleLocks`**
|
|
442
|
+
|
|
443
|
+
Append to `scripts/daemon/session-lock.mjs`:
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
/**
|
|
447
|
+
* Update fields on an existing lock.
|
|
448
|
+
* @param {string} itemId - Raw item ID
|
|
449
|
+
* @param {object} updates - Fields to merge (e.g., { holdingSent: true })
|
|
450
|
+
*/
|
|
451
|
+
export function updateLock(itemId, updates) {
|
|
452
|
+
const safeId = sanitiseItemId(itemId);
|
|
453
|
+
const lockPath = join(LOCKS_DIR, `${safeId}.lock`);
|
|
454
|
+
try {
|
|
455
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
456
|
+
const updated = { ...existing, ...updates };
|
|
457
|
+
writeFileSync(lockPath, JSON.stringify(updated, null, 2));
|
|
458
|
+
} catch {
|
|
459
|
+
// Lock doesn't exist — no-op
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Scan and remove stale locks (older than STALE_LOCK_MS).
|
|
465
|
+
* Call on daemon startup to clean up after crashes.
|
|
466
|
+
* @returns {number} Number of stale locks cleared
|
|
467
|
+
*/
|
|
468
|
+
export function scanStaleLocks() {
|
|
469
|
+
let cleared = 0;
|
|
470
|
+
try {
|
|
471
|
+
const files = readdirSync(LOCKS_DIR).filter((f) => f.endsWith(".lock"));
|
|
472
|
+
for (const file of files) {
|
|
473
|
+
const lockPath = join(LOCKS_DIR, file);
|
|
474
|
+
try {
|
|
475
|
+
const data = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
476
|
+
const age = Date.now() - new Date(data.acquiredAt).getTime();
|
|
477
|
+
if (age >= STALE_LOCK_MS) {
|
|
478
|
+
unlinkSync(lockPath);
|
|
479
|
+
cleared++;
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
// Corrupted lock — remove it
|
|
483
|
+
try { unlinkSync(lockPath); cleared++; } catch {}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
// LOCKS_DIR doesn't exist yet — nothing to scan
|
|
488
|
+
}
|
|
489
|
+
return cleared;
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
- [ ] **Step 11: Run tests to verify all pass**
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Expected: All tests pass.
|
|
500
|
+
|
|
501
|
+
- [ ] **Step 12: Write failing tests for sent-message registry**
|
|
502
|
+
|
|
503
|
+
Append to `scripts/daemon/test-session-lock.mjs`:
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
describe("sent-message registry", () => {
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
509
|
+
// Ensure clean registry
|
|
510
|
+
try { unlinkSync(REGISTRY_PATH); } catch {}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
afterEach(() => {
|
|
514
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("allows first send to a channel", () => {
|
|
518
|
+
const result = checkRecentlySent("C001", "1234.5678", "holding");
|
|
519
|
+
assert.equal(result.allowed, true);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("blocks duplicate send to same channel+thread+type within window", () => {
|
|
523
|
+
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
524
|
+
const result = checkRecentlySent("C001", "1234.5678", "holding");
|
|
525
|
+
assert.equal(result.allowed, false);
|
|
526
|
+
assert.ok(result.reason.includes("duplicate"));
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("allows different type to same channel+thread", () => {
|
|
530
|
+
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
531
|
+
const result = checkRecentlySent("C001", "1234.5678", "substantive");
|
|
532
|
+
assert.equal(result.allowed, true);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("allows same type to different channel", () => {
|
|
536
|
+
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
537
|
+
const result = checkRecentlySent("C002", "1234.5678", "holding");
|
|
538
|
+
assert.equal(result.allowed, true);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("blocks rapid substantive sends to same channel even with different threads", () => {
|
|
542
|
+
registerSent("C001", "1111.0000", "substantive", "s-1", "Here is the answer");
|
|
543
|
+
const result = checkRecentlySent("C001", "2222.0000", "substantive");
|
|
544
|
+
assert.equal(result.allowed, false);
|
|
545
|
+
assert.ok(result.reason.includes("rapid"));
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
- [ ] **Step 13: Implement `registerSent` and `checkRecentlySent`**
|
|
551
|
+
|
|
552
|
+
Append to `scripts/daemon/session-lock.mjs`:
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
/**
|
|
556
|
+
* Register that a message was sent.
|
|
557
|
+
* @param {string} channel - Slack channel ID or email address
|
|
558
|
+
* @param {string|null} threadTs - Thread timestamp (null for non-threaded)
|
|
559
|
+
* @param {string} type - "holding" | "quick_reply" | "substantive"
|
|
560
|
+
* @param {string} sessionId - Session that sent it
|
|
561
|
+
* @param {string} textPreview - First ~100 chars of the message
|
|
562
|
+
*/
|
|
563
|
+
export function registerSent(channel, threadTs, type, sessionId, textPreview) {
|
|
564
|
+
const entry = {
|
|
565
|
+
timestamp: new Date().toISOString(),
|
|
566
|
+
channel,
|
|
567
|
+
threadTs: threadTs || null,
|
|
568
|
+
type,
|
|
569
|
+
sessionId,
|
|
570
|
+
textPreview: (textPreview || "").substring(0, 100),
|
|
571
|
+
};
|
|
572
|
+
try {
|
|
573
|
+
appendFileSync(REGISTRY_PATH, JSON.stringify(entry) + "\n");
|
|
574
|
+
} catch {
|
|
575
|
+
// Registry dir might not exist yet in tests
|
|
576
|
+
mkdirSync(SESSION_DIR, { recursive: true });
|
|
577
|
+
appendFileSync(REGISTRY_PATH, JSON.stringify(entry) + "\n");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Check if a similar message was recently sent to the same channel/thread.
|
|
583
|
+
* @param {string} channel
|
|
584
|
+
* @param {string|null} threadTs
|
|
585
|
+
* @param {string} type - "holding" | "quick_reply" | "substantive"
|
|
586
|
+
* @returns {{ allowed: boolean, reason?: string }}
|
|
587
|
+
*/
|
|
588
|
+
export function checkRecentlySent(channel, threadTs, type) {
|
|
589
|
+
let entries = [];
|
|
590
|
+
try {
|
|
591
|
+
const raw = readFileSync(REGISTRY_PATH, "utf-8").trim();
|
|
592
|
+
if (!raw) return { allowed: true };
|
|
593
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
594
|
+
// Only check recent entries (last MAX_REGISTRY_ENTRIES lines)
|
|
595
|
+
const recent = lines.slice(-MAX_REGISTRY_ENTRIES);
|
|
596
|
+
entries = recent.map((line) => {
|
|
597
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
598
|
+
}).filter(Boolean);
|
|
599
|
+
} catch {
|
|
600
|
+
// No registry file yet — nothing sent
|
|
601
|
+
return { allowed: true };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const now = Date.now();
|
|
605
|
+
|
|
606
|
+
// Rule 1: Same channel + thread + type within 5 min → BLOCK
|
|
607
|
+
const exactMatch = entries.find((e) => {
|
|
608
|
+
const age = now - new Date(e.timestamp).getTime();
|
|
609
|
+
return e.channel === channel
|
|
610
|
+
&& e.threadTs === (threadTs || null)
|
|
611
|
+
&& e.type === type
|
|
612
|
+
&& age < SENT_DEDUP_WINDOW_MS;
|
|
613
|
+
});
|
|
614
|
+
if (exactMatch) {
|
|
615
|
+
return { allowed: false, reason: `duplicate: same channel+thread+type sent ${Math.round((now - new Date(exactMatch.timestamp).getTime()) / 1000)}s ago` };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Rule 2: Same channel + substantive within 2 min (any thread) → BLOCK
|
|
619
|
+
if (type === "substantive") {
|
|
620
|
+
const rapidMatch = entries.find((e) => {
|
|
621
|
+
const age = now - new Date(e.timestamp).getTime();
|
|
622
|
+
return e.channel === channel
|
|
623
|
+
&& e.type === "substantive"
|
|
624
|
+
&& age < SENT_SUBSTANTIVE_WINDOW_MS;
|
|
625
|
+
});
|
|
626
|
+
if (rapidMatch) {
|
|
627
|
+
return { allowed: false, reason: `rapid: substantive message to same channel sent ${Math.round((now - new Date(rapidMatch.timestamp).getTime()) / 1000)}s ago` };
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return { allowed: true };
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
- [ ] **Step 14: Run all session-lock tests**
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
node --test scripts/daemon/test-session-lock.mjs
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Expected: All tests pass (sanitiseItemId + lock lifecycle + registry).
|
|
642
|
+
|
|
643
|
+
- [ ] **Step 15: Commit**
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
git add scripts/daemon/session-lock.mjs scripts/daemon/test-session-lock.mjs
|
|
647
|
+
git commit -m "feat: file-based session locks and sent-message registry
|
|
648
|
+
|
|
649
|
+
Replaces in-memory dedup Map with persistent file locks using atomic
|
|
650
|
+
exclusive-create (wx flag). Adds sent-message registry to prevent
|
|
651
|
+
duplicate sends even when locking fails. All tests passing."
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## Task 3: Implement Context Compiler
|
|
657
|
+
|
|
658
|
+
**Files:**
|
|
659
|
+
- Create: `scripts/daemon/context-compiler.mjs`
|
|
660
|
+
- Test: `scripts/daemon/test-context-compiler.mjs`
|
|
661
|
+
|
|
662
|
+
- [ ] **Step 1: Write failing tests for token budget determination**
|
|
663
|
+
|
|
664
|
+
Create `scripts/daemon/test-context-compiler.mjs`:
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
668
|
+
import assert from "node:assert/strict";
|
|
669
|
+
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
670
|
+
import { join } from "path";
|
|
671
|
+
|
|
672
|
+
const TEST_DIR = join(import.meta.dirname, "../../.test-context-compiler");
|
|
673
|
+
process.env.__TEST_SOPHIE_DIR = TEST_DIR;
|
|
674
|
+
|
|
675
|
+
// Create minimal directory structure needed
|
|
676
|
+
function setupTestDir() {
|
|
677
|
+
mkdirSync(join(TEST_DIR, "memory", "profiles", "users"), { recursive: true });
|
|
678
|
+
mkdirSync(join(TEST_DIR, "memory", "interactions", "slack", "dm-alice-smith"), { recursive: true });
|
|
679
|
+
mkdirSync(join(TEST_DIR, "memory", "interactions", "slack", "D001TESTCH"), { recursive: true });
|
|
680
|
+
mkdirSync(join(TEST_DIR, "policies"), { recursive: true });
|
|
681
|
+
mkdirSync(join(TEST_DIR, "state", "sessions"), { recursive: true });
|
|
682
|
+
writeFileSync(join(TEST_DIR, "state", "sessions", "active.json"), "{}");
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function cleanTestDir() {
|
|
686
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Write a minimal information-barriers.yaml for tests
|
|
690
|
+
function writeBarriersPolicy() {
|
|
691
|
+
writeFileSync(join(TEST_DIR, "policies", "information-barriers.yaml"), `domains:
|
|
692
|
+
adaptic-internal-legal:
|
|
693
|
+
description: "Adaptic's own legal strategy, GC advice, privilege-protected"
|
|
694
|
+
default_sensitivity: high
|
|
695
|
+
rollup-strategy:
|
|
696
|
+
description: "Project Aether M&A targets, deal terms, pipeline"
|
|
697
|
+
default_sensitivity: critical
|
|
698
|
+
ftlab-operations:
|
|
699
|
+
description: "FTLab JV operational matters"
|
|
700
|
+
default_sensitivity: low
|
|
701
|
+
public:
|
|
702
|
+
description: "Published information, press, website — no restrictions"
|
|
703
|
+
default_sensitivity: none
|
|
704
|
+
`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
describe("compileContext", () => {
|
|
708
|
+
beforeEach(() => {
|
|
709
|
+
setupTestDir();
|
|
710
|
+
writeBarriersPolicy();
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
afterEach(() => {
|
|
714
|
+
cleanTestDir();
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("returns adaptive token budget based on priority", async () => {
|
|
718
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
719
|
+
|
|
720
|
+
const criticalResult = await compileContext(
|
|
721
|
+
{ sender: "Unknown", channel: "C001" },
|
|
722
|
+
{ priority: "critical", model: "opus", action: "respond" },
|
|
723
|
+
{}
|
|
724
|
+
);
|
|
725
|
+
assert.ok(criticalResult.tokenBudget >= 12000);
|
|
726
|
+
|
|
727
|
+
const normalResult = await compileContext(
|
|
728
|
+
{ sender: "Unknown", channel: "C001" },
|
|
729
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
730
|
+
{}
|
|
731
|
+
);
|
|
732
|
+
assert.ok(normalResult.tokenBudget >= 8000 && normalResult.tokenBudget < 12000);
|
|
733
|
+
|
|
734
|
+
const lowResult = await compileContext(
|
|
735
|
+
{ sender: "Unknown", channel: "C001" },
|
|
736
|
+
{ priority: "low", model: "sonnet", action: "respond" },
|
|
737
|
+
{}
|
|
738
|
+
);
|
|
739
|
+
assert.ok(lowResult.tokenBudget >= 4000 && lowResult.tokenBudget < 8000);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("includes sender profile when available", async () => {
|
|
743
|
+
writeFileSync(join(TEST_DIR, "memory", "profiles", "users", "alice-smith.yaml"),
|
|
744
|
+
`name: Alice Smith\nrole: Partner\nprivilege_level: leadership\ncommunication_preferences:\n voice: formal\n`
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
748
|
+
const result = await compileContext(
|
|
749
|
+
{ sender: "Alice Smith", channel: "C001" },
|
|
750
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
751
|
+
{}
|
|
752
|
+
);
|
|
753
|
+
assert.ok(result.contextBlock.includes("Alice Smith"));
|
|
754
|
+
assert.ok(result.contextBlock.includes("Sender Profile"));
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it("produces minimal block when no profile exists", async () => {
|
|
758
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
759
|
+
const result = await compileContext(
|
|
760
|
+
{ sender: "Nobody Special", channel: "C001" },
|
|
761
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
762
|
+
{}
|
|
763
|
+
);
|
|
764
|
+
assert.ok(result.contextBlock.includes("COMPILED CONTEXT"));
|
|
765
|
+
// Should not crash, should still have section headers
|
|
766
|
+
assert.ok(result.contextBlock.includes("END CONTEXT"));
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it("filters history by disclosure boundaries", async () => {
|
|
770
|
+
// Profile with excluded domains
|
|
771
|
+
writeFileSync(join(TEST_DIR, "memory", "profiles", "users", "alice-smith.yaml"),
|
|
772
|
+
`name: Alice Smith\nrole: Partner\ninformation_boundaries:\n excluded_domains:\n - rollup-strategy\n - adaptic-internal-legal\n`
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
// History with mixed domain content
|
|
776
|
+
const historyDir = join(TEST_DIR, "memory", "interactions", "slack", "dm-alice-smith");
|
|
777
|
+
const today = new Date().toISOString().split("T")[0];
|
|
778
|
+
writeFileSync(join(historyDir, `${today}.jsonl`), [
|
|
779
|
+
JSON.stringify({ ts: "1000", from: "alice-smith", content: "How is the FTLab project going?", received_at: "2026-04-06T10:00:00Z" }),
|
|
780
|
+
JSON.stringify({ ts: "1001", from: "sophie", content: "Project Aether acquisition target list has 9 candidates", received_at: "2026-04-06T10:01:00Z" }),
|
|
781
|
+
JSON.stringify({ ts: "1002", from: "sophie", content: "The FTLab operations are on track", received_at: "2026-04-06T10:02:00Z" }),
|
|
782
|
+
JSON.stringify({ ts: "1003", from: "sophie", content: "GC advice on the legal strategy is privileged", received_at: "2026-04-06T10:03:00Z" }),
|
|
783
|
+
].join("\n") + "\n");
|
|
784
|
+
|
|
785
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
786
|
+
const result = await compileContext(
|
|
787
|
+
{ sender: "Alice Smith", channel: "D001TESTCH", channel_id: "D001TESTCH" },
|
|
788
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
789
|
+
{}
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
// FTLab operations message should be included
|
|
793
|
+
assert.ok(result.contextBlock.includes("FTLab operations are on track"));
|
|
794
|
+
assert.ok(result.contextBlock.includes("FTLab project going"));
|
|
795
|
+
// Rollup strategy and legal messages should be filtered out
|
|
796
|
+
assert.ok(!result.contextBlock.includes("Aether acquisition target"));
|
|
797
|
+
assert.ok(!result.contextBlock.includes("GC advice"));
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it("includes disclosure boundaries block with restrictions", async () => {
|
|
801
|
+
writeFileSync(join(TEST_DIR, "memory", "profiles", "users", "alice-smith.yaml"),
|
|
802
|
+
`name: Alice Smith\ninformation_boundaries:\n excluded_domains:\n - rollup-strategy\n - adaptic-internal-legal\n`
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
806
|
+
const result = await compileContext(
|
|
807
|
+
{ sender: "Alice Smith", channel: "C001" },
|
|
808
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
809
|
+
{}
|
|
810
|
+
);
|
|
811
|
+
assert.ok(result.contextBlock.includes("Disclosure Boundaries"));
|
|
812
|
+
assert.ok(result.contextBlock.includes("DO NOT mention"));
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it("includes active sessions when present", async () => {
|
|
816
|
+
writeFileSync(join(TEST_DIR, "state", "sessions", "active.json"), JSON.stringify({
|
|
817
|
+
"s-100": { sender: "Bob", channel: "C002", summary: "Draft investor deck", model: "opus", startTime: Date.now() - 60000 }
|
|
818
|
+
}));
|
|
819
|
+
|
|
820
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
821
|
+
const result = await compileContext(
|
|
822
|
+
{ sender: "Unknown", channel: "C001" },
|
|
823
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
824
|
+
{}
|
|
825
|
+
);
|
|
826
|
+
assert.ok(result.contextBlock.includes("Active Sessions"));
|
|
827
|
+
assert.ok(result.contextBlock.includes("Draft investor deck"));
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it("includes sent-registry entries for same sender", async () => {
|
|
831
|
+
writeFileSync(join(TEST_DIR, "state", "sessions", "sent-registry.jsonl"),
|
|
832
|
+
JSON.stringify({
|
|
833
|
+
timestamp: new Date().toISOString(),
|
|
834
|
+
channel: "D001TESTCH",
|
|
835
|
+
threadTs: null,
|
|
836
|
+
type: "holding",
|
|
837
|
+
sessionId: "s-1",
|
|
838
|
+
textPreview: "Got it, looking into this now."
|
|
839
|
+
}) + "\n"
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
843
|
+
const result = await compileContext(
|
|
844
|
+
{ sender: "Unknown", channel: "D001TESTCH", channel_id: "D001TESTCH" },
|
|
845
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
846
|
+
{}
|
|
847
|
+
);
|
|
848
|
+
assert.ok(result.contextBlock.includes("Already Sent"));
|
|
849
|
+
assert.ok(result.contextBlock.includes("Got it, looking into this"));
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it("CEO gets all domains — no filtering", async () => {
|
|
853
|
+
writeFileSync(join(TEST_DIR, "memory", "profiles", "users", "mehran-granfar.yaml"),
|
|
854
|
+
`name: Mehran Granfar\nprivilege_level: ceo\n`
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
const historyDir = join(TEST_DIR, "memory", "interactions", "slack", "dm-mehran-granfar");
|
|
858
|
+
mkdirSync(historyDir, { recursive: true });
|
|
859
|
+
const today = new Date().toISOString().split("T")[0];
|
|
860
|
+
writeFileSync(join(historyDir, `${today}.jsonl`), [
|
|
861
|
+
JSON.stringify({ ts: "1000", from: "sophie", content: "Project Aether target list updated", received_at: "2026-04-06T10:00:00Z" }),
|
|
862
|
+
JSON.stringify({ ts: "1001", from: "sophie", content: "GC legal strategy memo ready", received_at: "2026-04-06T10:01:00Z" }),
|
|
863
|
+
].join("\n") + "\n");
|
|
864
|
+
|
|
865
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
866
|
+
const result = await compileContext(
|
|
867
|
+
{ sender: "Mehran Granfar", channel: "D099CEO", channel_id: "D099CEO" },
|
|
868
|
+
{ priority: "critical", model: "opus", action: "respond" },
|
|
869
|
+
{}
|
|
870
|
+
);
|
|
871
|
+
// CEO should see everything
|
|
872
|
+
assert.ok(result.contextBlock.includes("Aether target list"));
|
|
873
|
+
assert.ok(result.contextBlock.includes("legal strategy memo"));
|
|
874
|
+
assert.ok(!result.contextBlock.includes("DO NOT mention"));
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
880
|
+
|
|
881
|
+
```bash
|
|
882
|
+
node --test scripts/daemon/test-context-compiler.mjs
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Expected: FAIL — `context-compiler.mjs` does not exist.
|
|
886
|
+
|
|
887
|
+
- [ ] **Step 3: Implement the context compiler**
|
|
888
|
+
|
|
889
|
+
Create `scripts/daemon/context-compiler.mjs`:
|
|
890
|
+
|
|
891
|
+
```javascript
|
|
892
|
+
#!/usr/bin/env node
|
|
893
|
+
/**
|
|
894
|
+
* context-compiler.mjs — Pre-compile session context
|
|
895
|
+
*
|
|
896
|
+
* Assembles sender profile, disclosure boundaries, conversation history,
|
|
897
|
+
* active session state, and sent-message registry into a single context
|
|
898
|
+
* block injected into every dispatched session's prompt.
|
|
899
|
+
*
|
|
900
|
+
* Key principle: sessions receive pre-filtered context as data, not
|
|
901
|
+
* instructions to navigate the filesystem.
|
|
902
|
+
*/
|
|
903
|
+
|
|
904
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
905
|
+
import { join } from "path";
|
|
906
|
+
|
|
907
|
+
const SOPHIE_AI_DIR = process.env.__TEST_SOPHIE_DIR
|
|
908
|
+
|| join(new URL(".", import.meta.url).pathname, "../..");
|
|
909
|
+
const SESSION_DIR = process.env.__TEST_SESSION_DIR
|
|
910
|
+
|| join(SOPHIE_AI_DIR, "state", "sessions");
|
|
911
|
+
|
|
912
|
+
// Approximate chars-per-token ratio for budget calculations
|
|
913
|
+
const CHARS_PER_TOKEN = 4;
|
|
914
|
+
|
|
915
|
+
// Token budgets by priority tier
|
|
916
|
+
const TOKEN_BUDGETS = {
|
|
917
|
+
critical: 12000,
|
|
918
|
+
high: 12000,
|
|
919
|
+
normal: 8000,
|
|
920
|
+
low: 4000,
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// Fixed allocations (tokens)
|
|
924
|
+
const PROFILE_BUDGET = 500;
|
|
925
|
+
const BOUNDARIES_BUDGET = 300;
|
|
926
|
+
const ACTIVE_SESSIONS_BUDGET = 300;
|
|
927
|
+
const SENT_REGISTRY_BUDGET = 500;
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Main entry point: compile all context for a session.
|
|
931
|
+
*
|
|
932
|
+
* @param {object} item - Inbox item (sender, channel, channel_id, raw_ref, etc.)
|
|
933
|
+
* @param {object} classResult - Classifier output (priority, model, action)
|
|
934
|
+
* @param {object} options - { type?: "inbox"|"backlog" }
|
|
935
|
+
* @returns {{ contextBlock: string, tokenBudget: number, tokenEstimate: number }}
|
|
936
|
+
*/
|
|
937
|
+
export async function compileContext(item, classResult, options = {}) {
|
|
938
|
+
const totalBudget = TOKEN_BUDGETS[classResult.priority] || TOKEN_BUDGETS.normal;
|
|
939
|
+
const historyBudget = totalBudget - PROFILE_BUDGET - BOUNDARIES_BUDGET - ACTIVE_SESSIONS_BUDGET - SENT_REGISTRY_BUDGET;
|
|
940
|
+
|
|
941
|
+
const senderSlug = item.sender ? item.sender.replace(/\s+/g, "-").toLowerCase() : null;
|
|
942
|
+
const channelId = extractChannelId(item);
|
|
943
|
+
|
|
944
|
+
// Step 2: Load user profile
|
|
945
|
+
const profileSection = loadUserProfile(senderSlug, PROFILE_BUDGET);
|
|
946
|
+
|
|
947
|
+
// Step 3: Load disclosure boundaries
|
|
948
|
+
const { boundariesSection, forbiddenDomains } = loadDisclosureBoundaries(senderSlug, BOUNDARIES_BUDGET);
|
|
949
|
+
|
|
950
|
+
// Step 4: Load conversation history (filtered by disclosure boundaries)
|
|
951
|
+
const historySection = loadConversationHistory(senderSlug, channelId, historyBudget, forbiddenDomains);
|
|
952
|
+
|
|
953
|
+
// Step 5: Load active sessions
|
|
954
|
+
const activeSection = loadActiveSessions(ACTIVE_SESSIONS_BUDGET);
|
|
955
|
+
|
|
956
|
+
// Step 6: Load sent-message registry
|
|
957
|
+
const sentSection = loadSentRegistry(channelId, SENT_REGISTRY_BUDGET);
|
|
958
|
+
|
|
959
|
+
// Step 7: Assemble
|
|
960
|
+
const sections = [
|
|
961
|
+
"--- COMPILED CONTEXT (do not repeat verbatim) ---",
|
|
962
|
+
"",
|
|
963
|
+
];
|
|
964
|
+
|
|
965
|
+
if (profileSection) {
|
|
966
|
+
sections.push("## Sender Profile", profileSection, "");
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (boundariesSection) {
|
|
970
|
+
sections.push("## Disclosure Boundaries", boundariesSection, "");
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (historySection) {
|
|
974
|
+
sections.push("## Conversation History (most recent last)", historySection, "");
|
|
975
|
+
} else {
|
|
976
|
+
sections.push("## Conversation History", "No prior conversation history found.", "");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (activeSection) {
|
|
980
|
+
sections.push("## Active Sessions", activeSection, "");
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (sentSection) {
|
|
984
|
+
sections.push("## Already Sent", sentSection, "");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
sections.push("--- END CONTEXT ---");
|
|
988
|
+
|
|
989
|
+
const contextBlock = sections.join("\n");
|
|
990
|
+
const tokenEstimate = Math.ceil(contextBlock.length / CHARS_PER_TOKEN);
|
|
991
|
+
|
|
992
|
+
return { contextBlock, tokenBudget: totalBudget, tokenEstimate };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
996
|
+
|
|
997
|
+
function extractChannelId(item) {
|
|
998
|
+
if (!item) return null;
|
|
999
|
+
if (item.channel_id) return item.channel_id;
|
|
1000
|
+
if (item.raw_ref) {
|
|
1001
|
+
const match = item.raw_ref.match(/slack:([DC][A-Z0-9]+):/);
|
|
1002
|
+
if (match) return match[1];
|
|
1003
|
+
}
|
|
1004
|
+
if (item.channel && /^[DC][A-Z0-9]{8,}$/.test(item.channel)) return item.channel;
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function loadUserProfile(senderSlug, budget) {
|
|
1009
|
+
if (!senderSlug) return null;
|
|
1010
|
+
const profilePath = join(SOPHIE_AI_DIR, "memory", "profiles", "users", `${senderSlug}.yaml`);
|
|
1011
|
+
try {
|
|
1012
|
+
const raw = readFileSync(profilePath, "utf-8");
|
|
1013
|
+
// Truncate to budget
|
|
1014
|
+
const maxChars = budget * CHARS_PER_TOKEN;
|
|
1015
|
+
return raw.substring(0, maxChars);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Load disclosure boundaries from the sender's profile.
|
|
1023
|
+
* Returns the boundaries section text and the list of forbidden domains.
|
|
1024
|
+
*/
|
|
1025
|
+
function loadDisclosureBoundaries(senderSlug, budget) {
|
|
1026
|
+
if (!senderSlug) {
|
|
1027
|
+
return {
|
|
1028
|
+
boundariesSection: "ALLOWED domains: public only. DO NOT mention or reference any internal Adaptic information.",
|
|
1029
|
+
forbiddenDomains: getAllDomains(),
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Read sender profile to find excluded domains
|
|
1034
|
+
const profilePath = join(SOPHIE_AI_DIR, "memory", "profiles", "users", `${senderSlug}.yaml`);
|
|
1035
|
+
let excludedDomains = [];
|
|
1036
|
+
let privilegeLevel = "unknown";
|
|
1037
|
+
|
|
1038
|
+
try {
|
|
1039
|
+
const raw = readFileSync(profilePath, "utf-8");
|
|
1040
|
+
|
|
1041
|
+
// Parse privilege_level
|
|
1042
|
+
const privMatch = raw.match(/privilege_level:\s*(\S+)/);
|
|
1043
|
+
if (privMatch) privilegeLevel = privMatch[1];
|
|
1044
|
+
|
|
1045
|
+
// CEO sees everything — no restrictions
|
|
1046
|
+
if (privilegeLevel === "ceo") {
|
|
1047
|
+
return { boundariesSection: null, forbiddenDomains: [] };
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Parse excluded_domains list
|
|
1051
|
+
const boundaryBlock = raw.match(/information_boundaries:[\s\S]*?(?=\n\S|\n$|$)/);
|
|
1052
|
+
if (boundaryBlock) {
|
|
1053
|
+
const excludedMatch = boundaryBlock[0].match(/excluded_domains:\s*\n((?:\s+-\s+.+\n?)*)/);
|
|
1054
|
+
if (excludedMatch) {
|
|
1055
|
+
excludedDomains = excludedMatch[1]
|
|
1056
|
+
.split("\n")
|
|
1057
|
+
.map((line) => line.replace(/^\s*-\s*/, "").trim())
|
|
1058
|
+
.filter(Boolean);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
} catch {
|
|
1062
|
+
// No profile — treat as unknown sender, restrict to public
|
|
1063
|
+
return {
|
|
1064
|
+
boundariesSection: "ALLOWED domains: public only. DO NOT mention or reference any internal Adaptic information.",
|
|
1065
|
+
forbiddenDomains: getAllDomains(),
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (excludedDomains.length === 0) {
|
|
1070
|
+
return { boundariesSection: null, forbiddenDomains: [] };
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Build human-readable restrictions from domain descriptions
|
|
1074
|
+
const domainDescriptions = loadDomainDescriptions();
|
|
1075
|
+
const restrictions = excludedDomains
|
|
1076
|
+
.map((d) => `- ${domainDescriptions[d] || d}`)
|
|
1077
|
+
.join("\n");
|
|
1078
|
+
|
|
1079
|
+
const boundariesSection = `DO NOT mention or reference:\n${restrictions}`;
|
|
1080
|
+
|
|
1081
|
+
return { boundariesSection, forbiddenDomains: excludedDomains };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Load all domain names from information-barriers.yaml.
|
|
1086
|
+
* Used when sender is unknown — restrict everything except public.
|
|
1087
|
+
*/
|
|
1088
|
+
function getAllDomains() {
|
|
1089
|
+
const descriptions = loadDomainDescriptions();
|
|
1090
|
+
return Object.keys(descriptions).filter((d) => d !== "public");
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Load domain name → description map from information-barriers.yaml.
|
|
1095
|
+
*/
|
|
1096
|
+
function loadDomainDescriptions() {
|
|
1097
|
+
const barriersPath = join(SOPHIE_AI_DIR, "policies", "information-barriers.yaml");
|
|
1098
|
+
try {
|
|
1099
|
+
const raw = readFileSync(barriersPath, "utf-8");
|
|
1100
|
+
const descriptions = {};
|
|
1101
|
+
const domainMatches = raw.matchAll(/^\s{2}([\w-]+):\s*\n\s+description:\s*"(.+?)"/gm);
|
|
1102
|
+
for (const m of domainMatches) {
|
|
1103
|
+
descriptions[m[1]] = m[2];
|
|
1104
|
+
}
|
|
1105
|
+
return descriptions;
|
|
1106
|
+
} catch {
|
|
1107
|
+
return {};
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Build keyword patterns for each domain, used to filter history entries.
|
|
1113
|
+
* Keywords are extracted from domain descriptions in information-barriers.yaml.
|
|
1114
|
+
*/
|
|
1115
|
+
function buildDomainKeywords() {
|
|
1116
|
+
const descriptions = loadDomainDescriptions();
|
|
1117
|
+
const keywords = {};
|
|
1118
|
+
for (const [domain, desc] of Object.entries(descriptions)) {
|
|
1119
|
+
// Extract significant words from description (4+ chars, lowercased)
|
|
1120
|
+
keywords[domain] = desc
|
|
1121
|
+
.toLowerCase()
|
|
1122
|
+
.replace(/[^a-z0-9\s]/g, "")
|
|
1123
|
+
.split(/\s+/)
|
|
1124
|
+
.filter((w) => w.length >= 4 && !["with", "from", "that", "this", "than"].includes(w));
|
|
1125
|
+
}
|
|
1126
|
+
return keywords;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Check if a message text matches any of the forbidden domains.
|
|
1131
|
+
* Uses keyword heuristic — conservative (exclude if uncertain).
|
|
1132
|
+
*/
|
|
1133
|
+
function matchesForbiddenDomain(text, forbiddenDomains, domainKeywords) {
|
|
1134
|
+
if (forbiddenDomains.length === 0) return false;
|
|
1135
|
+
const lower = text.toLowerCase();
|
|
1136
|
+
for (const domain of forbiddenDomains) {
|
|
1137
|
+
const keywords = domainKeywords[domain] || [];
|
|
1138
|
+
// If 2+ keywords from a domain match, consider it a match
|
|
1139
|
+
let hits = 0;
|
|
1140
|
+
for (const kw of keywords) {
|
|
1141
|
+
if (lower.includes(kw)) hits++;
|
|
1142
|
+
if (hits >= 2) return true;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Load conversation history, filtered by disclosure boundaries.
|
|
1150
|
+
*/
|
|
1151
|
+
function loadConversationHistory(senderSlug, channelId, budgetTokens, forbiddenDomains) {
|
|
1152
|
+
const maxChars = budgetTokens * CHARS_PER_TOKEN;
|
|
1153
|
+
const entries = [];
|
|
1154
|
+
const today = new Date().toISOString().split("T")[0];
|
|
1155
|
+
|
|
1156
|
+
// Candidate directories to scan
|
|
1157
|
+
const candidateDirs = [];
|
|
1158
|
+
if (channelId) candidateDirs.push(join(SOPHIE_AI_DIR, "memory", "interactions", "slack", channelId));
|
|
1159
|
+
if (senderSlug) {
|
|
1160
|
+
candidateDirs.push(join(SOPHIE_AI_DIR, "memory", "interactions", "slack", `dm-${senderSlug}`));
|
|
1161
|
+
candidateDirs.push(join(SOPHIE_AI_DIR, "memory", "interactions", "slack", senderSlug));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
for (const dir of candidateDirs) {
|
|
1165
|
+
try {
|
|
1166
|
+
const files = readdirSync(dir)
|
|
1167
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
1168
|
+
.sort()
|
|
1169
|
+
.reverse()
|
|
1170
|
+
.slice(0, 3); // Today + yesterday + day before
|
|
1171
|
+
|
|
1172
|
+
for (const file of files) {
|
|
1173
|
+
try {
|
|
1174
|
+
const content = readFileSync(join(dir, file), "utf-8").trim();
|
|
1175
|
+
if (!content) continue;
|
|
1176
|
+
for (const line of content.split("\n").filter(Boolean)) {
|
|
1177
|
+
try { entries.push(JSON.parse(line)); } catch {}
|
|
1178
|
+
}
|
|
1179
|
+
} catch {}
|
|
1180
|
+
}
|
|
1181
|
+
} catch {}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (entries.length === 0) return null;
|
|
1185
|
+
|
|
1186
|
+
// Deduplicate by timestamp
|
|
1187
|
+
const seen = new Set();
|
|
1188
|
+
const unique = entries.filter((e) => {
|
|
1189
|
+
const key = `${e.ts || e.received_at || ""}:${e.from || ""}`;
|
|
1190
|
+
if (seen.has(key)) return false;
|
|
1191
|
+
seen.add(key);
|
|
1192
|
+
return true;
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// Sort chronologically
|
|
1196
|
+
unique.sort((a, b) => {
|
|
1197
|
+
const tsA = a.ts || a.received_at || "";
|
|
1198
|
+
const tsB = b.ts || b.received_at || "";
|
|
1199
|
+
return tsA.localeCompare(tsB);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
// Filter by disclosure boundaries
|
|
1203
|
+
const domainKeywords = buildDomainKeywords();
|
|
1204
|
+
const filtered = unique.filter((e) => {
|
|
1205
|
+
const text = e.content || e.text || "";
|
|
1206
|
+
return !matchesForbiddenDomain(text, forbiddenDomains, domainKeywords);
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
// Format and truncate to budget (most recent first for truncation)
|
|
1210
|
+
const lines = [];
|
|
1211
|
+
let totalChars = 0;
|
|
1212
|
+
|
|
1213
|
+
// Take from end (most recent) to respect budget
|
|
1214
|
+
for (let i = filtered.length - 1; i >= 0; i--) {
|
|
1215
|
+
const e = filtered[i];
|
|
1216
|
+
const from = e.from || e.user_name || e.sender || "unknown";
|
|
1217
|
+
const text = (e.content || e.text || "").substring(0, 300);
|
|
1218
|
+
const ts = e.received_at || e.ts || "";
|
|
1219
|
+
const responded = e.response_sent ? " [Responded]" : "";
|
|
1220
|
+
const line = `[${ts}] ${from}: ${text}${responded}`;
|
|
1221
|
+
|
|
1222
|
+
if (totalChars + line.length > maxChars) break;
|
|
1223
|
+
lines.unshift(line); // Prepend to maintain chronological order
|
|
1224
|
+
totalChars += line.length;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Load active session state from active.json.
|
|
1232
|
+
*/
|
|
1233
|
+
function loadActiveSessions(budgetTokens) {
|
|
1234
|
+
const activePath = join(SESSION_DIR, "active.json");
|
|
1235
|
+
try {
|
|
1236
|
+
const raw = readFileSync(activePath, "utf-8");
|
|
1237
|
+
const sessions = JSON.parse(raw);
|
|
1238
|
+
const entries = Object.entries(sessions);
|
|
1239
|
+
if (entries.length === 0) return null;
|
|
1240
|
+
|
|
1241
|
+
const lines = entries.map(([id, s]) => {
|
|
1242
|
+
const age = Math.round((Date.now() - (s.startTime || Date.now())) / 60000);
|
|
1243
|
+
return `- [${id}] ${s.summary || "unknown task"} (${s.model || "sonnet"}, ${s.sender || "system"}, started ${age}m ago)`;
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
return lines.join("\n");
|
|
1247
|
+
} catch {
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Load recent sent-message registry entries for a specific channel.
|
|
1254
|
+
*/
|
|
1255
|
+
function loadSentRegistry(channelId, budgetTokens) {
|
|
1256
|
+
if (!channelId) return null;
|
|
1257
|
+
const registryPath = join(SESSION_DIR, "sent-registry.jsonl");
|
|
1258
|
+
try {
|
|
1259
|
+
const raw = readFileSync(registryPath, "utf-8").trim();
|
|
1260
|
+
if (!raw) return null;
|
|
1261
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
1262
|
+
const entries = lines
|
|
1263
|
+
.map((line) => { try { return JSON.parse(line); } catch { return null; } })
|
|
1264
|
+
.filter(Boolean)
|
|
1265
|
+
.filter((e) => e.channel === channelId);
|
|
1266
|
+
|
|
1267
|
+
if (entries.length === 0) return null;
|
|
1268
|
+
|
|
1269
|
+
// Take last 20 entries for this channel
|
|
1270
|
+
const recent = entries.slice(-20);
|
|
1271
|
+
const formatted = recent.map((e) =>
|
|
1272
|
+
`[${e.timestamp}] (${e.type}) ${e.textPreview || "(no preview)"}`
|
|
1273
|
+
);
|
|
1274
|
+
return formatted.join("\n");
|
|
1275
|
+
} catch {
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
- [ ] **Step 4: Run all context compiler tests**
|
|
1282
|
+
|
|
1283
|
+
```bash
|
|
1284
|
+
node --test scripts/daemon/test-context-compiler.mjs
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
Expected: All 7 tests pass.
|
|
1288
|
+
|
|
1289
|
+
- [ ] **Step 5: Commit**
|
|
1290
|
+
|
|
1291
|
+
```bash
|
|
1292
|
+
git add scripts/daemon/context-compiler.mjs scripts/daemon/test-context-compiler.mjs
|
|
1293
|
+
git commit -m "feat: context compiler with disclosure boundary filtering
|
|
1294
|
+
|
|
1295
|
+
Pre-compiles sender profile, conversation history, active sessions,
|
|
1296
|
+
and sent-message registry into a single context block. Filters history
|
|
1297
|
+
entries by information barrier domains. Adaptive token budget: 12K for
|
|
1298
|
+
critical/high, 8K for normal, 4K for low priority."
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
## Task 4: Integrate Session Locks into Daemon
|
|
1304
|
+
|
|
1305
|
+
**Files:**
|
|
1306
|
+
- Modify: `scripts/daemon/sophie-daemon.mjs`
|
|
1307
|
+
|
|
1308
|
+
- [ ] **Step 1: Read the current file**
|
|
1309
|
+
|
|
1310
|
+
Read `scripts/daemon/sophie-daemon.mjs` to confirm current line numbers.
|
|
1311
|
+
|
|
1312
|
+
- [ ] **Step 2: Add session-lock imports, replace in-memory dedup**
|
|
1313
|
+
|
|
1314
|
+
Replace the dedup-related code. Make these changes to `sophie-daemon.mjs`:
|
|
1315
|
+
|
|
1316
|
+
**Add import** (after the existing imports at line 35):
|
|
1317
|
+
|
|
1318
|
+
```javascript
|
|
1319
|
+
import { acquireLock, updateLock, scanStaleLocks } from "./session-lock.mjs";
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
**Remove** these lines entirely:
|
|
1323
|
+
- Line 44: `const DEDUP_WINDOW = 5 * 60 * 1000;`
|
|
1324
|
+
- Line 46: `const recentlyProcessed = new Map();`
|
|
1325
|
+
- Lines 71-83: The entire `isDuplicate` function and `cleanDedup` function
|
|
1326
|
+
|
|
1327
|
+
**Replace the poll loop filter** (line 101):
|
|
1328
|
+
|
|
1329
|
+
Old:
|
|
1330
|
+
```javascript
|
|
1331
|
+
const newItems = result.items.filter((item) => !isDuplicate(item.id || item.raw_ref));
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
New:
|
|
1335
|
+
```javascript
|
|
1336
|
+
const newItems = result.items.filter((item) => {
|
|
1337
|
+
const itemId = item.id || item.raw_ref || `${svc.name}-${Date.now()}`;
|
|
1338
|
+
const lock = acquireLock(itemId, {
|
|
1339
|
+
sender: item.sender || "unknown",
|
|
1340
|
+
channel: item.channel || item.channel_id || "unknown",
|
|
1341
|
+
});
|
|
1342
|
+
return lock.acquired;
|
|
1343
|
+
});
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
**Remove the `cleanDedup()` call** (line 121):
|
|
1347
|
+
|
|
1348
|
+
Old:
|
|
1349
|
+
```javascript
|
|
1350
|
+
cleanDedup();
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
Remove this line entirely.
|
|
1354
|
+
|
|
1355
|
+
**Add stale lock scan on startup** in `main()` (after emergency stop check, around line 332):
|
|
1356
|
+
|
|
1357
|
+
```javascript
|
|
1358
|
+
// Clear orphaned locks from prior crashes
|
|
1359
|
+
const staleCleared = scanStaleLocks();
|
|
1360
|
+
if (staleCleared > 0) {
|
|
1361
|
+
console.log(`[daemon] Cleared ${staleCleared} stale session locks`);
|
|
1362
|
+
}
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
- [ ] **Step 3: Update holding message to set lock flag**
|
|
1366
|
+
|
|
1367
|
+
In `processItem()`, after the holding message is sent (around line 180), add lock update:
|
|
1368
|
+
|
|
1369
|
+
Old:
|
|
1370
|
+
```javascript
|
|
1371
|
+
const holdResult = await sendHoldingMessage(item, classResult);
|
|
1372
|
+
holdingText = holdResult.sent ? holdResult.holdingText : null;
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
New:
|
|
1376
|
+
```javascript
|
|
1377
|
+
const holdResult = await sendHoldingMessage(item, classResult);
|
|
1378
|
+
holdingText = holdResult.sent ? holdResult.holdingText : null;
|
|
1379
|
+
if (holdResult.sent) {
|
|
1380
|
+
const lockItemId = item.id || item.raw_ref || `${service}-${Date.now()}`;
|
|
1381
|
+
updateLock(lockItemId, { holdingSent: true });
|
|
1382
|
+
}
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
- [ ] **Step 4: Verify daemon module loads without errors**
|
|
1386
|
+
|
|
1387
|
+
```bash
|
|
1388
|
+
node -e "import('./scripts/daemon/sophie-daemon.mjs').catch(e => { console.error(e.message); process.exit(1); })"
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
Expected: May fail due to missing env vars, but should NOT fail on import errors. If it errors on missing poller modules or env vars, that's expected — the structural changes are correct.
|
|
1392
|
+
|
|
1393
|
+
- [ ] **Step 5: Commit**
|
|
1394
|
+
|
|
1395
|
+
```bash
|
|
1396
|
+
git add scripts/daemon/sophie-daemon.mjs
|
|
1397
|
+
git commit -m "refactor: replace in-memory dedup with file-based session locks
|
|
1398
|
+
|
|
1399
|
+
Remove recentlyProcessed Map and isDuplicate(). Use acquireLock() for
|
|
1400
|
+
atomic item claim. Set holdingSent flag when holding message is sent.
|
|
1401
|
+
Scan and clear stale locks on daemon startup."
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1406
|
+
## Task 5: Integrate Session Locks into Dispatcher
|
|
1407
|
+
|
|
1408
|
+
**Files:**
|
|
1409
|
+
- Modify: `scripts/daemon/dispatcher.mjs`
|
|
1410
|
+
|
|
1411
|
+
- [ ] **Step 1: Read the current file**
|
|
1412
|
+
|
|
1413
|
+
Read `scripts/daemon/dispatcher.mjs` to confirm current line numbers.
|
|
1414
|
+
|
|
1415
|
+
- [ ] **Step 2: Add imports and fix backlogKey**
|
|
1416
|
+
|
|
1417
|
+
Add import at top (after existing imports):
|
|
1418
|
+
|
|
1419
|
+
```javascript
|
|
1420
|
+
import { releaseLock, readLock } from "./session-lock.mjs";
|
|
1421
|
+
import { readFileSync as _readFileSync, writeFileSync as _writeFileSync, renameSync } from "fs";
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
**Stabilise `backlogKey()`** (replace lines 94-96):
|
|
1425
|
+
|
|
1426
|
+
Old:
|
|
1427
|
+
```javascript
|
|
1428
|
+
function backlogKey(item) {
|
|
1429
|
+
return item.title || item.id || item.summary || JSON.stringify(item).substring(0, 100);
|
|
1430
|
+
}
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
New:
|
|
1434
|
+
```javascript
|
|
1435
|
+
function backlogKey(item) {
|
|
1436
|
+
return item.id || item.title || item.summary || JSON.stringify(item).substring(0, 100);
|
|
1437
|
+
}
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
- [ ] **Step 3: Write active.json on session spawn/close**
|
|
1441
|
+
|
|
1442
|
+
Add helper functions after the existing `logSession` function:
|
|
1443
|
+
|
|
1444
|
+
```javascript
|
|
1445
|
+
const ACTIVE_PATH = join(SOPHIE_AI_DIR, "state", "sessions", "active.json");
|
|
1446
|
+
|
|
1447
|
+
function writeActiveSession(sessionId, entry) {
|
|
1448
|
+
try {
|
|
1449
|
+
let active = {};
|
|
1450
|
+
try { active = JSON.parse(_readFileSync(ACTIVE_PATH, "utf-8")); } catch {}
|
|
1451
|
+
active[sessionId] = {
|
|
1452
|
+
sender: entry.item?.sender || null,
|
|
1453
|
+
channel: entry.item?.channel || entry.item?.channel_id || null,
|
|
1454
|
+
summary: entry.classResult?.summary || "unknown",
|
|
1455
|
+
model: entry.classResult?.model || "sonnet",
|
|
1456
|
+
startTime: Date.now(),
|
|
1457
|
+
source: entry.source,
|
|
1458
|
+
};
|
|
1459
|
+
const tmpPath = ACTIVE_PATH + ".tmp";
|
|
1460
|
+
_writeFileSync(tmpPath, JSON.stringify(active, null, 2));
|
|
1461
|
+
renameSync(tmpPath, ACTIVE_PATH);
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
console.warn(`[dispatcher] Failed to write active.json: ${err.message}`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function removeActiveSession(sessionId) {
|
|
1468
|
+
try {
|
|
1469
|
+
let active = {};
|
|
1470
|
+
try { active = JSON.parse(_readFileSync(ACTIVE_PATH, "utf-8")); } catch {}
|
|
1471
|
+
delete active[sessionId];
|
|
1472
|
+
const tmpPath = ACTIVE_PATH + ".tmp";
|
|
1473
|
+
_writeFileSync(tmpPath, JSON.stringify(active, null, 2));
|
|
1474
|
+
renameSync(tmpPath, ACTIVE_PATH);
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
console.warn(`[dispatcher] Failed to update active.json: ${err.message}`);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
**In `spawnSession()`**, after `activeSessions.set(sessionId, ...)` (around line 208), add:
|
|
1482
|
+
|
|
1483
|
+
```javascript
|
|
1484
|
+
writeActiveSession(sessionId, entry);
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
**In the `proc.on("close")` handler** (around line 224), after `activeSessions.delete(sessionId)`, add:
|
|
1488
|
+
|
|
1489
|
+
```javascript
|
|
1490
|
+
removeActiveSession(sessionId);
|
|
1491
|
+
|
|
1492
|
+
// Release item lock
|
|
1493
|
+
const itemId = item.id || item.raw_ref || item.title;
|
|
1494
|
+
if (itemId) releaseLock(itemId);
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
- [ ] **Step 4: Verify module loads**
|
|
1498
|
+
|
|
1499
|
+
```bash
|
|
1500
|
+
node -e "import('./scripts/daemon/dispatcher.mjs').catch(e => { console.error(e.message); process.exit(1); })"
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
Expected: No import errors.
|
|
1504
|
+
|
|
1505
|
+
- [ ] **Step 5: Commit**
|
|
1506
|
+
|
|
1507
|
+
```bash
|
|
1508
|
+
git add scripts/daemon/dispatcher.mjs
|
|
1509
|
+
git commit -m "refactor: write active.json and release locks on session lifecycle
|
|
1510
|
+
|
|
1511
|
+
Dispatcher now writes/removes entries from state/sessions/active.json
|
|
1512
|
+
on session spawn/close (atomic via tmp+rename). Releases item locks
|
|
1513
|
+
on session completion. Stabilise backlogKey to prefer item.id."
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
---
|
|
1517
|
+
|
|
1518
|
+
## Task 6: Integrate Context Compiler into Prompt Builder
|
|
1519
|
+
|
|
1520
|
+
**Files:**
|
|
1521
|
+
- Modify: `scripts/daemon/prompt-builder.mjs`
|
|
1522
|
+
|
|
1523
|
+
- [ ] **Step 1: Read the current file**
|
|
1524
|
+
|
|
1525
|
+
Read `scripts/daemon/prompt-builder.mjs` to confirm current state.
|
|
1526
|
+
|
|
1527
|
+
- [ ] **Step 2: Add context compiler import and feature flag**
|
|
1528
|
+
|
|
1529
|
+
Add import at top (after existing imports):
|
|
1530
|
+
|
|
1531
|
+
```javascript
|
|
1532
|
+
import { compileContext } from "./context-compiler.mjs";
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
- [ ] **Step 3: Replace conversation history and profile instruction with compiled context**
|
|
1536
|
+
|
|
1537
|
+
In `buildPrompt()`, replace sections 4 and 6 with the compiled context.
|
|
1538
|
+
|
|
1539
|
+
**Remove the MAX_HISTORY_LINES constant** (line 12):
|
|
1540
|
+
|
|
1541
|
+
```javascript
|
|
1542
|
+
// Delete: const MAX_HISTORY_LINES = 30;
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
**Remove the `loadConversationHistory()` function** (lines 148-216 approximately) entirely — this logic now lives in context-compiler.mjs.
|
|
1546
|
+
|
|
1547
|
+
**Remove the `extractChannelId()` helper** (lines 221-233) — also moved to context-compiler.
|
|
1548
|
+
|
|
1549
|
+
**Replace section 4 (conversation history) and section 6 (profile instruction)** in `buildPrompt()`.
|
|
1550
|
+
|
|
1551
|
+
Old section 4 (around lines 369-376):
|
|
1552
|
+
```javascript
|
|
1553
|
+
// 4. Conversation history (so the session has prior context)
|
|
1554
|
+
if (type === "inbox" && item) {
|
|
1555
|
+
const history = loadConversationHistory(item);
|
|
1556
|
+
if (history) {
|
|
1557
|
+
parts.push(history);
|
|
1558
|
+
parts.push("");
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
Old section 6 (around lines 382-387):
|
|
1564
|
+
```javascript
|
|
1565
|
+
// 6. User profile lookup (for inbox items with a sender)
|
|
1566
|
+
if (type === "inbox" && item.sender) {
|
|
1567
|
+
const profileName = item.sender.replace(/\s+/g, "-").toLowerCase();
|
|
1568
|
+
parts.push(`Before responding, read memory/profiles/users/${profileName}.yaml for sender preferences, standing instructions, and relationship context. If the file does not exist, proceed without it.`);
|
|
1569
|
+
parts.push("");
|
|
1570
|
+
}
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
Replace BOTH with a single compiled context block (insert after section 3, before section 5):
|
|
1574
|
+
|
|
1575
|
+
```javascript
|
|
1576
|
+
// 4. Compiled context (profile + history + disclosure boundaries + active sessions + sent messages)
|
|
1577
|
+
if (process.env.DAEMON_CONTEXT_COMPILER === "1" && (type === "inbox" ? item : true)) {
|
|
1578
|
+
try {
|
|
1579
|
+
const { contextBlock } = await compileContext(
|
|
1580
|
+
item || {},
|
|
1581
|
+
classResult,
|
|
1582
|
+
{ type }
|
|
1583
|
+
);
|
|
1584
|
+
parts.push(contextBlock);
|
|
1585
|
+
parts.push("");
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
console.error(`[prompt-builder] Context compilation failed: ${err.message}`);
|
|
1588
|
+
// Fall through to legacy behaviour
|
|
1589
|
+
if (type === "inbox" && item) {
|
|
1590
|
+
const history = loadConversationHistoryLegacy(item);
|
|
1591
|
+
if (history) { parts.push(history); parts.push(""); }
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
} else if (type === "inbox" && item) {
|
|
1595
|
+
// Legacy path (feature flag off)
|
|
1596
|
+
const history = loadConversationHistoryLegacy(item);
|
|
1597
|
+
if (history) { parts.push(history); parts.push(""); }
|
|
1598
|
+
}
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
**Keep the old `loadConversationHistory` as `loadConversationHistoryLegacy`** — rename the function (don't delete) so the feature flag fallback works:
|
|
1602
|
+
|
|
1603
|
+
```javascript
|
|
1604
|
+
// Legacy — kept for feature flag fallback, will be removed after validation
|
|
1605
|
+
function loadConversationHistoryLegacy(item) {
|
|
1606
|
+
// ... existing loadConversationHistory code unchanged ...
|
|
1607
|
+
}
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
Also keep `extractChannelId` renamed as `extractChannelIdLegacy` used by the legacy function. Or since context-compiler has its own, just keep the legacy version local.
|
|
1611
|
+
|
|
1612
|
+
**Remove the old section 6 (profile instruction)** — it's now inside the compiled context block. In the legacy path, add it back:
|
|
1613
|
+
|
|
1614
|
+
After the legacy history loading block, add:
|
|
1615
|
+
|
|
1616
|
+
```javascript
|
|
1617
|
+
// Legacy profile instruction (only when feature flag is off)
|
|
1618
|
+
if (process.env.DAEMON_CONTEXT_COMPILER !== "1" && type === "inbox" && item && item.sender) {
|
|
1619
|
+
const profileName = item.sender.replace(/\s+/g, "-").toLowerCase();
|
|
1620
|
+
parts.push(`Before responding, read memory/profiles/users/${profileName}.yaml for sender preferences, standing instructions, and relationship context. If the file does not exist, proceed without it.`);
|
|
1621
|
+
parts.push("");
|
|
1622
|
+
}
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
- [ ] **Step 4: Verify module loads**
|
|
1626
|
+
|
|
1627
|
+
```bash
|
|
1628
|
+
node -e "import('./scripts/daemon/prompt-builder.mjs').catch(e => { console.error(e.message); process.exit(1); })"
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
Expected: No import errors.
|
|
1632
|
+
|
|
1633
|
+
- [ ] **Step 5: Commit**
|
|
1634
|
+
|
|
1635
|
+
```bash
|
|
1636
|
+
git add scripts/daemon/prompt-builder.mjs
|
|
1637
|
+
git commit -m "feat: integrate context compiler into prompt builder
|
|
1638
|
+
|
|
1639
|
+
Replace self-discovery instructions with pre-compiled context block.
|
|
1640
|
+
Feature-flagged via DAEMON_CONTEXT_COMPILER=1 — legacy path preserved
|
|
1641
|
+
as fallback. Sessions now receive rich pre-filtered context including
|
|
1642
|
+
profile, disclosure boundaries, history, active sessions, and sent
|
|
1643
|
+
messages directly in the prompt."
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
---
|
|
1647
|
+
|
|
1648
|
+
## Task 7: Integrate Sent-Registry into Responder
|
|
1649
|
+
|
|
1650
|
+
**Files:**
|
|
1651
|
+
- Modify: `scripts/daemon/responder.mjs`
|
|
1652
|
+
|
|
1653
|
+
- [ ] **Step 1: Read the current file**
|
|
1654
|
+
|
|
1655
|
+
Read `scripts/daemon/responder.mjs` to confirm current state.
|
|
1656
|
+
|
|
1657
|
+
- [ ] **Step 2: Add session-lock imports**
|
|
1658
|
+
|
|
1659
|
+
Add after existing imports:
|
|
1660
|
+
|
|
1661
|
+
```javascript
|
|
1662
|
+
import { checkRecentlySent, registerSent } from "./session-lock.mjs";
|
|
1663
|
+
```
|
|
1664
|
+
|
|
1665
|
+
- [ ] **Step 3: Add dedup check and registration to `sendQuickResponse`**
|
|
1666
|
+
|
|
1667
|
+
In `sendQuickResponse()`, after the validation check and before `sendSlackMessage()`:
|
|
1668
|
+
|
|
1669
|
+
Find the section (around line 396):
|
|
1670
|
+
```javascript
|
|
1671
|
+
if (item.service === "slack") {
|
|
1672
|
+
const channel = resolveSlackChannel(item);
|
|
1673
|
+
if (channel) {
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
Replace with:
|
|
1677
|
+
```javascript
|
|
1678
|
+
if (item.service === "slack") {
|
|
1679
|
+
const channel = resolveSlackChannel(item);
|
|
1680
|
+
if (channel) {
|
|
1681
|
+
// Check sent-message registry for duplicates
|
|
1682
|
+
const dupCheck = checkRecentlySent(channel, item.thread_id || null, "quick_reply");
|
|
1683
|
+
if (!dupCheck.allowed) {
|
|
1684
|
+
console.log(`[responder] Quick reply blocked by sent-registry: ${dupCheck.reason}`);
|
|
1685
|
+
logResponse({ type: "quick_response_dedup_blocked", sender: item.sender, reason: dupCheck.reason });
|
|
1686
|
+
return { sent: false, text: null, blocked: true, reason: dupCheck.reason };
|
|
1687
|
+
}
|
|
1688
|
+
```
|
|
1689
|
+
|
|
1690
|
+
After the successful `sendSlackMessage()` call and before `sendResult = { sent: true, ... }`, add:
|
|
1691
|
+
|
|
1692
|
+
```javascript
|
|
1693
|
+
registerSent(channel, item.thread_id || null, "quick_reply", "quick-responder", text.substring(0, 100));
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
- [ ] **Step 4: Add dedup check and registration to `sendHoldingMessage`**
|
|
1697
|
+
|
|
1698
|
+
In `sendHoldingMessage()`, same pattern. After `const channel = resolveSlackChannel(item);`:
|
|
1699
|
+
|
|
1700
|
+
```javascript
|
|
1701
|
+
if (channel) {
|
|
1702
|
+
// Check sent-message registry for duplicates
|
|
1703
|
+
const dupCheck = checkRecentlySent(channel, item.thread_id || null, "holding");
|
|
1704
|
+
if (!dupCheck.allowed) {
|
|
1705
|
+
console.log(`[responder] Holding message blocked by sent-registry: ${dupCheck.reason}`);
|
|
1706
|
+
logResponse({ type: "holding_dedup_blocked", sender: item.sender, reason: dupCheck.reason });
|
|
1707
|
+
return { sent: false, holdingText: null, blocked: true, reason: dupCheck.reason };
|
|
1708
|
+
}
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
After successful `sendSlackMessage()`:
|
|
1712
|
+
|
|
1713
|
+
```javascript
|
|
1714
|
+
registerSent(channel, item.thread_id || null, "holding", "quick-responder", text.substring(0, 100));
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
- [ ] **Step 5: Verify module loads**
|
|
1718
|
+
|
|
1719
|
+
```bash
|
|
1720
|
+
node -e "import('./scripts/daemon/responder.mjs').catch(e => { console.error(e.message); process.exit(1); })"
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
Expected: No import errors.
|
|
1724
|
+
|
|
1725
|
+
- [ ] **Step 6: Commit**
|
|
1726
|
+
|
|
1727
|
+
```bash
|
|
1728
|
+
git add scripts/daemon/responder.mjs
|
|
1729
|
+
git commit -m "feat: add sent-message registry checks to responder
|
|
1730
|
+
|
|
1731
|
+
Quick replies and holding messages now check the sent-registry before
|
|
1732
|
+
sending and register after successful send. Prevents duplicate messages
|
|
1733
|
+
even when upstream dedup fails."
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
---
|
|
1737
|
+
|
|
1738
|
+
## Task 8: Integration Test
|
|
1739
|
+
|
|
1740
|
+
**Files:**
|
|
1741
|
+
- Create: `scripts/daemon/test-integration.mjs`
|
|
1742
|
+
|
|
1743
|
+
- [ ] **Step 1: Write integration tests**
|
|
1744
|
+
|
|
1745
|
+
Create `scripts/daemon/test-integration.mjs`:
|
|
1746
|
+
|
|
1747
|
+
```javascript
|
|
1748
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
1749
|
+
import assert from "node:assert/strict";
|
|
1750
|
+
import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "fs";
|
|
1751
|
+
import { join } from "path";
|
|
1752
|
+
|
|
1753
|
+
const TEST_DIR = join(import.meta.dirname, "../../.test-integration");
|
|
1754
|
+
process.env.__TEST_SESSION_DIR = join(TEST_DIR, "state", "sessions");
|
|
1755
|
+
process.env.__TEST_SOPHIE_DIR = TEST_DIR;
|
|
1756
|
+
|
|
1757
|
+
function setupTestEnv() {
|
|
1758
|
+
mkdirSync(join(TEST_DIR, "state", "sessions", "locks"), { recursive: true });
|
|
1759
|
+
mkdirSync(join(TEST_DIR, "memory", "profiles", "users"), { recursive: true });
|
|
1760
|
+
mkdirSync(join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user"), { recursive: true });
|
|
1761
|
+
mkdirSync(join(TEST_DIR, "policies"), { recursive: true });
|
|
1762
|
+
writeFileSync(join(TEST_DIR, "state", "sessions", "active.json"), "{}");
|
|
1763
|
+
|
|
1764
|
+
writeFileSync(join(TEST_DIR, "policies", "information-barriers.yaml"), `domains:
|
|
1765
|
+
adaptic-internal-legal:
|
|
1766
|
+
description: "Adaptic legal strategy, GC advice, privilege-protected"
|
|
1767
|
+
default_sensitivity: high
|
|
1768
|
+
rollup-strategy:
|
|
1769
|
+
description: "Project Aether M&A targets, deal terms, pipeline"
|
|
1770
|
+
default_sensitivity: critical
|
|
1771
|
+
public:
|
|
1772
|
+
description: "Published information, press, website"
|
|
1773
|
+
default_sensitivity: none
|
|
1774
|
+
`);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function cleanTestEnv() {
|
|
1778
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
describe("Integration: lock → compile → prompt pipeline", () => {
|
|
1782
|
+
beforeEach(setupTestEnv);
|
|
1783
|
+
afterEach(cleanTestEnv);
|
|
1784
|
+
|
|
1785
|
+
it("acquireLock prevents duplicate processing of same item", async () => {
|
|
1786
|
+
const { acquireLock, releaseLock } = await import("./session-lock.mjs");
|
|
1787
|
+
|
|
1788
|
+
const first = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
|
|
1789
|
+
assert.equal(first.acquired, true);
|
|
1790
|
+
|
|
1791
|
+
const second = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
|
|
1792
|
+
assert.equal(second.acquired, false);
|
|
1793
|
+
|
|
1794
|
+
releaseLock("slack-C001-12345");
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
it("sent-registry prevents duplicate sends across quick-reply and holding paths", async () => {
|
|
1798
|
+
const { registerSent, checkRecentlySent } = await import("./session-lock.mjs");
|
|
1799
|
+
|
|
1800
|
+
// Holding message sent
|
|
1801
|
+
registerSent("C001", "12345.6789", "holding", "s-1", "Looking into this now");
|
|
1802
|
+
|
|
1803
|
+
// Quick reply to same channel+thread should still be allowed (different type)
|
|
1804
|
+
const quickCheck = checkRecentlySent("C001", "12345.6789", "quick_reply");
|
|
1805
|
+
assert.equal(quickCheck.allowed, true);
|
|
1806
|
+
|
|
1807
|
+
// Another holding to same channel+thread should be blocked
|
|
1808
|
+
const holdCheck = checkRecentlySent("C001", "12345.6789", "holding");
|
|
1809
|
+
assert.equal(holdCheck.allowed, false);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
it("context compiler respects disclosure boundaries end-to-end", async () => {
|
|
1813
|
+
// Create a profile with restrictions
|
|
1814
|
+
writeFileSync(
|
|
1815
|
+
join(TEST_DIR, "memory", "profiles", "users", "test-user.yaml"),
|
|
1816
|
+
`name: Test User\ninformation_boundaries:\n excluded_domains:\n - rollup-strategy\n`
|
|
1817
|
+
);
|
|
1818
|
+
|
|
1819
|
+
// Create history with mixed content
|
|
1820
|
+
const today = new Date().toISOString().split("T")[0];
|
|
1821
|
+
writeFileSync(
|
|
1822
|
+
join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user", `${today}.jsonl`),
|
|
1823
|
+
[
|
|
1824
|
+
JSON.stringify({ ts: "100", from: "test-user", content: "What is the status?", received_at: "2026-04-06T10:00:00Z" }),
|
|
1825
|
+
JSON.stringify({ ts: "101", from: "sophie", content: "Project Aether deal terms are looking good", received_at: "2026-04-06T10:01:00Z" }),
|
|
1826
|
+
JSON.stringify({ ts: "102", from: "sophie", content: "The public website update is live", received_at: "2026-04-06T10:02:00Z" }),
|
|
1827
|
+
].join("\n") + "\n"
|
|
1828
|
+
);
|
|
1829
|
+
|
|
1830
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
1831
|
+
const { contextBlock } = await compileContext(
|
|
1832
|
+
{ sender: "Test User", channel: "D001", channel_id: "D001" },
|
|
1833
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
1834
|
+
{}
|
|
1835
|
+
);
|
|
1836
|
+
|
|
1837
|
+
// Public content should be included
|
|
1838
|
+
assert.ok(contextBlock.includes("website update is live"));
|
|
1839
|
+
// Rollup content should be filtered
|
|
1840
|
+
assert.ok(!contextBlock.includes("Aether deal terms"));
|
|
1841
|
+
// Should have disclosure boundaries
|
|
1842
|
+
assert.ok(contextBlock.includes("DO NOT mention"));
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
it("active sessions are visible in compiled context", async () => {
|
|
1846
|
+
writeFileSync(
|
|
1847
|
+
join(TEST_DIR, "state", "sessions", "active.json"),
|
|
1848
|
+
JSON.stringify({
|
|
1849
|
+
"s-999": {
|
|
1850
|
+
sender: "Alice",
|
|
1851
|
+
channel: "C002",
|
|
1852
|
+
summary: "Preparing board pack",
|
|
1853
|
+
model: "opus",
|
|
1854
|
+
startTime: Date.now() - 120000,
|
|
1855
|
+
},
|
|
1856
|
+
})
|
|
1857
|
+
);
|
|
1858
|
+
|
|
1859
|
+
const { compileContext } = await import("./context-compiler.mjs");
|
|
1860
|
+
const { contextBlock } = await compileContext(
|
|
1861
|
+
{ sender: "Test", channel: "C001" },
|
|
1862
|
+
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
1863
|
+
{}
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
assert.ok(contextBlock.includes("Preparing board pack"));
|
|
1867
|
+
assert.ok(contextBlock.includes("Active Sessions"));
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1870
|
+
```
|
|
1871
|
+
|
|
1872
|
+
- [ ] **Step 2: Run integration tests**
|
|
1873
|
+
|
|
1874
|
+
```bash
|
|
1875
|
+
node --test scripts/daemon/test-integration.mjs
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
Expected: All 4 tests pass.
|
|
1879
|
+
|
|
1880
|
+
- [ ] **Step 3: Run all test files together**
|
|
1881
|
+
|
|
1882
|
+
```bash
|
|
1883
|
+
node --test scripts/daemon/test-session-lock.mjs scripts/daemon/test-context-compiler.mjs scripts/daemon/test-integration.mjs
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
Expected: All tests pass.
|
|
1887
|
+
|
|
1888
|
+
- [ ] **Step 4: Commit**
|
|
1889
|
+
|
|
1890
|
+
```bash
|
|
1891
|
+
git add scripts/daemon/test-integration.mjs
|
|
1892
|
+
git commit -m "test: integration tests for session lock + context compiler pipeline
|
|
1893
|
+
|
|
1894
|
+
Covers: duplicate lock prevention, sent-registry cross-path dedup,
|
|
1895
|
+
disclosure boundary filtering end-to-end, active session visibility."
|
|
1896
|
+
```
|
|
1897
|
+
|
|
1898
|
+
---
|
|
1899
|
+
|
|
1900
|
+
## Task 9: Feature Flag and .env Update
|
|
1901
|
+
|
|
1902
|
+
**Files:**
|
|
1903
|
+
- Modify: `.env` (or `.env.example` if it exists)
|
|
1904
|
+
- Modify: `config/environment.yaml` (if feature flags live there)
|
|
1905
|
+
|
|
1906
|
+
- [ ] **Step 1: Add feature flag to .env**
|
|
1907
|
+
|
|
1908
|
+
Add the following line to `.env`:
|
|
1909
|
+
|
|
1910
|
+
```
|
|
1911
|
+
DAEMON_CONTEXT_COMPILER=1
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
- [ ] **Step 2: Add npm test script**
|
|
1915
|
+
|
|
1916
|
+
Add to `package.json` scripts:
|
|
1917
|
+
|
|
1918
|
+
```json
|
|
1919
|
+
"test:daemon": "node --test scripts/daemon/test-session-lock.mjs scripts/daemon/test-context-compiler.mjs scripts/daemon/test-integration.mjs"
|
|
1920
|
+
```
|
|
1921
|
+
|
|
1922
|
+
- [ ] **Step 3: Verify all tests pass with feature flag**
|
|
1923
|
+
|
|
1924
|
+
```bash
|
|
1925
|
+
DAEMON_CONTEXT_COMPILER=1 npm run test:daemon
|
|
1926
|
+
```
|
|
1927
|
+
|
|
1928
|
+
Expected: All tests pass.
|
|
1929
|
+
|
|
1930
|
+
- [ ] **Step 4: Commit**
|
|
1931
|
+
|
|
1932
|
+
```bash
|
|
1933
|
+
git add package.json .env
|
|
1934
|
+
git commit -m "feat: enable context compiler feature flag and add test script
|
|
1935
|
+
|
|
1936
|
+
Set DAEMON_CONTEXT_COMPILER=1 to activate the new context compilation
|
|
1937
|
+
pipeline. Add npm run test:daemon for running all daemon tests."
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
---
|
|
1941
|
+
|
|
1942
|
+
## Task 10: Add .gitignore Entries and Final Cleanup
|
|
1943
|
+
|
|
1944
|
+
**Files:**
|
|
1945
|
+
- Modify: `.gitignore`
|
|
1946
|
+
- Create: `state/sessions/locks/.gitkeep`
|
|
1947
|
+
|
|
1948
|
+
- [ ] **Step 1: Verify .gitignore has the right entries**
|
|
1949
|
+
|
|
1950
|
+
Ensure `.gitignore` contains:
|
|
1951
|
+
|
|
1952
|
+
```
|
|
1953
|
+
# Session state (ephemeral)
|
|
1954
|
+
state/sessions/locks/*.lock
|
|
1955
|
+
state/sessions/sent-registry.jsonl
|
|
1956
|
+
state/sessions/active.json.tmp
|
|
1957
|
+
|
|
1958
|
+
# Test directories
|
|
1959
|
+
.test-session-locks/
|
|
1960
|
+
.test-context-compiler/
|
|
1961
|
+
.test-integration/
|
|
1962
|
+
```
|
|
1963
|
+
|
|
1964
|
+
- [ ] **Step 2: Run full test suite one final time**
|
|
1965
|
+
|
|
1966
|
+
```bash
|
|
1967
|
+
npm run test:daemon
|
|
1968
|
+
```
|
|
1969
|
+
|
|
1970
|
+
Expected: All tests pass.
|
|
1971
|
+
|
|
1972
|
+
- [ ] **Step 3: Commit**
|
|
1973
|
+
|
|
1974
|
+
```bash
|
|
1975
|
+
git add .gitignore state/sessions/locks/.gitkeep
|
|
1976
|
+
git commit -m "chore: gitignore ephemeral session state and test directories"
|
|
1977
|
+
```
|
|
1978
|
+
|
|
1979
|
+
---
|
|
1980
|
+
|
|
1981
|
+
## Summary
|
|
1982
|
+
|
|
1983
|
+
| Task | What it builds | Key files |
|
|
1984
|
+
|------|---------------|-----------|
|
|
1985
|
+
| 1 | Directory structure | `state/sessions/` |
|
|
1986
|
+
| 2 | Session lock module + tests | `session-lock.mjs`, `test-session-lock.mjs` |
|
|
1987
|
+
| 3 | Context compiler + tests | `context-compiler.mjs`, `test-context-compiler.mjs` |
|
|
1988
|
+
| 4 | Daemon lock integration | `sophie-daemon.mjs` |
|
|
1989
|
+
| 5 | Dispatcher active.json + lock release | `dispatcher.mjs` |
|
|
1990
|
+
| 6 | Prompt builder context injection | `prompt-builder.mjs` |
|
|
1991
|
+
| 7 | Responder sent-registry integration | `responder.mjs` |
|
|
1992
|
+
| 8 | Integration tests | `test-integration.mjs` |
|
|
1993
|
+
| 9 | Feature flag + test script | `.env`, `package.json` |
|
|
1994
|
+
| 10 | Gitignore + cleanup | `.gitignore` |
|