@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,1493 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-send outbound correspondence validator.
|
|
3
|
+
|
|
4
|
+
Checks outbound messages for:
|
|
5
|
+
1. Relationship claims — "our project manager" must match confirmed status
|
|
6
|
+
2. Title/role accuracy — titles must match org chart / profiles
|
|
7
|
+
3. AI self-disclosure — Sophie must never reveal she is AI
|
|
8
|
+
4. In-person meeting self-scheduling — Sophie must not attend physical meetings
|
|
9
|
+
5. Unverified commitment claims — flag strong commitment language
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
echo "message body" | python3 validate-outbound.py --type email --recipient graham@kpm.com
|
|
13
|
+
python3 validate-outbound.py --message "message body" --type slack --recipient C099ABCDEF
|
|
14
|
+
|
|
15
|
+
Exit codes:
|
|
16
|
+
0 = clean (no blocking issues)
|
|
17
|
+
1 = issues found (at least one "block" severity issue)
|
|
18
|
+
|
|
19
|
+
Outputs JSON to stdout:
|
|
20
|
+
{"status": "clean"|"issues_found", "issues": [...], "metadata": {...}}
|
|
21
|
+
|
|
22
|
+
Performance target: < 1 second (no API calls — file reads only).
|
|
23
|
+
|
|
24
|
+
Created: 2026-04-03 — triggered by ib-20260403-008 (Graham/KPM incident)
|
|
25
|
+
"""
|
|
26
|
+
import argparse
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from datetime import datetime, timezone
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
# Import disclosure assessment module (Information Barrier Layer 2)
|
|
36
|
+
_da_module = None
|
|
37
|
+
try:
|
|
38
|
+
import importlib.util as _da_ilu
|
|
39
|
+
_da_path = Path(__file__).resolve().parent / "disclosure_assessment.py"
|
|
40
|
+
if _da_path.exists():
|
|
41
|
+
_da_spec = _da_ilu.spec_from_file_location("disclosure_assessment", str(_da_path))
|
|
42
|
+
_da_mod = _da_ilu.module_from_spec(_da_spec)
|
|
43
|
+
_da_spec.loader.exec_module(_da_mod)
|
|
44
|
+
_da_module = _da_mod
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# Resolve paths relative to the repo root
|
|
49
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
50
|
+
REPO_ROOT = SCRIPT_DIR.parent
|
|
51
|
+
|
|
52
|
+
# Data source paths
|
|
53
|
+
CONTACTS_PATH = REPO_ROOT / "config" / "contacts.yaml"
|
|
54
|
+
ORG_CHART_PATH = REPO_ROOT / "knowledge" / "entities" / "org-chart.md"
|
|
55
|
+
USER_PROFILES_DIR = REPO_ROOT / "memory" / "profiles" / "users"
|
|
56
|
+
VALIDATION_RULES_PATH = REPO_ROOT / "config" / "validation-rules.yaml"
|
|
57
|
+
ENTITY_INDEX_PATH = REPO_ROOT / "memory" / "indexes" / "entity-relationships.yaml"
|
|
58
|
+
AUDIT_LOG_DIR = REPO_ROOT / "logs" / "audit"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ─────────────────────────────────────────────
|
|
62
|
+
# Nested YAML parser (handles maps, lists, and scalar values)
|
|
63
|
+
# Replaces the original flat key-value parser that missed nested fields
|
|
64
|
+
# like relationship.type and relationship.status (Graham incident, G4)
|
|
65
|
+
# ─────────────────────────────────────────────
|
|
66
|
+
def _parse_yaml_simple(text):
|
|
67
|
+
"""Parse YAML text into nested dicts/lists.
|
|
68
|
+
|
|
69
|
+
Handles:
|
|
70
|
+
- Nested mappings (indented key: value blocks)
|
|
71
|
+
- Lists (- item and - key: value)
|
|
72
|
+
- Quoted and unquoted scalar values
|
|
73
|
+
- Comments and blank lines
|
|
74
|
+
|
|
75
|
+
Returns a dict (possibly nested). Flat profiles still work —
|
|
76
|
+
a flat file returns a flat dict, same as before.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
import yaml as _yaml
|
|
80
|
+
return _yaml.safe_load(text) or {}
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
lines = text.split('\n')
|
|
85
|
+
return _parse_yaml_block(lines, 0, 0)[0]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _parse_yaml_block(lines, start, base_indent):
|
|
89
|
+
"""Recursively parse a YAML block starting at *start* with *base_indent*.
|
|
90
|
+
|
|
91
|
+
Returns (parsed_dict, next_line_index).
|
|
92
|
+
"""
|
|
93
|
+
result = {}
|
|
94
|
+
i = start
|
|
95
|
+
while i < len(lines):
|
|
96
|
+
raw = lines[i]
|
|
97
|
+
|
|
98
|
+
# Skip blank lines and comments
|
|
99
|
+
stripped = raw.lstrip()
|
|
100
|
+
if not stripped or stripped.startswith('#'):
|
|
101
|
+
i += 1
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Measure indentation
|
|
105
|
+
indent = len(raw) - len(raw.lstrip())
|
|
106
|
+
|
|
107
|
+
# If we've dedented past our block, stop
|
|
108
|
+
if indent < base_indent and i > start:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
# Skip lines that are deeper than expected (handled recursively)
|
|
112
|
+
if indent > base_indent and i > start:
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
# ── List item ────────────────────────────────
|
|
116
|
+
if stripped.startswith('- '):
|
|
117
|
+
# We encountered a list where we expected a mapping value.
|
|
118
|
+
# Parse the full list and return it as a special case —
|
|
119
|
+
# caller will assign it as the value.
|
|
120
|
+
lst, i = _parse_yaml_list(lines, i, indent)
|
|
121
|
+
# If this list appears at the top level inside a mapping block,
|
|
122
|
+
# we shouldn't be here — this branch is for when a key's value
|
|
123
|
+
# is a list. Return what we have so far and let the caller handle it.
|
|
124
|
+
if not result:
|
|
125
|
+
return lst, i
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
# ── Mapping entry (key: ...) ─────────────────
|
|
129
|
+
if ':' in stripped and not stripped.startswith('-'):
|
|
130
|
+
key, _, rest = stripped.partition(':')
|
|
131
|
+
key = key.strip()
|
|
132
|
+
rest = rest.strip()
|
|
133
|
+
|
|
134
|
+
if rest:
|
|
135
|
+
# Inline value — scalar
|
|
136
|
+
result[key] = _yaml_scalar(rest)
|
|
137
|
+
i += 1
|
|
138
|
+
else:
|
|
139
|
+
# Block value — could be nested map or list
|
|
140
|
+
# Peek at next meaningful line to determine child indent
|
|
141
|
+
child_indent, child_idx = _next_content_line(lines, i + 1)
|
|
142
|
+
if child_indent is not None and child_indent > indent:
|
|
143
|
+
# Check if child is a list
|
|
144
|
+
child_stripped = lines[child_idx].lstrip()
|
|
145
|
+
if child_stripped.startswith('- '):
|
|
146
|
+
value, i = _parse_yaml_list(lines, child_idx, child_indent)
|
|
147
|
+
else:
|
|
148
|
+
value, i = _parse_yaml_block(lines, child_idx, child_indent)
|
|
149
|
+
result[key] = value
|
|
150
|
+
else:
|
|
151
|
+
# Empty value
|
|
152
|
+
result[key] = None
|
|
153
|
+
i += 1
|
|
154
|
+
else:
|
|
155
|
+
i += 1
|
|
156
|
+
|
|
157
|
+
return result, i
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _parse_yaml_list(lines, start, base_indent):
|
|
161
|
+
"""Parse a YAML list starting at *start*.
|
|
162
|
+
|
|
163
|
+
Returns (list, next_line_index).
|
|
164
|
+
"""
|
|
165
|
+
result = []
|
|
166
|
+
i = start
|
|
167
|
+
while i < len(lines):
|
|
168
|
+
raw = lines[i]
|
|
169
|
+
stripped = raw.lstrip()
|
|
170
|
+
if not stripped or stripped.startswith('#'):
|
|
171
|
+
i += 1
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
indent = len(raw) - len(raw.lstrip())
|
|
175
|
+
if indent < base_indent:
|
|
176
|
+
break
|
|
177
|
+
if indent > base_indent:
|
|
178
|
+
# Continuation of previous list item (nested block)
|
|
179
|
+
# This is handled inside the item parsing below
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
if not stripped.startswith('- '):
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
item_content = stripped[2:].strip()
|
|
186
|
+
|
|
187
|
+
if ':' in item_content and not item_content.startswith('"') and not item_content.startswith("'"):
|
|
188
|
+
# Mapping-style list item: - key: value [possibly with more keys below]
|
|
189
|
+
k, _, v = item_content.partition(':')
|
|
190
|
+
k = k.strip()
|
|
191
|
+
v = v.strip()
|
|
192
|
+
item_dict = {k: _yaml_scalar(v) if v else None}
|
|
193
|
+
|
|
194
|
+
# Check for continuation keys at deeper indent
|
|
195
|
+
child_indent, child_idx = _next_content_line(lines, i + 1)
|
|
196
|
+
if child_indent is not None and child_indent > indent:
|
|
197
|
+
extra, i = _parse_yaml_block(lines, child_idx, child_indent)
|
|
198
|
+
if isinstance(extra, dict):
|
|
199
|
+
item_dict.update(extra)
|
|
200
|
+
else:
|
|
201
|
+
i = i # no change
|
|
202
|
+
else:
|
|
203
|
+
i += 1
|
|
204
|
+
|
|
205
|
+
result.append(item_dict)
|
|
206
|
+
else:
|
|
207
|
+
# Simple scalar list item
|
|
208
|
+
result.append(_yaml_scalar(item_content))
|
|
209
|
+
i += 1
|
|
210
|
+
|
|
211
|
+
return result, i
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _next_content_line(lines, start):
|
|
215
|
+
"""Find the next non-blank, non-comment line from *start*.
|
|
216
|
+
|
|
217
|
+
Returns (indent, index) or (None, None).
|
|
218
|
+
"""
|
|
219
|
+
i = start
|
|
220
|
+
while i < len(lines):
|
|
221
|
+
stripped = lines[i].lstrip()
|
|
222
|
+
if stripped and not stripped.startswith('#'):
|
|
223
|
+
indent = len(lines[i]) - len(lines[i].lstrip())
|
|
224
|
+
return indent, i
|
|
225
|
+
i += 1
|
|
226
|
+
return None, None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _yaml_scalar(text):
|
|
230
|
+
"""Convert a YAML scalar string to an appropriate Python type."""
|
|
231
|
+
if not text:
|
|
232
|
+
return None
|
|
233
|
+
# Remove surrounding quotes
|
|
234
|
+
if (text.startswith('"') and text.endswith('"')) or \
|
|
235
|
+
(text.startswith("'") and text.endswith("'")):
|
|
236
|
+
return text[1:-1]
|
|
237
|
+
# Remove inline comments
|
|
238
|
+
if ' #' in text:
|
|
239
|
+
text = text[:text.index(' #')].strip()
|
|
240
|
+
elif '\t#' in text:
|
|
241
|
+
text = text[:text.index('\t#')].strip()
|
|
242
|
+
# Boolean
|
|
243
|
+
if text.lower() in ('true', 'yes'):
|
|
244
|
+
return True
|
|
245
|
+
if text.lower() in ('false', 'no'):
|
|
246
|
+
return False
|
|
247
|
+
# Null
|
|
248
|
+
if text.lower() in ('null', '~', ''):
|
|
249
|
+
return None
|
|
250
|
+
# Numeric
|
|
251
|
+
try:
|
|
252
|
+
return int(text)
|
|
253
|
+
except ValueError:
|
|
254
|
+
pass
|
|
255
|
+
try:
|
|
256
|
+
return float(text)
|
|
257
|
+
except ValueError:
|
|
258
|
+
pass
|
|
259
|
+
# Strip quotes that may remain
|
|
260
|
+
return text.strip('"').strip("'")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _nested_get(data, *keys):
|
|
264
|
+
"""Safely traverse nested dicts. Returns None if any key is missing."""
|
|
265
|
+
current = data
|
|
266
|
+
for key in keys:
|
|
267
|
+
if isinstance(current, dict):
|
|
268
|
+
current = current.get(key)
|
|
269
|
+
else:
|
|
270
|
+
return None
|
|
271
|
+
return current
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ─────────────────────────────────────────────
|
|
275
|
+
# Data Loading — cached per process
|
|
276
|
+
# ─────────────────────────────────────────────
|
|
277
|
+
_contacts_cache = None
|
|
278
|
+
_profiles_cache = None
|
|
279
|
+
_org_titles_cache = None
|
|
280
|
+
_entity_index_cache = None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def load_entity_index():
|
|
284
|
+
"""Load entity-relationships.yaml as the primary entity/relationship index.
|
|
285
|
+
|
|
286
|
+
Returns dict with keys:
|
|
287
|
+
- 'entities': list of entity dicts (people, companies)
|
|
288
|
+
- 'relationships': list of relationship dicts
|
|
289
|
+
- 'by_name': dict mapping lowercase name/alias -> entity dict
|
|
290
|
+
- 'rels_by_subject': dict mapping entity id -> list of relationship dicts
|
|
291
|
+
|
|
292
|
+
Gracefully returns empty structure if file is missing or malformed.
|
|
293
|
+
"""
|
|
294
|
+
global _entity_index_cache
|
|
295
|
+
if _entity_index_cache is not None:
|
|
296
|
+
return _entity_index_cache
|
|
297
|
+
|
|
298
|
+
empty = {'entities': [], 'relationships': [], 'by_name': {}, 'rels_by_subject': {}}
|
|
299
|
+
|
|
300
|
+
if not ENTITY_INDEX_PATH.exists():
|
|
301
|
+
_entity_index_cache = empty
|
|
302
|
+
return _entity_index_cache
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
text = ENTITY_INDEX_PATH.read_text(encoding='utf-8')
|
|
306
|
+
data = _parse_yaml_simple(text)
|
|
307
|
+
|
|
308
|
+
if not isinstance(data, dict):
|
|
309
|
+
_entity_index_cache = empty
|
|
310
|
+
return _entity_index_cache
|
|
311
|
+
|
|
312
|
+
entities = data.get('entities', []) or []
|
|
313
|
+
relationships = data.get('relationships', []) or []
|
|
314
|
+
|
|
315
|
+
# Build name lookup: lowercase name/alias -> entity dict
|
|
316
|
+
by_name = {}
|
|
317
|
+
for entity in entities:
|
|
318
|
+
if not isinstance(entity, dict):
|
|
319
|
+
continue
|
|
320
|
+
# Index by full name
|
|
321
|
+
name = entity.get('name', '')
|
|
322
|
+
if name and isinstance(name, str):
|
|
323
|
+
by_name[name.lower()] = entity
|
|
324
|
+
# Index by first name + last name (short form)
|
|
325
|
+
parts = name.split()
|
|
326
|
+
if len(parts) >= 2:
|
|
327
|
+
short = f"{parts[0]} {parts[-1]}"
|
|
328
|
+
by_name[short.lower()] = entity
|
|
329
|
+
# Index by first name alone (for single-name lookups)
|
|
330
|
+
if len(parts) >= 1:
|
|
331
|
+
first = parts[0].lower()
|
|
332
|
+
# Only index first name if it's not too common / ambiguous
|
|
333
|
+
# We allow it but give full name priority in lookup_entity
|
|
334
|
+
if first not in by_name:
|
|
335
|
+
by_name[first] = entity
|
|
336
|
+
|
|
337
|
+
# Index by aliases
|
|
338
|
+
aliases = entity.get('aliases', []) or []
|
|
339
|
+
if isinstance(aliases, list):
|
|
340
|
+
for alias in aliases:
|
|
341
|
+
if alias and isinstance(alias, str):
|
|
342
|
+
by_name[alias.lower()] = entity
|
|
343
|
+
|
|
344
|
+
# Build relationship lookup by subject
|
|
345
|
+
rels_by_subject = {}
|
|
346
|
+
for rel in relationships:
|
|
347
|
+
if not isinstance(rel, dict):
|
|
348
|
+
continue
|
|
349
|
+
subj = rel.get('subject', '')
|
|
350
|
+
if subj:
|
|
351
|
+
rels_by_subject.setdefault(subj, []).append(rel)
|
|
352
|
+
|
|
353
|
+
_entity_index_cache = {
|
|
354
|
+
'entities': entities,
|
|
355
|
+
'relationships': relationships,
|
|
356
|
+
'by_name': by_name,
|
|
357
|
+
'rels_by_subject': rels_by_subject,
|
|
358
|
+
}
|
|
359
|
+
except Exception:
|
|
360
|
+
_entity_index_cache = empty
|
|
361
|
+
|
|
362
|
+
return _entity_index_cache
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def lookup_entity(name, entity_index=None):
|
|
366
|
+
"""Look up a person or company by name in the entity index.
|
|
367
|
+
|
|
368
|
+
Performs fuzzy matching: checks full name, first+last, first name, aliases.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
name: The name to look up (case-insensitive)
|
|
372
|
+
entity_index: The loaded entity index (from load_entity_index).
|
|
373
|
+
If None, loads it.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
dict with keys:
|
|
377
|
+
- 'entity': the entity dict (or None)
|
|
378
|
+
- 'relationships_to_adaptic': list of relationship dicts describing
|
|
379
|
+
this entity's relationship to Adaptic/its entities
|
|
380
|
+
- 'is_internal': bool — True if entity is employed by an Adaptic entity
|
|
381
|
+
- 'relationship_type': str — summary classification
|
|
382
|
+
('internal', 'vendor_candidate', 'external_vendor', 'external_advisor',
|
|
383
|
+
'investor', 'regulator', 'external', 'unknown')
|
|
384
|
+
Returns None if entity not found.
|
|
385
|
+
"""
|
|
386
|
+
if entity_index is None:
|
|
387
|
+
entity_index = load_entity_index()
|
|
388
|
+
|
|
389
|
+
by_name = entity_index.get('by_name', {})
|
|
390
|
+
rels_by_subject = entity_index.get('rels_by_subject', {})
|
|
391
|
+
|
|
392
|
+
name_lower = name.lower().strip()
|
|
393
|
+
if not name_lower:
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
# Try exact match first, then partial
|
|
397
|
+
entity = by_name.get(name_lower)
|
|
398
|
+
|
|
399
|
+
if entity is None:
|
|
400
|
+
# Try first+last combination from input
|
|
401
|
+
parts = name_lower.split()
|
|
402
|
+
if len(parts) >= 2:
|
|
403
|
+
short = f"{parts[0]} {parts[-1]}"
|
|
404
|
+
entity = by_name.get(short)
|
|
405
|
+
if entity is None and len(parts) == 1:
|
|
406
|
+
# Single name — try it (already indexed by first name above)
|
|
407
|
+
entity = by_name.get(parts[0])
|
|
408
|
+
|
|
409
|
+
if entity is None:
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
entity_id = entity.get('id', '')
|
|
413
|
+
|
|
414
|
+
# Collect relationships where this entity is the subject
|
|
415
|
+
subject_rels = rels_by_subject.get(entity_id, [])
|
|
416
|
+
|
|
417
|
+
# Determine relationship to Adaptic
|
|
418
|
+
adaptic_ids = {
|
|
419
|
+
'company:adaptic-group', 'company:adaptic-holdings',
|
|
420
|
+
'company:adaptic-am-difc', 'company:adaptic-technologies',
|
|
421
|
+
'company:adaptic-group-investments', 'company:adaptic-investments',
|
|
422
|
+
'company:adaptic-inc', 'company:adaptic-ai-uk',
|
|
423
|
+
'company:adaptic-am-uk', 'company:adaptic-am-cayman',
|
|
424
|
+
'company:adaptic-am-asia', 'company:adaptic-asia',
|
|
425
|
+
'company:adaptic-am-eu', 'company:adaptic-am-us',
|
|
426
|
+
'company:adaptic-am-ksa', 'company:adaptic-ksa',
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
rels_to_adaptic = []
|
|
430
|
+
is_internal = False
|
|
431
|
+
rel_type = 'unknown'
|
|
432
|
+
|
|
433
|
+
for rel in subject_rels:
|
|
434
|
+
obj = rel.get('object', '')
|
|
435
|
+
pred = rel.get('predicate', '')
|
|
436
|
+
|
|
437
|
+
if obj in adaptic_ids or obj.startswith('company:adaptic'):
|
|
438
|
+
rels_to_adaptic.append(rel)
|
|
439
|
+
|
|
440
|
+
if pred in ('employed_by', 'directs'):
|
|
441
|
+
is_internal = True
|
|
442
|
+
rel_type = 'internal'
|
|
443
|
+
elif pred == 'vendor_candidate_for' and rel_type != 'internal':
|
|
444
|
+
rel_type = 'vendor_candidate'
|
|
445
|
+
elif pred == 'vendor_for' and rel_type not in ('internal', 'vendor_candidate'):
|
|
446
|
+
rel_type = 'external_vendor'
|
|
447
|
+
elif pred == 'advises' and rel_type not in ('internal', 'vendor_candidate', 'external_vendor'):
|
|
448
|
+
rel_type = 'external_advisor'
|
|
449
|
+
elif pred == 'invests_in' and rel_type not in ('internal',):
|
|
450
|
+
rel_type = 'investor'
|
|
451
|
+
elif pred == 'regulates' and rel_type not in ('internal',):
|
|
452
|
+
rel_type = 'regulator'
|
|
453
|
+
|
|
454
|
+
# Check if entity is employed by a non-Adaptic company (external person)
|
|
455
|
+
if rel_type == 'unknown':
|
|
456
|
+
for rel in subject_rels:
|
|
457
|
+
pred = rel.get('predicate', '')
|
|
458
|
+
if pred == 'employed_by':
|
|
459
|
+
rel_type = 'external'
|
|
460
|
+
break
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
'entity': entity,
|
|
464
|
+
'relationships_to_adaptic': rels_to_adaptic,
|
|
465
|
+
'is_internal': is_internal,
|
|
466
|
+
'relationship_type': rel_type,
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def load_contacts():
|
|
471
|
+
"""Load confirmed contacts from config/contacts.yaml.
|
|
472
|
+
|
|
473
|
+
Returns dict: lowercase_name -> {role, classification, organisation}
|
|
474
|
+
"""
|
|
475
|
+
global _contacts_cache
|
|
476
|
+
if _contacts_cache is not None:
|
|
477
|
+
return _contacts_cache
|
|
478
|
+
|
|
479
|
+
_contacts_cache = {}
|
|
480
|
+
if not CONTACTS_PATH.exists():
|
|
481
|
+
return _contacts_cache
|
|
482
|
+
|
|
483
|
+
text = CONTACTS_PATH.read_text(encoding='utf-8')
|
|
484
|
+
|
|
485
|
+
# Simple parser: extract contact blocks
|
|
486
|
+
current_contact = {}
|
|
487
|
+
for line in text.split('\n'):
|
|
488
|
+
stripped = line.strip()
|
|
489
|
+
if stripped.startswith('- name:'):
|
|
490
|
+
if current_contact.get('name'):
|
|
491
|
+
name_lower = current_contact['name'].lower()
|
|
492
|
+
_contacts_cache[name_lower] = current_contact
|
|
493
|
+
current_contact = {'name': stripped.split(':', 1)[1].strip()}
|
|
494
|
+
elif stripped.startswith('role:') and current_contact:
|
|
495
|
+
current_contact['role'] = stripped.split(':', 1)[1].strip()
|
|
496
|
+
elif stripped.startswith('classification:') and current_contact:
|
|
497
|
+
current_contact['classification'] = stripped.split(':', 1)[1].strip()
|
|
498
|
+
elif stripped.startswith('organisation:') and current_contact:
|
|
499
|
+
current_contact['organisation'] = stripped.split(':', 1)[1].strip()
|
|
500
|
+
|
|
501
|
+
if current_contact.get('name'):
|
|
502
|
+
name_lower = current_contact['name'].lower()
|
|
503
|
+
_contacts_cache[name_lower] = current_contact
|
|
504
|
+
|
|
505
|
+
return _contacts_cache
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def load_user_profiles():
|
|
509
|
+
"""Load all user profiles from memory/profiles/users/*.yaml.
|
|
510
|
+
|
|
511
|
+
Returns dict: lowercase_name -> {name, title, company, relationship_type,
|
|
512
|
+
relationship_status, classification}
|
|
513
|
+
"""
|
|
514
|
+
global _profiles_cache
|
|
515
|
+
if _profiles_cache is not None:
|
|
516
|
+
return _profiles_cache
|
|
517
|
+
|
|
518
|
+
_profiles_cache = {}
|
|
519
|
+
if not USER_PROFILES_DIR.exists():
|
|
520
|
+
return _profiles_cache
|
|
521
|
+
|
|
522
|
+
for yaml_file in USER_PROFILES_DIR.glob('*.yaml'):
|
|
523
|
+
try:
|
|
524
|
+
text = yaml_file.read_text(encoding='utf-8')
|
|
525
|
+
data = _parse_yaml_simple(text)
|
|
526
|
+
|
|
527
|
+
# Navigate nested structure — profiles may have top-level keys
|
|
528
|
+
# (flat) or nested sections like identity.name, relationship.type
|
|
529
|
+
identity = data.get('identity', {}) if isinstance(data.get('identity'), dict) else {}
|
|
530
|
+
relationship = data.get('relationship', {}) if isinstance(data.get('relationship'), dict) else {}
|
|
531
|
+
|
|
532
|
+
# Name: try identity.name, then top-level name, then filename
|
|
533
|
+
name = _nested_get(data, 'identity', 'name') or data.get('name', '')
|
|
534
|
+
if not name or not isinstance(name, str):
|
|
535
|
+
name = yaml_file.stem.replace('-', ' ').title()
|
|
536
|
+
|
|
537
|
+
# Title: identity.title or top-level
|
|
538
|
+
title = _nested_get(data, 'identity', 'title') or data.get('title', '')
|
|
539
|
+
|
|
540
|
+
# Company: identity.company or top-level
|
|
541
|
+
company = _nested_get(data, 'identity', 'company') or data.get('company', '')
|
|
542
|
+
|
|
543
|
+
# Relationship type: relationship.type, or flat relationship_to_adaptic, or top-level type
|
|
544
|
+
rel_type = (
|
|
545
|
+
_nested_get(data, 'relationship', 'type')
|
|
546
|
+
or data.get('relationship_to_adaptic', '')
|
|
547
|
+
or data.get('type', '')
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Relationship status: relationship.status, or flat relationship_status, or top-level status
|
|
551
|
+
rel_status = (
|
|
552
|
+
_nested_get(data, 'relationship', 'status')
|
|
553
|
+
or data.get('relationship_status', '')
|
|
554
|
+
or data.get('status', '')
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Relationship category (for richer matching)
|
|
558
|
+
rel_category = _nested_get(data, 'relationship', 'category') or data.get('category', '')
|
|
559
|
+
|
|
560
|
+
# Classification & privilege — may be top-level or nested
|
|
561
|
+
classification = data.get('classification', '')
|
|
562
|
+
privilege_level = data.get('privilege_level', '')
|
|
563
|
+
|
|
564
|
+
profile = {
|
|
565
|
+
'name': name if isinstance(name, str) else str(name),
|
|
566
|
+
'title': title if isinstance(title, str) else str(title) if title else '',
|
|
567
|
+
'company': company if isinstance(company, str) else str(company) if company else '',
|
|
568
|
+
'relationship_type': rel_type if isinstance(rel_type, str) else str(rel_type) if rel_type else '',
|
|
569
|
+
'relationship_status': rel_status if isinstance(rel_status, str) else str(rel_status) if rel_status else '',
|
|
570
|
+
'relationship_category': rel_category if isinstance(rel_category, str) else str(rel_category) if rel_category else '',
|
|
571
|
+
'classification': classification if isinstance(classification, str) else str(classification) if classification else '',
|
|
572
|
+
'privilege_level': privilege_level if isinstance(privilege_level, str) else str(privilege_level) if privilege_level else '',
|
|
573
|
+
}
|
|
574
|
+
_profiles_cache[name.lower()] = profile
|
|
575
|
+
except Exception:
|
|
576
|
+
continue
|
|
577
|
+
|
|
578
|
+
return _profiles_cache
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def load_org_titles():
|
|
582
|
+
"""Load confirmed titles from knowledge/entities/org-chart.md.
|
|
583
|
+
|
|
584
|
+
Returns dict: lowercase_name -> confirmed_title
|
|
585
|
+
"""
|
|
586
|
+
global _org_titles_cache
|
|
587
|
+
if _org_titles_cache is not None:
|
|
588
|
+
return _org_titles_cache
|
|
589
|
+
|
|
590
|
+
_org_titles_cache = {}
|
|
591
|
+
if not ORG_CHART_PATH.exists():
|
|
592
|
+
return _org_titles_cache
|
|
593
|
+
|
|
594
|
+
text = ORG_CHART_PATH.read_text(encoding='utf-8')
|
|
595
|
+
|
|
596
|
+
# Parse patterns like "### Name — Title" and "Name — Title" from tree
|
|
597
|
+
patterns = [
|
|
598
|
+
r'###\s+(.+?)\s+[—\-]+\s+(.+)', # ### Mehran Granfar — CEO / Founder / UBO
|
|
599
|
+
r'[├└]\s*(.+?)\s+[—\-]+\s+(.+)', # ├── Hootan Yazhari — CFO / CIO
|
|
600
|
+
]
|
|
601
|
+
for pattern in patterns:
|
|
602
|
+
for match in re.finditer(pattern, text):
|
|
603
|
+
name = match.group(1).strip()
|
|
604
|
+
title = match.group(2).strip()
|
|
605
|
+
# Clean up name (remove markdown, extra chars)
|
|
606
|
+
name = re.sub(r'[\*\#]', '', name).strip()
|
|
607
|
+
# Remove full name suffixes for matching
|
|
608
|
+
first_last = name.split()
|
|
609
|
+
if len(first_last) >= 2:
|
|
610
|
+
short_name = f"{first_last[0]} {first_last[-1]}"
|
|
611
|
+
_org_titles_cache[short_name.lower()] = title
|
|
612
|
+
_org_titles_cache[name.lower()] = title
|
|
613
|
+
|
|
614
|
+
return _org_titles_cache
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# ─────────────────────────────────────────────
|
|
618
|
+
# Validation Checks
|
|
619
|
+
# ─────────────────────────────────────────────
|
|
620
|
+
|
|
621
|
+
def check_relationship_claims(message):
|
|
622
|
+
"""Check for unverified relationship/affiliation claims.
|
|
623
|
+
|
|
624
|
+
Uses entity-relationships.yaml as PRIMARY lookup source.
|
|
625
|
+
Falls back to user profiles if entity not found in the index.
|
|
626
|
+
|
|
627
|
+
Returns list of issue dicts.
|
|
628
|
+
"""
|
|
629
|
+
issues = []
|
|
630
|
+
contacts = load_contacts()
|
|
631
|
+
profiles = load_user_profiles()
|
|
632
|
+
entity_index = load_entity_index()
|
|
633
|
+
message_lower = message.lower()
|
|
634
|
+
|
|
635
|
+
# Confirmed statuses — people with these can be called "our X" (profile fallback)
|
|
636
|
+
confirmed_statuses = {
|
|
637
|
+
'confirmed', 'confirmed_employee', 'confirmed_contractor', 'internal',
|
|
638
|
+
'confirmed_advisor', 'confirmed_partner',
|
|
639
|
+
}
|
|
640
|
+
confirmed_classifications = {
|
|
641
|
+
'internal-ceo', 'internal-executive', 'internal-team',
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
# Entity index relationship types that permit possessive claims ("our X")
|
|
645
|
+
entity_internal_types = {'internal'}
|
|
646
|
+
|
|
647
|
+
# Entity index relationship types that BLOCK possessive claims
|
|
648
|
+
entity_external_types = {
|
|
649
|
+
'vendor_candidate', 'external_vendor', 'external_advisor',
|
|
650
|
+
'investor', 'regulator', 'external', 'unknown',
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
# Relationship claim patterns
|
|
654
|
+
claim_patterns = [
|
|
655
|
+
(r'\b(our|my|the company\'s|Adaptic\'s|the team\'s)\s+'
|
|
656
|
+
r'(project manager|PM|consultant|advisor|counsel|lawyer|developer|'
|
|
657
|
+
r'engineer|designer|analyst|hire|employee|team member|colleague|'
|
|
658
|
+
r'staff member|contractor|associate|partner|director|officer|'
|
|
659
|
+
r'head of\s+\w+|lead\s*\w*|manager|VP|chief\s+\w+)',
|
|
660
|
+
"Possessive role claim"),
|
|
661
|
+
(r'\bwe\'ve\s+(hired|appointed|engaged|retained|onboarded|brought on|contracted)\s+',
|
|
662
|
+
"Hiring/engagement claim"),
|
|
663
|
+
(r'\b(has joined|is joining|will be joining|recently joined|now works|'
|
|
664
|
+
r'working with us|part of our team|on our team|member of our)\b',
|
|
665
|
+
"Team membership claim"),
|
|
666
|
+
(r'\b(is our|are our|will be our)\s+\w+',
|
|
667
|
+
"Direct role attribution"),
|
|
668
|
+
]
|
|
669
|
+
|
|
670
|
+
for pattern, description in claim_patterns:
|
|
671
|
+
for match in re.finditer(pattern, message, re.IGNORECASE):
|
|
672
|
+
matched_text = match.group(0)
|
|
673
|
+
# Extract context — look for a name near the match
|
|
674
|
+
start = max(0, match.start() - 80)
|
|
675
|
+
end = min(len(message), match.end() + 80)
|
|
676
|
+
context_window = message[start:end]
|
|
677
|
+
|
|
678
|
+
# Check if any known person is referenced nearby
|
|
679
|
+
flagged = False
|
|
680
|
+
flagged_person = None
|
|
681
|
+
entity_lookup_used = False
|
|
682
|
+
|
|
683
|
+
# ── PRIMARY: Entity index lookup ─────────────────
|
|
684
|
+
# Scan the context window for any entity name from the index
|
|
685
|
+
by_name = entity_index.get('by_name', {})
|
|
686
|
+
for entity_name_lower, entity in by_name.items():
|
|
687
|
+
# Only check person entities, skip companies
|
|
688
|
+
if entity.get('type') != 'person':
|
|
689
|
+
continue
|
|
690
|
+
|
|
691
|
+
# Check if this entity's name appears in the context
|
|
692
|
+
name_in_context = False
|
|
693
|
+
# Check full name
|
|
694
|
+
full_name = entity.get('name', '')
|
|
695
|
+
if full_name and full_name.lower() in context_window.lower():
|
|
696
|
+
name_in_context = True
|
|
697
|
+
# Check aliases
|
|
698
|
+
if not name_in_context:
|
|
699
|
+
for alias in (entity.get('aliases', []) or []):
|
|
700
|
+
if alias and len(alias) > 2 and alias.lower() in context_window.lower():
|
|
701
|
+
name_in_context = True
|
|
702
|
+
break
|
|
703
|
+
# Check name parts (first name, last name)
|
|
704
|
+
if not name_in_context and full_name:
|
|
705
|
+
parts = full_name.split()
|
|
706
|
+
for part in parts:
|
|
707
|
+
if len(part) > 2 and part.lower() in context_window.lower():
|
|
708
|
+
name_in_context = True
|
|
709
|
+
break
|
|
710
|
+
|
|
711
|
+
if not name_in_context:
|
|
712
|
+
continue
|
|
713
|
+
|
|
714
|
+
# Found a person near the claim — look up their relationship
|
|
715
|
+
result = lookup_entity(full_name, entity_index)
|
|
716
|
+
if result:
|
|
717
|
+
entity_lookup_used = True
|
|
718
|
+
rel_type = result.get('relationship_type', 'unknown')
|
|
719
|
+
|
|
720
|
+
if rel_type in entity_external_types:
|
|
721
|
+
# BLOCK — external person referenced with possessive claim
|
|
722
|
+
flagged = True
|
|
723
|
+
flagged_person = full_name
|
|
724
|
+
# Build enriched suggestion from entity data
|
|
725
|
+
entity_role = entity.get('primary_role', '')
|
|
726
|
+
entity_company = entity.get('primary_company', '')
|
|
727
|
+
suggestion_parts = [
|
|
728
|
+
f"{full_name} is classified as '{rel_type}' in the entity index."
|
|
729
|
+
]
|
|
730
|
+
if entity_role:
|
|
731
|
+
suggestion_parts.append(f"Their role is: {entity_role}.")
|
|
732
|
+
if entity_company:
|
|
733
|
+
suggestion_parts.append(f"Their company: {entity_company}.")
|
|
734
|
+
suggestion_parts.append(
|
|
735
|
+
"Do not use possessive language ('our', 'my') for external parties."
|
|
736
|
+
)
|
|
737
|
+
break # Found a flagged person, stop scanning
|
|
738
|
+
|
|
739
|
+
elif rel_type in entity_internal_types:
|
|
740
|
+
# ALLOW — confirmed internal person
|
|
741
|
+
# Don't flag, and mark that we found a confirmed person
|
|
742
|
+
flagged = False
|
|
743
|
+
flagged_person = None
|
|
744
|
+
entity_lookup_used = True
|
|
745
|
+
break
|
|
746
|
+
|
|
747
|
+
# ── FALLBACK: Profile-based lookup (if entity index didn't resolve) ──
|
|
748
|
+
if not entity_lookup_used:
|
|
749
|
+
for name_lower, profile in profiles.items():
|
|
750
|
+
rel_status = profile.get('relationship_status', '')
|
|
751
|
+
rel_type = profile.get('relationship_type', '')
|
|
752
|
+
priv_level = profile.get('privilege_level', '')
|
|
753
|
+
classification = profile.get('classification', '')
|
|
754
|
+
|
|
755
|
+
# Check if this person is in the context window
|
|
756
|
+
name_parts = name_lower.split()
|
|
757
|
+
name_in_context = False
|
|
758
|
+
for part in name_parts:
|
|
759
|
+
if len(part) > 2 and part.lower() in context_window.lower():
|
|
760
|
+
name_in_context = True
|
|
761
|
+
break
|
|
762
|
+
|
|
763
|
+
if not name_in_context:
|
|
764
|
+
continue
|
|
765
|
+
|
|
766
|
+
# Is this person confirmed?
|
|
767
|
+
is_confirmed = (
|
|
768
|
+
rel_status in confirmed_statuses or
|
|
769
|
+
classification in confirmed_classifications or
|
|
770
|
+
priv_level == 'internal'
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
# Also check contacts registry
|
|
774
|
+
contact = contacts.get(name_lower, {})
|
|
775
|
+
contact_class = contact.get('classification', '')
|
|
776
|
+
if contact_class in confirmed_classifications:
|
|
777
|
+
is_confirmed = True
|
|
778
|
+
|
|
779
|
+
if not is_confirmed:
|
|
780
|
+
flagged = True
|
|
781
|
+
flagged_person = profile.get('name', name_lower)
|
|
782
|
+
break
|
|
783
|
+
|
|
784
|
+
# Also flag if no person is identified but claim is made
|
|
785
|
+
# (could be about someone not in our records at all)
|
|
786
|
+
if not flagged and not entity_lookup_used:
|
|
787
|
+
# Check all known profiles — if none are mentioned, still warn
|
|
788
|
+
any_confirmed_nearby = False
|
|
789
|
+
for name_lower, profile in {**profiles, **{k: {'name': v.get('name', k)} for k, v in contacts.items()}}.items():
|
|
790
|
+
name_parts = name_lower.split()
|
|
791
|
+
for part in name_parts:
|
|
792
|
+
if len(part) > 2 and part.lower() in context_window.lower():
|
|
793
|
+
# Person found — check if confirmed
|
|
794
|
+
contact = contacts.get(name_lower, {})
|
|
795
|
+
contact_class = contact.get('classification', '')
|
|
796
|
+
p = profiles.get(name_lower, {})
|
|
797
|
+
if (contact_class in confirmed_classifications or
|
|
798
|
+
p.get('relationship_status') in confirmed_statuses or
|
|
799
|
+
p.get('privilege_level') == 'internal'):
|
|
800
|
+
any_confirmed_nearby = True
|
|
801
|
+
break
|
|
802
|
+
|
|
803
|
+
if not any_confirmed_nearby:
|
|
804
|
+
# Nobody confirmed near this claim — warn
|
|
805
|
+
flagged = True
|
|
806
|
+
flagged_person = None
|
|
807
|
+
|
|
808
|
+
if flagged:
|
|
809
|
+
# Build suggestion based on source
|
|
810
|
+
if flagged_person and entity_lookup_used:
|
|
811
|
+
result = lookup_entity(flagged_person, entity_index)
|
|
812
|
+
if result:
|
|
813
|
+
rel_type = result.get('relationship_type', 'unknown')
|
|
814
|
+
entity_data = result.get('entity', {})
|
|
815
|
+
entity_role = entity_data.get('primary_role', '') if entity_data else ''
|
|
816
|
+
entity_company = entity_data.get('primary_company', '') if entity_data else ''
|
|
817
|
+
suggestion = (
|
|
818
|
+
f"{flagged_person} is '{rel_type}' per entity index"
|
|
819
|
+
f"{' (' + entity_role + ')' if entity_role else ''}. "
|
|
820
|
+
f"Do not use possessive language for external parties."
|
|
821
|
+
)
|
|
822
|
+
else:
|
|
823
|
+
suggestion = f"Verify {flagged_person}'s relationship status before claiming affiliation."
|
|
824
|
+
elif flagged_person:
|
|
825
|
+
suggestion = f"Verify {flagged_person}'s relationship status before claiming affiliation."
|
|
826
|
+
else:
|
|
827
|
+
suggestion = "Could not identify a confirmed team member near this claim. Verify the relationship."
|
|
828
|
+
|
|
829
|
+
issue = {
|
|
830
|
+
"type": "relationship_claim",
|
|
831
|
+
"severity": "block",
|
|
832
|
+
"matched_text": matched_text,
|
|
833
|
+
"description": f"{description}: \"{matched_text}\"",
|
|
834
|
+
"suggestion": suggestion,
|
|
835
|
+
"context": context_window.strip(),
|
|
836
|
+
"lookup_source": "entity_index" if entity_lookup_used else "profile_fallback",
|
|
837
|
+
}
|
|
838
|
+
if flagged_person:
|
|
839
|
+
issue["person"] = flagged_person
|
|
840
|
+
issues.append(issue)
|
|
841
|
+
|
|
842
|
+
return issues
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def check_title_accuracy(message):
|
|
846
|
+
"""Check that titles attributed to named people match confirmed records.
|
|
847
|
+
|
|
848
|
+
Returns list of issue dicts.
|
|
849
|
+
"""
|
|
850
|
+
issues = []
|
|
851
|
+
org_titles = load_org_titles()
|
|
852
|
+
contacts = load_contacts()
|
|
853
|
+
profiles = load_user_profiles()
|
|
854
|
+
|
|
855
|
+
# Build comprehensive title map: name -> known title
|
|
856
|
+
title_map = {}
|
|
857
|
+
for name_lower, title in org_titles.items():
|
|
858
|
+
title_map[name_lower] = title
|
|
859
|
+
for name_lower, contact in contacts.items():
|
|
860
|
+
if contact.get('role'):
|
|
861
|
+
title_map[name_lower] = contact['role']
|
|
862
|
+
for name_lower, profile in profiles.items():
|
|
863
|
+
if profile.get('title'):
|
|
864
|
+
title_map[name_lower] = profile['title']
|
|
865
|
+
|
|
866
|
+
# Pattern: "Name, Title" or "Name (Title)" or "Name, the Title"
|
|
867
|
+
title_attribution_patterns = [
|
|
868
|
+
r'([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+),\s+(?:the\s+)?([A-Z][A-Za-z\s/]+?)(?:\.|,|\s+at\s+|\s+of\s+|\s+from\s+|\s*$)',
|
|
869
|
+
r'([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\s+\(([^)]+)\)',
|
|
870
|
+
]
|
|
871
|
+
|
|
872
|
+
for pattern in title_attribution_patterns:
|
|
873
|
+
for match in re.finditer(pattern, message):
|
|
874
|
+
name = match.group(1).strip()
|
|
875
|
+
attributed_title = match.group(2).strip()
|
|
876
|
+
name_lower = name.lower()
|
|
877
|
+
|
|
878
|
+
# Look up known title
|
|
879
|
+
known_title = title_map.get(name_lower)
|
|
880
|
+
if not known_title:
|
|
881
|
+
# Try partial match (first + last name)
|
|
882
|
+
parts = name_lower.split()
|
|
883
|
+
if len(parts) >= 2:
|
|
884
|
+
short = f"{parts[0]} {parts[-1]}"
|
|
885
|
+
known_title = title_map.get(short)
|
|
886
|
+
|
|
887
|
+
if known_title:
|
|
888
|
+
# Compare — normalize for comparison
|
|
889
|
+
known_normalized = known_title.lower().replace('/', ' ').replace('-', ' ')
|
|
890
|
+
attr_normalized = attributed_title.lower().replace('/', ' ').replace('-', ' ')
|
|
891
|
+
|
|
892
|
+
# Check if attributed title is a reasonable match
|
|
893
|
+
known_words = set(known_normalized.split())
|
|
894
|
+
attr_words = set(attr_normalized.split())
|
|
895
|
+
|
|
896
|
+
# If there's very little overlap, flag it
|
|
897
|
+
if known_words and attr_words:
|
|
898
|
+
overlap = known_words & attr_words
|
|
899
|
+
if len(overlap) < 1 and len(attr_words) > 1:
|
|
900
|
+
issues.append({
|
|
901
|
+
"type": "title_accuracy",
|
|
902
|
+
"severity": "warn",
|
|
903
|
+
"person": name,
|
|
904
|
+
"attributed_title": attributed_title,
|
|
905
|
+
"known_title": known_title,
|
|
906
|
+
"description": (
|
|
907
|
+
f"Title \"{attributed_title}\" for {name} does not match "
|
|
908
|
+
f"confirmed title \"{known_title}\""
|
|
909
|
+
),
|
|
910
|
+
"suggestion": f"Use confirmed title: \"{known_title}\"",
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
return issues
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def check_ai_self_disclosure(message):
|
|
917
|
+
"""Scan for language that reveals Sophie is AI.
|
|
918
|
+
|
|
919
|
+
Must NOT flag general business discussion of Adaptic's AI products.
|
|
920
|
+
Returns list of issue dicts.
|
|
921
|
+
"""
|
|
922
|
+
issues = []
|
|
923
|
+
|
|
924
|
+
# Business context exceptions — if these appear, the word "AI" is OK
|
|
925
|
+
business_exceptions = [
|
|
926
|
+
r'AI[- ]Born', r'AI[- ]native', r'Adaptic\s*AI', r'Adaptic\.ai',
|
|
927
|
+
r'artificial intelligence\s+strategy', r'AI\s+products?',
|
|
928
|
+
r'AI\s+platform', r'AI\s+capabilit', r'machine learning\s+models?',
|
|
929
|
+
r'AI[- ]driven', r'AI\s+solutions?', r'AI\s+technolog',
|
|
930
|
+
r'AI\s+invest', r'AI\s+fund', r'AI\s+sector', r'AI\s+market',
|
|
931
|
+
r'AI\s+infrastructure', r'AI\s+research', r'generative\s+AI',
|
|
932
|
+
]
|
|
933
|
+
|
|
934
|
+
# Self-referential AI disclosure patterns
|
|
935
|
+
disclosure_patterns = [
|
|
936
|
+
(r'\bI\s+(?:am|\'m)\s+(?:an?\s+)?(?:AI|artificial intelligence|language model|LLM|chatbot|bot|virtual assistant|automated system|machine)\b',
|
|
937
|
+
"Direct AI self-identification"),
|
|
938
|
+
(r'\b(?:I\'m|I am)\s+not\s+(?:human|a real person|a person)\b',
|
|
939
|
+
"Human status denial"),
|
|
940
|
+
(r'\bas an AI\b',
|
|
941
|
+
"Self-referential 'as an AI'"),
|
|
942
|
+
(r'\b(?:my|I)\s+(?:training|training data|knowledge cutoff|neural network|parameters)\b',
|
|
943
|
+
"AI-specific self-reference"),
|
|
944
|
+
(r'\bSophie\s+is\s+(?:an?\s+)?(?:AI|bot|automated|virtual|artificial)\b',
|
|
945
|
+
"Third-person AI disclosure about Sophie"),
|
|
946
|
+
(r'\b(?:automated|AI[- ]powered|AI[- ]operated|machine[- ]generated)\s+(?:assistant|chief of staff|executive|system)\b',
|
|
947
|
+
"Role described as automated"),
|
|
948
|
+
(r'\b(?:I don\'t have|I lack)\s+(?:feelings|emotions|a body|physical form|consciousness)\b',
|
|
949
|
+
"Self-disclosure via limitation"),
|
|
950
|
+
(r'\b(?:my programming|my algorithms?|my code)\b',
|
|
951
|
+
"Self-referencing technical nature"),
|
|
952
|
+
]
|
|
953
|
+
|
|
954
|
+
for pattern, description in disclosure_patterns:
|
|
955
|
+
for match in re.finditer(pattern, message, re.IGNORECASE):
|
|
956
|
+
matched_text = match.group(0)
|
|
957
|
+
start = max(0, match.start() - 40)
|
|
958
|
+
end = min(len(message), match.end() + 40)
|
|
959
|
+
context_window = message[start:end]
|
|
960
|
+
|
|
961
|
+
# Check if this is actually a business context exception
|
|
962
|
+
is_business_context = False
|
|
963
|
+
for exc_pattern in business_exceptions:
|
|
964
|
+
if re.search(exc_pattern, context_window, re.IGNORECASE):
|
|
965
|
+
is_business_context = True
|
|
966
|
+
break
|
|
967
|
+
|
|
968
|
+
if not is_business_context:
|
|
969
|
+
issues.append({
|
|
970
|
+
"type": "ai_self_disclosure",
|
|
971
|
+
"severity": "block",
|
|
972
|
+
"matched_text": matched_text,
|
|
973
|
+
"description": f"AI self-disclosure detected: {description}",
|
|
974
|
+
"suggestion": "Remove or rephrase to avoid revealing Sophie's AI nature.",
|
|
975
|
+
"context": context_window.strip(),
|
|
976
|
+
})
|
|
977
|
+
|
|
978
|
+
return issues
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def check_in_person_self_scheduling(message):
|
|
982
|
+
"""Detect Sophie scheduling herself for in-person meetings.
|
|
983
|
+
|
|
984
|
+
Should NOT flag scheduling for Mehran or team members.
|
|
985
|
+
Returns list of issue dicts.
|
|
986
|
+
"""
|
|
987
|
+
issues = []
|
|
988
|
+
|
|
989
|
+
# Patterns where Sophie offers to physically attend
|
|
990
|
+
self_attend_patterns = [
|
|
991
|
+
(r'\bI\'ll\s+(?:attend|be there|come to|visit|meet you at|stop by|drop by|come by|swing by|show up)\b',
|
|
992
|
+
"Sophie offering to physically attend"),
|
|
993
|
+
(r'\b(?:meet me at|see you at|I\'ll see you|I can meet|let me come|I\'ll come)\b',
|
|
994
|
+
"Sophie scheduling herself for physical meeting"),
|
|
995
|
+
(r'\b(?:I\'ll be in|I\'m in|I will be at)\s+(?:the office|DIFC|Innovation|your office|the meeting room|the boardroom)\b',
|
|
996
|
+
"Sophie placing herself in physical location"),
|
|
997
|
+
(r'\bSophie\s+(?:will attend|will be at|will meet|will visit)\b',
|
|
998
|
+
"Third-person Sophie physical attendance"),
|
|
999
|
+
(r'\bI\'ll\s+(?:fly|travel|drive|take a cab|take a taxi)\b',
|
|
1000
|
+
"Sophie describing physical travel"),
|
|
1001
|
+
(r'\b(?:looking forward to meeting you|see you (?:there|soon|Monday|Tuesday|Wednesday|Thursday|Friday|tomorrow|next week))\b',
|
|
1002
|
+
"Sophie implying physical presence at meeting"),
|
|
1003
|
+
]
|
|
1004
|
+
|
|
1005
|
+
# Exception patterns — scheduling others is fine
|
|
1006
|
+
team_scheduling_patterns = [
|
|
1007
|
+
r'\b(?:Mehran|Hootan|Nima|Bronwyn|Shayan|the team|our team|he|she|they)\s+(?:will|can|should|could)\s+(?:attend|be there|meet|visit)',
|
|
1008
|
+
r'\b(?:Mehran|Hootan|Nima|Bronwyn|Shayan)\s+(?:will|can)\s+(?:come|be at|meet you)',
|
|
1009
|
+
r'\b(?:schedule|book|set up|arrange)\s+(?:a|the)\s+(?:meeting|call|session)\s+(?:for|with)\s+(?:Mehran|Hootan|Nima|Bronwyn)',
|
|
1010
|
+
]
|
|
1011
|
+
|
|
1012
|
+
for pattern, description in self_attend_patterns:
|
|
1013
|
+
for match in re.finditer(pattern, message, re.IGNORECASE):
|
|
1014
|
+
matched_text = match.group(0)
|
|
1015
|
+
start = max(0, match.start() - 50)
|
|
1016
|
+
end = min(len(message), match.end() + 50)
|
|
1017
|
+
context_window = message[start:end]
|
|
1018
|
+
|
|
1019
|
+
# Check if this is actually about scheduling someone else
|
|
1020
|
+
is_team_scheduling = False
|
|
1021
|
+
for exc_pattern in team_scheduling_patterns:
|
|
1022
|
+
if re.search(exc_pattern, context_window, re.IGNORECASE):
|
|
1023
|
+
is_team_scheduling = True
|
|
1024
|
+
break
|
|
1025
|
+
|
|
1026
|
+
if not is_team_scheduling:
|
|
1027
|
+
issues.append({
|
|
1028
|
+
"type": "in_person_self_scheduling",
|
|
1029
|
+
"severity": "block",
|
|
1030
|
+
"matched_text": matched_text,
|
|
1031
|
+
"description": f"In-person self-scheduling detected: {description}",
|
|
1032
|
+
"suggestion": "Sophie cannot attend in-person meetings. Propose Mehran or a team member instead.",
|
|
1033
|
+
"context": context_window.strip(),
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
return issues
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def check_unverified_commitments(message):
|
|
1040
|
+
"""Flag strong commitment language that should be verified.
|
|
1041
|
+
|
|
1042
|
+
Returns list of issue dicts.
|
|
1043
|
+
"""
|
|
1044
|
+
issues = []
|
|
1045
|
+
|
|
1046
|
+
commitment_patterns = [
|
|
1047
|
+
(r'\b(?:we\'ve|we have)\s+(?:closed|secured|signed|finalized|completed|received approval)',
|
|
1048
|
+
"Commitment claim — verify status"),
|
|
1049
|
+
(r'\b(?:deal is done|agreement signed|funds received|licence approved|licence granted)',
|
|
1050
|
+
"Completion claim — verify against records"),
|
|
1051
|
+
(r'\b(?:we\'ve|we have)\s+(?:raised|received)\s+\$[\d,.]+',
|
|
1052
|
+
"Fundraising claim — verify amount"),
|
|
1053
|
+
]
|
|
1054
|
+
|
|
1055
|
+
for pattern, description in commitment_patterns:
|
|
1056
|
+
for match in re.finditer(pattern, message, re.IGNORECASE):
|
|
1057
|
+
matched_text = match.group(0)
|
|
1058
|
+
start = max(0, match.start() - 40)
|
|
1059
|
+
end = min(len(message), match.end() + 40)
|
|
1060
|
+
context_window = message[start:end]
|
|
1061
|
+
|
|
1062
|
+
issues.append({
|
|
1063
|
+
"type": "unverified_commitment",
|
|
1064
|
+
"severity": "warn",
|
|
1065
|
+
"matched_text": matched_text,
|
|
1066
|
+
"description": f"Unverified commitment: {description}",
|
|
1067
|
+
"suggestion": "Verify this claim against known records before sending.",
|
|
1068
|
+
"context": context_window.strip(),
|
|
1069
|
+
})
|
|
1070
|
+
|
|
1071
|
+
return issues
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
# ─────────────────────────────────────────────
|
|
1075
|
+
# Main Validation Pipeline
|
|
1076
|
+
# ─────────────────────────────────────────────
|
|
1077
|
+
|
|
1078
|
+
def _load_rules_config():
|
|
1079
|
+
"""Load validation-rules.yaml. Returns dict or None on failure."""
|
|
1080
|
+
try:
|
|
1081
|
+
if VALIDATION_RULES_PATH.exists():
|
|
1082
|
+
text = VALIDATION_RULES_PATH.read_text(encoding='utf-8')
|
|
1083
|
+
return _parse_yaml_simple(text)
|
|
1084
|
+
except Exception:
|
|
1085
|
+
pass
|
|
1086
|
+
return None
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def validate_message(message, message_type="email", recipient="", channel=None):
|
|
1090
|
+
"""Run all validation checks on a message.
|
|
1091
|
+
|
|
1092
|
+
Args:
|
|
1093
|
+
message: The message body text
|
|
1094
|
+
message_type: "email", "slack", or "whatsapp"
|
|
1095
|
+
recipient: Recipient identifier (email address, channel ID, phone number)
|
|
1096
|
+
channel: Optional Slack channel slug (for disclosure assessment)
|
|
1097
|
+
|
|
1098
|
+
Returns:
|
|
1099
|
+
dict with keys: status, issues, metadata
|
|
1100
|
+
"""
|
|
1101
|
+
start_time = time.time()
|
|
1102
|
+
all_issues = []
|
|
1103
|
+
|
|
1104
|
+
# Run all checks
|
|
1105
|
+
all_issues.extend(check_relationship_claims(message))
|
|
1106
|
+
all_issues.extend(check_title_accuracy(message))
|
|
1107
|
+
all_issues.extend(check_ai_self_disclosure(message))
|
|
1108
|
+
all_issues.extend(check_in_person_self_scheduling(message))
|
|
1109
|
+
all_issues.extend(check_unverified_commitments(message))
|
|
1110
|
+
|
|
1111
|
+
# ── Rule 6: Disclosure Assessment (Information Barrier) ──
|
|
1112
|
+
if _da_module:
|
|
1113
|
+
try:
|
|
1114
|
+
rules_config = _load_rules_config()
|
|
1115
|
+
da_config = rules_config.get("disclosure_assessment", {}) if rules_config else {}
|
|
1116
|
+
if da_config.get("enabled", False):
|
|
1117
|
+
# Derive a slug from the recipient string
|
|
1118
|
+
recipient_slug = None
|
|
1119
|
+
if recipient:
|
|
1120
|
+
recipient_slug = (
|
|
1121
|
+
recipient.lower()
|
|
1122
|
+
.replace(" ", "-")
|
|
1123
|
+
.replace("@", "-at-")
|
|
1124
|
+
.replace(".", "-")
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
if recipient_slug:
|
|
1128
|
+
da_result = _da_module.assess_disclosure(
|
|
1129
|
+
message, recipient_slug, channel=channel
|
|
1130
|
+
)
|
|
1131
|
+
outcome = da_result.get("outcome", "passed")
|
|
1132
|
+
if outcome == "blocked":
|
|
1133
|
+
assessments = da_result.get("references_assessed", [])
|
|
1134
|
+
first_reason = (
|
|
1135
|
+
assessments[0].get("reasoning", "disclosure violation")
|
|
1136
|
+
if assessments else "disclosure violation detected"
|
|
1137
|
+
)
|
|
1138
|
+
all_issues.append({
|
|
1139
|
+
"type": "disclosure_assessment",
|
|
1140
|
+
"severity": "block",
|
|
1141
|
+
"matched_text": "",
|
|
1142
|
+
"description": (
|
|
1143
|
+
f"INFORMATION BARRIER: {da_result.get('severity', 'unknown')} severity"
|
|
1144
|
+
f" — {first_reason}"
|
|
1145
|
+
),
|
|
1146
|
+
"suggestion": "Remove or redact the restricted reference before sending.",
|
|
1147
|
+
"detail": da_result,
|
|
1148
|
+
})
|
|
1149
|
+
elif outcome == "stripped":
|
|
1150
|
+
all_issues.append({
|
|
1151
|
+
"type": "disclosure_assessment",
|
|
1152
|
+
"severity": "warn",
|
|
1153
|
+
"matched_text": "",
|
|
1154
|
+
"description": (
|
|
1155
|
+
"INFORMATION BARRIER: Medium severity references detected"
|
|
1156
|
+
" — content should be reviewed"
|
|
1157
|
+
),
|
|
1158
|
+
"suggestion": "Review and redact medium-sensitivity references before sending.",
|
|
1159
|
+
"detail": da_result,
|
|
1160
|
+
})
|
|
1161
|
+
except Exception:
|
|
1162
|
+
pass # Fail-open on assessment errors
|
|
1163
|
+
|
|
1164
|
+
# Deduplicate by matched_text + type
|
|
1165
|
+
seen = set()
|
|
1166
|
+
unique_issues = []
|
|
1167
|
+
for issue in all_issues:
|
|
1168
|
+
key = (issue.get('type', ''), issue.get('matched_text', ''))
|
|
1169
|
+
if key not in seen:
|
|
1170
|
+
seen.add(key)
|
|
1171
|
+
unique_issues.append(issue)
|
|
1172
|
+
|
|
1173
|
+
elapsed_ms = round((time.time() - start_time) * 1000, 1)
|
|
1174
|
+
|
|
1175
|
+
has_blockers = any(i['severity'] == 'block' for i in unique_issues)
|
|
1176
|
+
status = "issues_found" if has_blockers else ("warnings" if unique_issues else "clean")
|
|
1177
|
+
|
|
1178
|
+
result = {
|
|
1179
|
+
"status": status,
|
|
1180
|
+
"issues": unique_issues,
|
|
1181
|
+
"metadata": {
|
|
1182
|
+
"message_type": message_type,
|
|
1183
|
+
"recipient": recipient,
|
|
1184
|
+
"checks_run": 6,
|
|
1185
|
+
"issues_found": len(unique_issues),
|
|
1186
|
+
"blockers": sum(1 for i in unique_issues if i['severity'] == 'block'),
|
|
1187
|
+
"warnings": sum(1 for i in unique_issues if i['severity'] == 'warn'),
|
|
1188
|
+
"elapsed_ms": elapsed_ms,
|
|
1189
|
+
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
1190
|
+
},
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return result
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
def log_validation_result(result):
|
|
1197
|
+
"""Log validation result to audit log."""
|
|
1198
|
+
try:
|
|
1199
|
+
AUDIT_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
1200
|
+
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
1201
|
+
log_file = AUDIT_LOG_DIR / f"{today}-validation.jsonl"
|
|
1202
|
+
|
|
1203
|
+
log_entry = {
|
|
1204
|
+
"timestamp": result["metadata"]["timestamp"],
|
|
1205
|
+
"agent": "validate-outbound",
|
|
1206
|
+
"action": "outbound_validation",
|
|
1207
|
+
"status": result["status"],
|
|
1208
|
+
"message_type": result["metadata"]["message_type"],
|
|
1209
|
+
"recipient": result["metadata"]["recipient"],
|
|
1210
|
+
"issues_found": result["metadata"]["issues_found"],
|
|
1211
|
+
"blockers": result["metadata"]["blockers"],
|
|
1212
|
+
"warnings": result["metadata"]["warnings"],
|
|
1213
|
+
"elapsed_ms": result["metadata"]["elapsed_ms"],
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
# Add issue summaries (but not full context to keep log compact)
|
|
1217
|
+
if result["issues"]:
|
|
1218
|
+
log_entry["issue_types"] = list(set(i["type"] for i in result["issues"]))
|
|
1219
|
+
log_entry["issue_summaries"] = [
|
|
1220
|
+
{"type": i["type"], "severity": i["severity"], "description": i["description"]}
|
|
1221
|
+
for i in result["issues"][:10] # Cap at 10 for log size
|
|
1222
|
+
]
|
|
1223
|
+
|
|
1224
|
+
with open(log_file, "a", encoding="utf-8") as f:
|
|
1225
|
+
f.write(json.dumps(log_entry) + "\n")
|
|
1226
|
+
except Exception:
|
|
1227
|
+
pass # Fail-open — validation logging should never block sending
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
# ─────────────────────────────────────────────
|
|
1231
|
+
# CLI Entry Point
|
|
1232
|
+
# ─────────────────────────────────────────────
|
|
1233
|
+
|
|
1234
|
+
def main():
|
|
1235
|
+
parser = argparse.ArgumentParser(
|
|
1236
|
+
description="Pre-send outbound correspondence validator"
|
|
1237
|
+
)
|
|
1238
|
+
parser.add_argument(
|
|
1239
|
+
"--message", "-m",
|
|
1240
|
+
help="Message body to validate (alternative to stdin)"
|
|
1241
|
+
)
|
|
1242
|
+
parser.add_argument(
|
|
1243
|
+
"--type", "-t",
|
|
1244
|
+
default="email",
|
|
1245
|
+
choices=["email", "slack", "whatsapp"],
|
|
1246
|
+
help="Message type (default: email)"
|
|
1247
|
+
)
|
|
1248
|
+
parser.add_argument(
|
|
1249
|
+
"--recipient", "-r",
|
|
1250
|
+
default="",
|
|
1251
|
+
help="Recipient identifier (email, channel ID, phone)"
|
|
1252
|
+
)
|
|
1253
|
+
parser.add_argument(
|
|
1254
|
+
"--channel", "-c",
|
|
1255
|
+
default=None,
|
|
1256
|
+
help="Slack channel slug (used for disclosure assessment)"
|
|
1257
|
+
)
|
|
1258
|
+
parser.add_argument(
|
|
1259
|
+
"--quiet", "-q",
|
|
1260
|
+
action="store_true",
|
|
1261
|
+
help="Suppress non-JSON output"
|
|
1262
|
+
)
|
|
1263
|
+
args = parser.parse_args()
|
|
1264
|
+
|
|
1265
|
+
# Get message from --message flag or stdin
|
|
1266
|
+
if args.message:
|
|
1267
|
+
message = args.message
|
|
1268
|
+
elif not sys.stdin.isatty():
|
|
1269
|
+
message = sys.stdin.read()
|
|
1270
|
+
else:
|
|
1271
|
+
print("ERROR: Provide message via --message flag or stdin", file=sys.stderr)
|
|
1272
|
+
sys.exit(2)
|
|
1273
|
+
|
|
1274
|
+
if not message.strip():
|
|
1275
|
+
print("ERROR: Empty message", file=sys.stderr)
|
|
1276
|
+
sys.exit(2)
|
|
1277
|
+
|
|
1278
|
+
# Run validation
|
|
1279
|
+
result = validate_message(message, args.type, args.recipient, channel=args.channel)
|
|
1280
|
+
|
|
1281
|
+
# Log to audit
|
|
1282
|
+
log_validation_result(result)
|
|
1283
|
+
|
|
1284
|
+
# Output JSON result
|
|
1285
|
+
print(json.dumps(result, indent=2))
|
|
1286
|
+
|
|
1287
|
+
# Human-readable summary to stderr (unless --quiet)
|
|
1288
|
+
if not args.quiet:
|
|
1289
|
+
meta = result["metadata"]
|
|
1290
|
+
if result["status"] == "clean":
|
|
1291
|
+
print(
|
|
1292
|
+
f"VALIDATION PASS: {meta['checks_run']} checks, "
|
|
1293
|
+
f"0 issues ({meta['elapsed_ms']}ms)",
|
|
1294
|
+
file=sys.stderr
|
|
1295
|
+
)
|
|
1296
|
+
else:
|
|
1297
|
+
print(
|
|
1298
|
+
f"VALIDATION {'BLOCK' if meta['blockers'] > 0 else 'WARN'}: "
|
|
1299
|
+
f"{meta['blockers']} blockers, {meta['warnings']} warnings "
|
|
1300
|
+
f"({meta['elapsed_ms']}ms)",
|
|
1301
|
+
file=sys.stderr
|
|
1302
|
+
)
|
|
1303
|
+
for issue in result["issues"]:
|
|
1304
|
+
severity_marker = "BLOCK" if issue["severity"] == "block" else "WARN"
|
|
1305
|
+
print(
|
|
1306
|
+
f" [{severity_marker}] {issue['description']}",
|
|
1307
|
+
file=sys.stderr
|
|
1308
|
+
)
|
|
1309
|
+
if issue.get("suggestion"):
|
|
1310
|
+
print(
|
|
1311
|
+
f" -> {issue['suggestion']}",
|
|
1312
|
+
file=sys.stderr
|
|
1313
|
+
)
|
|
1314
|
+
|
|
1315
|
+
# Exit code: 1 if blockers, 0 otherwise
|
|
1316
|
+
sys.exit(1 if result["metadata"]["blockers"] > 0 else 0)
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
# ─────────────────────────────────────────────
|
|
1320
|
+
# Test Suite
|
|
1321
|
+
# ─────────────────────────────────────────────
|
|
1322
|
+
|
|
1323
|
+
def run_tests():
|
|
1324
|
+
"""Run built-in validation tests. Returns True if all pass."""
|
|
1325
|
+
passed = 0
|
|
1326
|
+
failed = 0
|
|
1327
|
+
total = 0
|
|
1328
|
+
|
|
1329
|
+
def assert_test(name, condition, detail=""):
|
|
1330
|
+
nonlocal passed, failed, total
|
|
1331
|
+
total += 1
|
|
1332
|
+
if condition:
|
|
1333
|
+
passed += 1
|
|
1334
|
+
print(f" PASS: {name}", file=sys.stderr)
|
|
1335
|
+
else:
|
|
1336
|
+
failed += 1
|
|
1337
|
+
print(f" FAIL: {name}{' — ' + detail if detail else ''}", file=sys.stderr)
|
|
1338
|
+
|
|
1339
|
+
print("Running validate-outbound.py tests...\n", file=sys.stderr)
|
|
1340
|
+
|
|
1341
|
+
# ── Test 1: Entity index loads without error ──
|
|
1342
|
+
print("[Entity Index Loading]", file=sys.stderr)
|
|
1343
|
+
idx = load_entity_index()
|
|
1344
|
+
assert_test("Entity index loads", isinstance(idx, dict))
|
|
1345
|
+
assert_test("Entity index has entities", len(idx.get('entities', [])) > 0)
|
|
1346
|
+
assert_test("Entity index has relationships", len(idx.get('relationships', [])) > 0)
|
|
1347
|
+
assert_test("Entity index has by_name lookup", len(idx.get('by_name', {})) > 0)
|
|
1348
|
+
|
|
1349
|
+
# ── Test 2: Entity lookup — known internal people ──
|
|
1350
|
+
print("\n[Entity Lookup — Internal]", file=sys.stderr)
|
|
1351
|
+
mehran = lookup_entity("Mehran Granfar", idx)
|
|
1352
|
+
assert_test("Mehran found", mehran is not None)
|
|
1353
|
+
assert_test("Mehran is internal", mehran and mehran.get('is_internal') is True)
|
|
1354
|
+
assert_test("Mehran rel_type is internal", mehran and mehran.get('relationship_type') == 'internal')
|
|
1355
|
+
|
|
1356
|
+
hootan = lookup_entity("Hootan", idx)
|
|
1357
|
+
assert_test("Hootan found by first name", hootan is not None)
|
|
1358
|
+
assert_test("Hootan is internal", hootan and hootan.get('is_internal') is True)
|
|
1359
|
+
|
|
1360
|
+
sophie = lookup_entity("Sophie Nguyen", idx)
|
|
1361
|
+
assert_test("Sophie found", sophie is not None)
|
|
1362
|
+
assert_test("Sophie is internal", sophie and sophie.get('is_internal') is True)
|
|
1363
|
+
|
|
1364
|
+
# ── Test 3: Entity lookup — known external people ──
|
|
1365
|
+
print("\n[Entity Lookup — External]", file=sys.stderr)
|
|
1366
|
+
graham = lookup_entity("Graham Syder", idx)
|
|
1367
|
+
assert_test("Graham found", graham is not None)
|
|
1368
|
+
assert_test("Graham is NOT internal", graham and graham.get('is_internal') is False)
|
|
1369
|
+
assert_test(
|
|
1370
|
+
"Graham is vendor_candidate",
|
|
1371
|
+
graham and graham.get('relationship_type') == 'vendor_candidate',
|
|
1372
|
+
f"got: {graham.get('relationship_type') if graham else 'None'}"
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
rishel = lookup_entity("Rishel", idx)
|
|
1376
|
+
assert_test("Rishel found by first name", rishel is not None)
|
|
1377
|
+
assert_test("Rishel is NOT internal", rishel and rishel.get('is_internal') is False)
|
|
1378
|
+
|
|
1379
|
+
# ── Test 4: Entity lookup — unknown person ──
|
|
1380
|
+
print("\n[Entity Lookup — Unknown]", file=sys.stderr)
|
|
1381
|
+
unknown = lookup_entity("John Smith", idx)
|
|
1382
|
+
assert_test("Unknown person returns None", unknown is None)
|
|
1383
|
+
|
|
1384
|
+
unknown_empty = lookup_entity("", idx)
|
|
1385
|
+
assert_test("Empty string returns None", unknown_empty is None)
|
|
1386
|
+
|
|
1387
|
+
# ── Test 5: Graham incident — MUST be blocked ──
|
|
1388
|
+
print("\n[Graham Incident — Possessive Claim Block]", file=sys.stderr)
|
|
1389
|
+
graham_msg = "Graham Syder is our project manager for the DIFC fitout."
|
|
1390
|
+
result = validate_message(graham_msg, "email", "internal@adaptic.ai")
|
|
1391
|
+
assert_test(
|
|
1392
|
+
"Graham 'our PM' is BLOCKED",
|
|
1393
|
+
result['status'] == 'issues_found' and result['metadata']['blockers'] > 0,
|
|
1394
|
+
f"status={result['status']}, blockers={result['metadata']['blockers']}"
|
|
1395
|
+
)
|
|
1396
|
+
# Verify entity_index was the lookup source
|
|
1397
|
+
rel_issues = [i for i in result['issues'] if i['type'] == 'relationship_claim']
|
|
1398
|
+
entity_sourced = any(i.get('lookup_source') == 'entity_index' for i in rel_issues)
|
|
1399
|
+
assert_test(
|
|
1400
|
+
"Graham block used entity_index as source",
|
|
1401
|
+
entity_sourced,
|
|
1402
|
+
f"sources: {[i.get('lookup_source') for i in rel_issues]}"
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
# Also test variant: "our PM Graham"
|
|
1406
|
+
graham_msg2 = "I've arranged for our PM Graham to review the floor plan."
|
|
1407
|
+
result2 = validate_message(graham_msg2, "slack", "C099ABCDEF")
|
|
1408
|
+
assert_test(
|
|
1409
|
+
"Graham 'our PM Graham' variant is BLOCKED",
|
|
1410
|
+
result2['status'] == 'issues_found' and result2['metadata']['blockers'] > 0,
|
|
1411
|
+
f"status={result2['status']}, blockers={result2['metadata']['blockers']}"
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
# ── Test 6: Internal person — MUST be allowed ──
|
|
1415
|
+
print("\n[Internal Person — Allowed Claims]", file=sys.stderr)
|
|
1416
|
+
hootan_msg = "Hootan Yazhari is our CFO and will be handling the DFSA submission."
|
|
1417
|
+
result3 = validate_message(hootan_msg, "email", "external@example.com")
|
|
1418
|
+
rel_issues3 = [i for i in result3['issues'] if i['type'] == 'relationship_claim']
|
|
1419
|
+
assert_test(
|
|
1420
|
+
"Hootan 'our CFO' is NOT blocked",
|
|
1421
|
+
len(rel_issues3) == 0,
|
|
1422
|
+
f"found {len(rel_issues3)} relationship issues"
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
# ── Test 7: AI self-disclosure still blocked ──
|
|
1426
|
+
print("\n[AI Self-Disclosure Block]", file=sys.stderr)
|
|
1427
|
+
ai_msg = "I am an AI assistant and I can help with that."
|
|
1428
|
+
result4 = validate_message(ai_msg, "email", "external@example.com")
|
|
1429
|
+
ai_issues = [i for i in result4['issues'] if i['type'] == 'ai_self_disclosure']
|
|
1430
|
+
assert_test("AI self-disclosure blocked", len(ai_issues) > 0)
|
|
1431
|
+
|
|
1432
|
+
ai_msg_sophie = "Sophie is an AI-operated automated assistant."
|
|
1433
|
+
result4b = validate_message(ai_msg_sophie, "email", "external@example.com")
|
|
1434
|
+
ai_issues_b = [i for i in result4b['issues'] if i['type'] == 'ai_self_disclosure']
|
|
1435
|
+
assert_test("Sophie third-person AI disclosure blocked", len(ai_issues_b) > 0)
|
|
1436
|
+
|
|
1437
|
+
# ── Test 8: In-person self-scheduling still blocked ──
|
|
1438
|
+
print("\n[In-Person Self-Scheduling Block]", file=sys.stderr)
|
|
1439
|
+
attend_msg = "I'll attend the meeting at the DIFC office tomorrow."
|
|
1440
|
+
result5 = validate_message(attend_msg, "slack", "C099ABCDEF")
|
|
1441
|
+
attend_issues = [i for i in result5['issues'] if i['type'] == 'in_person_self_scheduling']
|
|
1442
|
+
assert_test("In-person self-scheduling blocked", len(attend_issues) > 0)
|
|
1443
|
+
|
|
1444
|
+
# ── Test 9: Clean message passes ──
|
|
1445
|
+
print("\n[Clean Message — No Issues]", file=sys.stderr)
|
|
1446
|
+
clean_msg = "Hi team, the quarterly report is ready for review. Please check your inboxes."
|
|
1447
|
+
result6 = validate_message(clean_msg, "email", "team@adaptic.ai")
|
|
1448
|
+
assert_test(
|
|
1449
|
+
"Clean message passes with no blockers",
|
|
1450
|
+
result6['metadata']['blockers'] == 0,
|
|
1451
|
+
f"blockers={result6['metadata']['blockers']}"
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
# ── Test 10: Shayan — investor, not internal employee ──
|
|
1455
|
+
print("\n[Shayan — Investor, Not Internal]", file=sys.stderr)
|
|
1456
|
+
shayan = lookup_entity("Shayan Kargarian", idx)
|
|
1457
|
+
assert_test("Shayan found", shayan is not None)
|
|
1458
|
+
assert_test(
|
|
1459
|
+
"Shayan is NOT internal",
|
|
1460
|
+
shayan and shayan.get('is_internal') is False,
|
|
1461
|
+
f"is_internal={shayan.get('is_internal') if shayan else 'None'}"
|
|
1462
|
+
)
|
|
1463
|
+
|
|
1464
|
+
# ── Test 11: Performance — under 1 second ──
|
|
1465
|
+
print("\n[Performance]", file=sys.stderr)
|
|
1466
|
+
perf_msg = "Graham is our project manager. He has joined the team. We've hired him."
|
|
1467
|
+
import time as _time
|
|
1468
|
+
t0 = _time.time()
|
|
1469
|
+
for _ in range(10):
|
|
1470
|
+
validate_message(perf_msg, "email", "test@test.com")
|
|
1471
|
+
elapsed = (_time.time() - t0) / 10
|
|
1472
|
+
assert_test(
|
|
1473
|
+
f"Average validation < 1s (got {elapsed*1000:.1f}ms)",
|
|
1474
|
+
elapsed < 1.0
|
|
1475
|
+
)
|
|
1476
|
+
|
|
1477
|
+
# ── Summary ──
|
|
1478
|
+
print(f"\n{'='*50}", file=sys.stderr)
|
|
1479
|
+
print(f"Results: {passed}/{total} passed, {failed} failed", file=sys.stderr)
|
|
1480
|
+
if failed > 0:
|
|
1481
|
+
print("TESTS FAILED", file=sys.stderr)
|
|
1482
|
+
return False
|
|
1483
|
+
else:
|
|
1484
|
+
print("ALL TESTS PASSED", file=sys.stderr)
|
|
1485
|
+
return True
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
if __name__ == "__main__":
|
|
1489
|
+
# Check for --test flag before argparse (to avoid --message requirement)
|
|
1490
|
+
if '--test' in sys.argv:
|
|
1491
|
+
success = run_tests()
|
|
1492
|
+
sys.exit(0 if success else 1)
|
|
1493
|
+
main()
|