@rubytech/create-maxy-code 0.1.446 → 0.1.448
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/package.json +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +6 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/index.ts +7 -0
- package/payload/platform/neo4j/schema.cypher +35 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +42 -5
- package/payload/platform/plugins/docs/references/calendar-booking.md +18 -1
- package/payload/platform/plugins/docs/references/claudeai-connectors.md +15 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +8 -3
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +4 -4
- package/payload/platform/plugins/memory/references/schema-construction.md +7 -2
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +23 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +70 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.js +22 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.js +60 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.js +80 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.js +90 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.js +194 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.js +16 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.js +103 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +88 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.d.ts +9 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.js +24 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/otp.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/otp.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/otp.js +49 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/otp.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +58 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js +127 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts +28 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js +54 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +21 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +38 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.js +39 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +23 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +44 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.d.ts +22 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.js +90 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts +27 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +75 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +57 -1
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +15 -4
- package/payload/platform/plugins/scheduling/PLUGIN.md +15 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +29 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +53 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +21 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +80 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +4 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +68 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js +75 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts +31 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js +41 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js.map +1 -0
- package/payload/platform/plugins/voice-mirror/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +72 -0
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/index.js +10 -10
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/index.js.map +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/neo4j.d.ts +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/neo4j.js +4 -4
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/neo4j.js.map +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-record-feedback.js +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/package-lock.json +2 -2
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/package.json +1 -1
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/scripts/smoke.mjs +9 -9
- package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/skills/voice-mirror/SKILL.md +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +12 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/templates/agents/admin/AGENTS.md +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -2
- package/payload/premium-plugins/writer-craft/.claude-plugin/plugin.json +1 -14
- package/payload/premium-plugins/writer-craft/PLUGIN.md +2 -51
- package/payload/server/public/assets/{AdminLoginScreens-D9L-CuWi.js → AdminLoginScreens-Bvf4HKmq.js} +1 -1
- package/payload/server/public/assets/AdminShell-CaGc8ODG.js +2 -0
- package/payload/server/public/assets/{Checkbox-CFdt8ljo.js → Checkbox-2SswiICc.js} +1 -1
- package/payload/server/public/assets/admin-B7GPCzpr.js +1 -0
- package/payload/server/public/assets/browser-CodHC_MO.js +1 -0
- package/payload/server/public/assets/calendar-DH7cyXKF.js +1 -0
- package/payload/server/public/assets/chat-BQyPAEMv.js +1 -0
- package/payload/server/public/assets/chevron-left-Cmev0R9v.js +1 -0
- package/payload/server/public/assets/data-DWxZzMfL.js +1 -0
- package/payload/server/public/assets/{graph-DrE7KupQ.js → graph-BToB78LG.js} +2 -2
- package/payload/server/public/assets/graph-labels-JRGrstzM.js +1 -0
- package/payload/server/public/assets/maximize-2-CEQRv64B.js +1 -0
- package/payload/server/public/assets/operator-Cx8cq2wf.js +1 -0
- package/payload/server/public/assets/page-D6kzsk18.js +32 -0
- package/payload/server/public/assets/page-TCsQy4eP.js +1 -0
- package/payload/server/public/assets/public-B61xcmAr.js +1 -0
- package/payload/server/public/assets/{rotate-ccw-Cqp4oqHV.js → rotate-ccw-7_3ol0Z4.js} +1 -1
- package/payload/server/public/assets/{tasks-bQKOhMga.js → tasks-BY9ajKXf.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-BIL4ZaDK.js → time-entry-format-DmNhIJnM.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-C5-GOl5l.js +1 -0
- package/payload/server/public/assets/useSelectionMode-UOUVnP9B.js +1 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-CVHKTSst.css → useSubAccountSwitcher-DgM1ergs.css} +1 -1
- package/payload/server/public/assets/useVoiceRecorder-BAP3yGxs.js +2 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +349 -171
- package/payload/premium-plugins/writer-craft/lib/mcp-lifeline/dist/index.d.ts +0 -8
- package/payload/premium-plugins/writer-craft/lib/mcp-lifeline/dist/index.d.ts.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-lifeline/dist/index.js +0 -71
- package/payload/premium-plugins/writer-craft/lib/mcp-lifeline/dist/index.js.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-lifeline/package.json +0 -7
- package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.d.ts +0 -51
- package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.d.ts.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.js +0 -196
- package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.js.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/package.json +0 -7
- package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.d.ts +0 -3
- package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.d.ts.map +0 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.js +0 -58
- package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.js.map +0 -1
- package/payload/premium-plugins/writer-craft/mcp/src/index.ts +0 -457
- package/payload/premium-plugins/writer-craft/mcp/src/lib/neo4j.ts +0 -56
- package/payload/premium-plugins/writer-craft/mcp/src/lib/voice-corpus.ts +0 -154
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +0 -833
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-ingest-session-text.ts +0 -153
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-record-feedback.ts +0 -148
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +0 -270
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-tag-content.ts +0 -181
- package/payload/premium-plugins/writer-craft/mcp/tsconfig.json +0 -8
- package/payload/server/public/assets/AdminShell-DSQrvZXq.js +0 -2
- package/payload/server/public/assets/admin-Ch4n-G0j.js +0 -1
- package/payload/server/public/assets/browser-B2AIxiuk.js +0 -1
- package/payload/server/public/assets/calendar-BvItH3B0.js +0 -1
- package/payload/server/public/assets/chat-eQFMaX9y.js +0 -1
- package/payload/server/public/assets/chevron-left-D2AnCEyE.js +0 -1
- package/payload/server/public/assets/data-BzOmbHoe.js +0 -1
- package/payload/server/public/assets/file-download-CK9j_Jx2.js +0 -1
- package/payload/server/public/assets/graph-labels-CwEtvIRV.js +0 -1
- package/payload/server/public/assets/maximize-2-CuzEom3K.js +0 -1
- package/payload/server/public/assets/operator-DlboYPPu.js +0 -1
- package/payload/server/public/assets/page-BuWH5gNK.js +0 -1
- package/payload/server/public/assets/page-Co9E8U5x.js +0 -32
- package/payload/server/public/assets/public-DtFpPMTG.js +0 -1
- package/payload/server/public/assets/useCopyFeedback-rB8QuqyH.js +0 -1
- package/payload/server/public/assets/useVoiceRecorder-Ctny8qWx.js +0 -2
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/lib/mcp-spawn-tee/index.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/lib/mcp-spawn-tee/package.json +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/index.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/index.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/neo4j.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/voice-corpus.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/voice-corpus.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/voice-corpus.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/lib/voice-corpus.js.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-distil-profile.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-distil-profile.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-distil-profile.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-distil-profile.js.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-ingest-session-text.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-ingest-session-text.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-ingest-session-text.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-ingest-session-text.js.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-record-feedback.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-record-feedback.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-retrieve-conditioning.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-retrieve-conditioning.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-retrieve-conditioning.js.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-tag-content.d.ts +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-tag-content.d.ts.map +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-tag-content.js +0 -0
- /package/payload/{premium-plugins/writer-craft → platform/plugins/voice-mirror}/mcp/dist/tools/voice-tag-content.js.map +0 -0
- /package/payload/server/public/assets/{useSubAccountSwitcher-CHEt_m6q.js → useSubAccountSwitcher-DF6bohQA.js} +0 -0
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* voice-ingest-session-text — write operator turns the agent read from its
|
|
3
|
-
* own session context as :Message {format:'text', source:'admin-pty'} corpus
|
|
4
|
-
* nodes.
|
|
5
|
-
*
|
|
6
|
-
* Invoked on demand by the voice-mirror skill (Task 644): the operator asks to
|
|
7
|
-
* capture this session's voice, the admin agent extracts its own operator
|
|
8
|
-
* turns from the live conversation context, and passes them here as `turns`.
|
|
9
|
-
* Re-running with the same turns is a no-op — MERGE on (contentHash, accountId)
|
|
10
|
-
* is the deduplication primitive.
|
|
11
|
-
*
|
|
12
|
-
* After ingest, triggers voice-distil-profile {format:'text'} so the cadence
|
|
13
|
-
* guard decides whether distillation runs.
|
|
14
|
-
*
|
|
15
|
-
* "Real operator text" filter (each supplied turn is excluded when):
|
|
16
|
-
* - text is empty after trimming → nothing to ingest
|
|
17
|
-
* - text starts with "/" → slash command
|
|
18
|
-
* - text contains "<system-reminder>" → harness injection
|
|
19
|
-
* - text.length >= 4096 → likely pasted content
|
|
20
|
-
*/
|
|
21
|
-
import { createHash } from "node:crypto";
|
|
22
|
-
import { getSession } from "../lib/neo4j.js";
|
|
23
|
-
import { voiceDistilProfile } from "./voice-distil-profile.js";
|
|
24
|
-
|
|
25
|
-
export interface VoiceIngestSessionTextParams {
|
|
26
|
-
turns: string[];
|
|
27
|
-
accountId: string;
|
|
28
|
-
userId: string;
|
|
29
|
-
sessionId?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface VoiceIngestSessionTextResult {
|
|
33
|
-
ingestedCount: number;
|
|
34
|
-
skippedCount: number;
|
|
35
|
-
sessionId: string | null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Applies the "real operator text" exclusions to a single supplied turn.
|
|
40
|
-
* Returns the trimmed text to ingest, or null when the turn should be skipped.
|
|
41
|
-
* Exported for testability (no DB required).
|
|
42
|
-
*/
|
|
43
|
-
export function filterOperatorText(text: unknown): string | null {
|
|
44
|
-
if (typeof text !== "string") return null;
|
|
45
|
-
const trimmed = text.trim();
|
|
46
|
-
if (trimmed.length === 0) return null;
|
|
47
|
-
if (trimmed.startsWith("/")) return null; // slash command
|
|
48
|
-
if (trimmed.includes("<system-reminder>")) return null; // harness injection
|
|
49
|
-
if (trimmed.length >= 4096) return null; // paste block
|
|
50
|
-
return trimmed;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function voiceIngestSessionText(
|
|
54
|
-
params: VoiceIngestSessionTextParams,
|
|
55
|
-
): Promise<VoiceIngestSessionTextResult> {
|
|
56
|
-
const { turns, accountId, userId, sessionId = null } = params;
|
|
57
|
-
|
|
58
|
-
if (!accountId || !userId) {
|
|
59
|
-
throw new Error("voice-ingest-session-text: accountId and userId required");
|
|
60
|
-
}
|
|
61
|
-
if (!Array.isArray(turns)) {
|
|
62
|
-
throw new Error("voice-ingest-session-text: turns array required");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 1. Apply the operator-text filter to each supplied turn.
|
|
66
|
-
const allTurns: string[] = [];
|
|
67
|
-
for (const turn of turns) {
|
|
68
|
-
const text = filterOperatorText(turn);
|
|
69
|
-
if (text !== null) allTurns.push(text);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (allTurns.length === 0) {
|
|
73
|
-
process.stderr.write(
|
|
74
|
-
`[voice-ingest-session-text] sessionId=${sessionId ?? "unknown"} adminUser=${userId} turns=0 bytes=0\n`,
|
|
75
|
-
);
|
|
76
|
-
return { ingestedCount: 0, skippedCount: 0, sessionId };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 2. Write each turn as a :Message node. MERGE on contentHash+accountId dedupes.
|
|
80
|
-
const session = getSession();
|
|
81
|
-
const now = new Date().toISOString();
|
|
82
|
-
let ingestedCount = 0;
|
|
83
|
-
let skippedCount = 0;
|
|
84
|
-
let totalBytes = 0;
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
for (const body of allTurns) {
|
|
88
|
-
const contentHash = createHash("sha256")
|
|
89
|
-
.update(body, "utf-8")
|
|
90
|
-
.digest("hex");
|
|
91
|
-
|
|
92
|
-
const result = await session.run(
|
|
93
|
-
`MATCH (a:AdminUser {accountId: $accountId, userId: $userId})
|
|
94
|
-
MERGE (n:Message {contentHash: $contentHash, accountId: $accountId})
|
|
95
|
-
ON CREATE SET
|
|
96
|
-
n.userId = $userId,
|
|
97
|
-
n.voiceAuthor = $userId,
|
|
98
|
-
n.body = $body,
|
|
99
|
-
n.occurredAt = $now,
|
|
100
|
-
n.sessionId = $sessionId,
|
|
101
|
-
n.source = 'admin-pty',
|
|
102
|
-
n.authorshipMode = 'human-only',
|
|
103
|
-
n.format = 'text',
|
|
104
|
-
n.createdAt = $now
|
|
105
|
-
MERGE (a)-[:AUTHORED]->(n)
|
|
106
|
-
RETURN n.createdAt = $now AS isNew`,
|
|
107
|
-
{
|
|
108
|
-
accountId,
|
|
109
|
-
userId,
|
|
110
|
-
contentHash,
|
|
111
|
-
body,
|
|
112
|
-
now,
|
|
113
|
-
sessionId,
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
if (result.records.length === 0) {
|
|
118
|
-
// :AdminUser not found — abort rather than orphan nodes.
|
|
119
|
-
throw new Error(
|
|
120
|
-
`voice-ingest-session-text: :AdminUser {accountId='${accountId}', userId='${userId}'} not found. Onboarding must promote the operator to :AdminUser before ingest.`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const isNew = result.records[0].get("isNew") === true;
|
|
125
|
-
if (isNew) {
|
|
126
|
-
ingestedCount++;
|
|
127
|
-
totalBytes += body.length;
|
|
128
|
-
} else {
|
|
129
|
-
skippedCount++;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
} finally {
|
|
133
|
-
await session.close();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
process.stderr.write(
|
|
137
|
-
`[voice-ingest-session-text] sessionId=${sessionId ?? "unknown"} adminUser=${userId} turns=${ingestedCount} bytes=${totalBytes} skipped=${skippedCount}\n`,
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// 3. Trigger text-format distillation (cadence guard decides whether to run).
|
|
141
|
-
if (ingestedCount > 0) {
|
|
142
|
-
try {
|
|
143
|
-
await voiceDistilProfile({ accountId, userId, format: "text" });
|
|
144
|
-
} catch (err) {
|
|
145
|
-
// Distil failure must not fail the ingest.
|
|
146
|
-
process.stderr.write(
|
|
147
|
-
`[voice-ingest-session-text] distil-after-ingest error: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return { ingestedCount, skippedCount, sessionId };
|
|
153
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* voice-record-feedback — capture an operator's edit on an agent draft
|
|
3
|
-
* as feedback for the next distillation.
|
|
4
|
-
*
|
|
5
|
-
* Task 424: the Haiku diff-intent summarisation moved into the calling
|
|
6
|
-
* agent's turn. The agent compares `originalText` to `editedText` itself
|
|
7
|
-
* and passes the resulting `intent` string as a required parameter. The
|
|
8
|
-
* tool now does the graph writes only.
|
|
9
|
-
*
|
|
10
|
-
* Task 462: `format` is required. `:VoiceEdit` carries the format; the
|
|
11
|
-
* `FEEDBACK_FOR` edge targets the per-format `:VoiceProfile`.
|
|
12
|
-
*
|
|
13
|
-
* Side effects:
|
|
14
|
-
* 1. Write `(:VoiceEdit)-[:FEEDBACK_FOR]->(:VoiceProfile {format})` if the
|
|
15
|
-
* per-format profile exists; otherwise write the :VoiceEdit alone so the
|
|
16
|
-
* next distillation back-fills the edge.
|
|
17
|
-
*/
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
19
|
-
import { getSession } from "../lib/neo4j.js";
|
|
20
|
-
import {
|
|
21
|
-
profileUserIdForScope,
|
|
22
|
-
type VoiceFormat,
|
|
23
|
-
type VoiceScope,
|
|
24
|
-
} from "../lib/voice-corpus.js";
|
|
25
|
-
|
|
26
|
-
export interface VoiceRecordFeedbackParams {
|
|
27
|
-
accountId: string;
|
|
28
|
-
userId: string;
|
|
29
|
-
originalText: string;
|
|
30
|
-
editedText: string;
|
|
31
|
-
/**
|
|
32
|
-
* Writing format of the draft being edited. Routes feedback to the
|
|
33
|
-
* matching per-format :VoiceProfile.
|
|
34
|
-
*/
|
|
35
|
-
format: VoiceFormat;
|
|
36
|
-
/**
|
|
37
|
-
* Scope of the edited draft (Task 676). `'org'` routes the edit to the
|
|
38
|
-
* account/house profile (`userId='__org__'`); `'personal'` (default) routes
|
|
39
|
-
* to the operator's own profile.
|
|
40
|
-
*/
|
|
41
|
-
scope?: VoiceScope;
|
|
42
|
-
/**
|
|
43
|
-
* One sentence describing what changed and what voice preference it reveals.
|
|
44
|
-
* The calling agent summarises this from `originalText` vs `editedText` in
|
|
45
|
-
* its own turn before invoking the tool (Task 424). Examples: "shorter and
|
|
46
|
-
* dropped the apology", "switched to first person", "removed corporate
|
|
47
|
-
* sign-off", "added a specific date".
|
|
48
|
-
*/
|
|
49
|
-
intent: string;
|
|
50
|
-
context?: {
|
|
51
|
-
skill?: string;
|
|
52
|
-
brief?: string;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface VoiceRecordFeedbackResult {
|
|
57
|
-
editId: string;
|
|
58
|
-
intent: string;
|
|
59
|
-
occurredAt: string;
|
|
60
|
-
hadProfile: boolean;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function voiceRecordFeedback(
|
|
64
|
-
params: VoiceRecordFeedbackParams,
|
|
65
|
-
): Promise<VoiceRecordFeedbackResult> {
|
|
66
|
-
const { accountId, userId, originalText, editedText, format, intent: rawIntent, context } = params;
|
|
67
|
-
const scope: VoiceScope = params.scope ?? "personal";
|
|
68
|
-
const profileUserId = profileUserIdForScope(scope, userId);
|
|
69
|
-
if (!accountId || !userId) {
|
|
70
|
-
throw new Error("voice-record-feedback: accountId and userId required");
|
|
71
|
-
}
|
|
72
|
-
if (!originalText || !editedText) {
|
|
73
|
-
throw new Error("voice-record-feedback: originalText and editedText required");
|
|
74
|
-
}
|
|
75
|
-
if (!format) {
|
|
76
|
-
throw new Error("voice-record-feedback: format is required");
|
|
77
|
-
}
|
|
78
|
-
const intent = (rawIntent ?? "").trim();
|
|
79
|
-
if (!intent) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"voice-record-feedback: intent is required — summarise what the operator changed in-turn before calling this tool (Task 424).",
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const editId = randomUUID();
|
|
86
|
-
const occurredAt = new Date().toISOString();
|
|
87
|
-
const cappedIntent = intent.slice(0, 500);
|
|
88
|
-
|
|
89
|
-
const session = getSession();
|
|
90
|
-
try {
|
|
91
|
-
// The :VoiceEdit carries the profile-key userId ($profileUserId — the
|
|
92
|
-
// sentinel for org) plus `scope`, so distillation's feedback query (keyed by
|
|
93
|
-
// profileUserId) reads org edits for the org profile and personal edits for
|
|
94
|
-
// the operator. The real editor is preserved via the AUTHORED edge from
|
|
95
|
-
// their :AdminUser. FEEDBACK_FOR targets the scoped profile (Task 676).
|
|
96
|
-
const result = await session.run(
|
|
97
|
-
`MATCH (a:AdminUser {accountId: $accountId, userId: $userId})
|
|
98
|
-
CREATE (e:VoiceEdit {
|
|
99
|
-
editId: $editId,
|
|
100
|
-
accountId: $accountId,
|
|
101
|
-
userId: $profileUserId,
|
|
102
|
-
scope: $scope,
|
|
103
|
-
format: $format,
|
|
104
|
-
originalText: $originalText,
|
|
105
|
-
editedText: $editedText,
|
|
106
|
-
intent: $intent,
|
|
107
|
-
occurredAt: $occurredAt,
|
|
108
|
-
skill: $skill,
|
|
109
|
-
brief: $brief
|
|
110
|
-
})
|
|
111
|
-
CREATE (a)-[:AUTHORED]->(e)
|
|
112
|
-
WITH e
|
|
113
|
-
OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
|
|
114
|
-
FOREACH (_ IN CASE WHEN p IS NULL THEN [] ELSE [1] END |
|
|
115
|
-
MERGE (e)-[:FEEDBACK_FOR]->(p)
|
|
116
|
-
)
|
|
117
|
-
RETURN elementId(e) AS editId, (p IS NOT NULL) AS hadProfile`,
|
|
118
|
-
{
|
|
119
|
-
accountId,
|
|
120
|
-
userId,
|
|
121
|
-
profileUserId,
|
|
122
|
-
scope,
|
|
123
|
-
format,
|
|
124
|
-
editId,
|
|
125
|
-
originalText,
|
|
126
|
-
editedText,
|
|
127
|
-
intent: cappedIntent,
|
|
128
|
-
occurredAt,
|
|
129
|
-
skill: context?.skill ?? null,
|
|
130
|
-
brief: context?.brief ?? null,
|
|
131
|
-
},
|
|
132
|
-
);
|
|
133
|
-
if (result.records.length === 0) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`voice-mirror: :AdminUser {accountId='${accountId}', userId='${userId}'} not found. Onboarding must promote the operator to :AdminUser before feedback can be recorded.`,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
const hadProfile = result.records[0].get("hadProfile") === true;
|
|
139
|
-
|
|
140
|
-
process.stderr.write(
|
|
141
|
-
`[voice-record-feedback] scope=${scope} userId=${profileUserId} format=${format} intent="${cappedIntent.replace(/"/g, "'")}" diffBytes=${editedText.length - originalText.length}\n`,
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
return { editId, intent: cappedIntent, occurredAt, hadProfile };
|
|
145
|
-
} finally {
|
|
146
|
-
await session.close();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* voice-retrieve-conditioning — return the operator's style card plus K
|
|
3
|
-
* voice-matched exemplars for a drafting brief.
|
|
4
|
-
*
|
|
5
|
-
* Task 462 changes:
|
|
6
|
-
* - `brief.format` (K-selector: 'short'|'long') renamed to `brief.length`
|
|
7
|
-
* to free the name for the new `brief.format: VoiceFormat` axis.
|
|
8
|
-
* - `brief.format: VoiceFormat` (required) — corpus exemplars are filtered
|
|
9
|
-
* to this writing format; the `:VoiceProfile` is the per-format profile.
|
|
10
|
-
* - All callers updated in the same commit.
|
|
11
|
-
*
|
|
12
|
-
* Retrieval order (best to worst):
|
|
13
|
-
* 1. If brief.topic is provided AND keyword search on the per-format corpus
|
|
14
|
-
* returns ≥K matches → use those (ordered by recency).
|
|
15
|
-
* 2. Otherwise, return the K most-recent per-format corpus nodes.
|
|
16
|
-
*
|
|
17
|
-
* Embeddings are not used in v1: the corpus is small (single operator's
|
|
18
|
-
* personal writing — typically <500 docs), and recency-with-keyword-filter
|
|
19
|
-
* is both faster and more predictable than vector ANN at that scale.
|
|
20
|
-
* Vector-search is a Task 365-follow-up if the corpus grows beyond the
|
|
21
|
-
* 500-doc breakeven point.
|
|
22
|
-
*
|
|
23
|
-
* Graceful degradation, discriminated (Task 493): the function never throws.
|
|
24
|
-
* Missing profile / empty corpus returns status "no-data"; a failed lookup
|
|
25
|
-
* (blank identity, driver/connection fault, Cypher error) returns status
|
|
26
|
-
* "error" with the message. The calling drafting skill falls back to its
|
|
27
|
-
* default register on both, but only "no-data" means "you have no profile".
|
|
28
|
-
*/
|
|
29
|
-
import neo4j, { type Session } from "neo4j-driver";
|
|
30
|
-
import { getSession } from "../lib/neo4j.js";
|
|
31
|
-
import { notTrashed } from "../lib/voice-corpus.js";
|
|
32
|
-
import {
|
|
33
|
-
voiceCorpusWhereWithFormat,
|
|
34
|
-
voiceCorpusWhereWithFormatAndAuthor,
|
|
35
|
-
ORG_USER_ID,
|
|
36
|
-
type VoiceFormat,
|
|
37
|
-
type VoiceScope,
|
|
38
|
-
} from "../lib/voice-corpus.js";
|
|
39
|
-
|
|
40
|
-
const DEFAULT_TOKEN_BUDGET_SHORT = 16_000;
|
|
41
|
-
const DEFAULT_TOKEN_BUDGET_LONG = 96_000;
|
|
42
|
-
const CHARS_PER_TOKEN = 4;
|
|
43
|
-
|
|
44
|
-
export interface VoiceRetrieveConditioningParams {
|
|
45
|
-
accountId: string;
|
|
46
|
-
userId: string;
|
|
47
|
-
brief: {
|
|
48
|
-
topic?: string;
|
|
49
|
-
/**
|
|
50
|
-
* Writing format for corpus and profile scoping (required, Task 462).
|
|
51
|
-
* One :VoiceProfile per (accountId, userId, format).
|
|
52
|
-
*/
|
|
53
|
-
format: VoiceFormat;
|
|
54
|
-
/**
|
|
55
|
-
* K-selector: 'short' = K=5 (16k token budget, email/post);
|
|
56
|
-
* 'long' = K=15 (96k token budget, brochure/prospectus).
|
|
57
|
-
* Renamed from `format` in Task 462 to free the name for VoiceFormat.
|
|
58
|
-
*/
|
|
59
|
-
length?: "short" | "long";
|
|
60
|
-
register?: string;
|
|
61
|
-
/**
|
|
62
|
-
* Voice scope (Task 676). `'personal'` (default) retrieves the caller's own
|
|
63
|
-
* profile and author-filtered exemplars, falling back to the org profile
|
|
64
|
-
* when the caller has none. `'org'` retrieves the account/house profile and
|
|
65
|
-
* account-wide exemplars, degrading to the default register when absent.
|
|
66
|
-
*/
|
|
67
|
-
scope?: VoiceScope;
|
|
68
|
-
};
|
|
69
|
-
tokenBudget?: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface VoiceExemplar {
|
|
73
|
-
nodeId: string;
|
|
74
|
-
label: string;
|
|
75
|
-
body: string;
|
|
76
|
-
source: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Outcome discriminator (Task 493). Three mutually-exclusive states so the
|
|
81
|
-
* drafting agent and operator can tell genuine no-data apart from a failed
|
|
82
|
-
* lookup — both previously collapsed into the same empty payload:
|
|
83
|
-
* - "ok" — query ran; a style card and/or exemplars were returned
|
|
84
|
-
* for the requested scope.
|
|
85
|
-
* - "fallback-org"— a personal request found no personal profile/corpus, so
|
|
86
|
-
* the org profile was returned instead (Task 676). Drafting
|
|
87
|
-
* uses the org voice; this is the standing signal that the
|
|
88
|
-
* caller has no personal attribution yet.
|
|
89
|
-
* - "no-data" — query ran; no profile exists and the corpus is empty for
|
|
90
|
-
* the requested scope (and, for personal, no org fallback).
|
|
91
|
-
* - "error" — the lookup failed (blank identity, driver/connection
|
|
92
|
-
* fault, or Cypher error). `error` carries the message.
|
|
93
|
-
* Drafting degrades gracefully to default register on "no-data" and "error";
|
|
94
|
-
* "fallback-org" still injects a (org) style card. The caller must only report
|
|
95
|
-
* "no profile exists" on "no-data".
|
|
96
|
-
*/
|
|
97
|
-
export type VoiceRetrieveStatus = "ok" | "fallback-org" | "no-data" | "error";
|
|
98
|
-
|
|
99
|
-
export interface VoiceRetrieveConditioningResult {
|
|
100
|
-
styleCard: string | null;
|
|
101
|
-
exemplars: VoiceExemplar[];
|
|
102
|
-
status: VoiceRetrieveStatus;
|
|
103
|
-
error?: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function errorResult(error: string): VoiceRetrieveConditioningResult {
|
|
107
|
-
return { styleCard: null, exemplars: [], status: "error", error };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function voiceRetrieveConditioning(
|
|
111
|
-
params: VoiceRetrieveConditioningParams,
|
|
112
|
-
): Promise<VoiceRetrieveConditioningResult> {
|
|
113
|
-
const { accountId, userId, brief } = params;
|
|
114
|
-
const scope: VoiceScope = brief.scope ?? "personal";
|
|
115
|
-
// Blank identity is an error, never genuine no-data — a blank value reaching
|
|
116
|
-
// here is a precondition failure, not "you have no profile". `accountId` is
|
|
117
|
-
// always required. `userId` is required only for personal scope: org scope is
|
|
118
|
-
// account-wide and keyed on the reserved ORG_USER_ID, so it resolves identity
|
|
119
|
-
// from `accountId` alone and never reads `userId` (Task 975). Emit a line so
|
|
120
|
-
// this path is no longer silent (Task 493 — closes the 06:46 path-1 ambiguity).
|
|
121
|
-
if (!accountId || (scope !== "org" && !userId)) {
|
|
122
|
-
process.stderr.write(
|
|
123
|
-
`[voice-retrieve] error: missing-identity scope=${scope} accountId=${Boolean(accountId)} userId=${Boolean(userId)}\n`,
|
|
124
|
-
);
|
|
125
|
-
return errorResult("missing accountId/userId");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const length = brief.length ?? "short";
|
|
129
|
-
const k = length === "long" ? 15 : 5;
|
|
130
|
-
const tokenBudget =
|
|
131
|
-
params.tokenBudget ??
|
|
132
|
-
(length === "long" ? DEFAULT_TOKEN_BUDGET_LONG : DEFAULT_TOKEN_BUDGET_SHORT);
|
|
133
|
-
const charBudget = tokenBudget * CHARS_PER_TOKEN;
|
|
134
|
-
const format = brief.format;
|
|
135
|
-
const topic = (brief.topic ?? "").trim();
|
|
136
|
-
const usesTopic = topic.length > 0;
|
|
137
|
-
|
|
138
|
-
// Body resolution (Task 471): for :KnowledgeDocument with HAS_SECTION
|
|
139
|
-
// children, concatenate child :Section bodies in `position` order (KD.body is
|
|
140
|
-
// null after Task 465 server-slicing); other labels read n.body via the
|
|
141
|
-
// COALESCE chain. The topic filter runs against the computed body.
|
|
142
|
-
const bodyComputeBlock = `WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
|
|
143
|
-
OPTIONAL MATCH (n)-[:HAS_SECTION]->(s:Section)
|
|
144
|
-
WHERE ${notTrashed("s")}
|
|
145
|
-
WITH n, ts, s ORDER BY s.position
|
|
146
|
-
WITH n, ts, collect(s.body) AS sectionBodies
|
|
147
|
-
WITH n, ts, CASE
|
|
148
|
-
WHEN 'KnowledgeDocument' IN labels(n) AND size(sectionBodies) > 0
|
|
149
|
-
THEN reduce(acc = '', b IN sectionBodies | acc + b + '\n\n')
|
|
150
|
-
ELSE coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '')
|
|
151
|
-
END AS body`;
|
|
152
|
-
|
|
153
|
-
// getSession() is inside the try so driver-construction / connection-config
|
|
154
|
-
// faults (NEO4J_URI unset, password file missing) are caught, logged, and
|
|
155
|
-
// returned as the error state rather than escaping the function silently
|
|
156
|
-
// (Task 493).
|
|
157
|
-
let session: Session | undefined;
|
|
158
|
-
try {
|
|
159
|
-
session = getSession();
|
|
160
|
-
|
|
161
|
-
// One scoped lookup: the profile style card (keyed on the profile's userId)
|
|
162
|
-
// plus K voice-matched exemplars over the scope's corpus. Org passes the
|
|
163
|
-
// account-wide corpus + `__org__`; personal passes the author-filtered
|
|
164
|
-
// corpus + the operator's userId. Each call gets a fresh char budget.
|
|
165
|
-
const lookup = async (
|
|
166
|
-
profileUserId: string,
|
|
167
|
-
exemplarWhere: string,
|
|
168
|
-
exemplarParams: Record<string, unknown>,
|
|
169
|
-
): Promise<{ styleCard: string | null; exemplars: VoiceExemplar[] }> => {
|
|
170
|
-
const profile = await session!.run(
|
|
171
|
-
`OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
|
|
172
|
-
RETURN p.styleCard AS styleCard`,
|
|
173
|
-
{ accountId, profileUserId, format },
|
|
174
|
-
);
|
|
175
|
-
const styleCard =
|
|
176
|
-
(profile.records[0]?.get("styleCard") as string | null) ?? null;
|
|
177
|
-
|
|
178
|
-
const cypher = usesTopic
|
|
179
|
-
? `MATCH (n)
|
|
180
|
-
WHERE ${exemplarWhere}
|
|
181
|
-
${bodyComputeBlock}
|
|
182
|
-
WHERE toLower(body) CONTAINS toLower($topic)
|
|
183
|
-
RETURN elementId(n) AS id, labels(n) AS labels, body,
|
|
184
|
-
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
185
|
-
ORDER BY ts IS NULL, ts DESC
|
|
186
|
-
LIMIT $k`
|
|
187
|
-
: `MATCH (n)
|
|
188
|
-
WHERE ${exemplarWhere}
|
|
189
|
-
${bodyComputeBlock}
|
|
190
|
-
RETURN elementId(n) AS id, labels(n) AS labels, body,
|
|
191
|
-
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
192
|
-
ORDER BY ts IS NULL, ts DESC
|
|
193
|
-
LIMIT $k`;
|
|
194
|
-
|
|
195
|
-
const result = await session!.run(cypher, {
|
|
196
|
-
...exemplarParams,
|
|
197
|
-
topic,
|
|
198
|
-
// neo4j-driver serializes a plain JS number as a Cypher float (15 →
|
|
199
|
-
// 15.0), which LIMIT rejects. Send a Neo4j integer.
|
|
200
|
-
k: neo4j.int(k),
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
let charsUsed = 0;
|
|
204
|
-
const exemplars: VoiceExemplar[] = [];
|
|
205
|
-
for (const r of result.records) {
|
|
206
|
-
const body = (r.get("body") as string | null) ?? "";
|
|
207
|
-
if (body.length === 0) continue;
|
|
208
|
-
const labels = (r.get("labels") as string[]) ?? [];
|
|
209
|
-
const label =
|
|
210
|
-
labels.find((l) =>
|
|
211
|
-
["KnowledgeDocument", "Message", "SocialPost", "Conversation"].includes(l),
|
|
212
|
-
) ?? labels[0] ?? "Unknown";
|
|
213
|
-
const remaining = charBudget - charsUsed;
|
|
214
|
-
if (remaining <= 0) break;
|
|
215
|
-
const truncated = body.length > remaining ? body.slice(0, remaining) : body;
|
|
216
|
-
exemplars.push({
|
|
217
|
-
nodeId: r.get("id") as string,
|
|
218
|
-
label,
|
|
219
|
-
body: truncated,
|
|
220
|
-
source: ((r.get("source") as string | null) ?? "").slice(0, 200),
|
|
221
|
-
});
|
|
222
|
-
charsUsed += truncated.length;
|
|
223
|
-
}
|
|
224
|
-
return { styleCard, exemplars };
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const hasData = (r: { styleCard: string | null; exemplars: VoiceExemplar[] }) =>
|
|
228
|
-
(r.styleCard !== null && r.styleCard.length > 0) || r.exemplars.length > 0;
|
|
229
|
-
|
|
230
|
-
const orgLookup = () =>
|
|
231
|
-
lookup(ORG_USER_ID, voiceCorpusWhereWithFormat(format), { accountId, format });
|
|
232
|
-
|
|
233
|
-
let result: { styleCard: string | null; exemplars: VoiceExemplar[] };
|
|
234
|
-
let status: VoiceRetrieveStatus;
|
|
235
|
-
if (scope === "personal") {
|
|
236
|
-
const personal = await lookup(
|
|
237
|
-
userId,
|
|
238
|
-
voiceCorpusWhereWithFormatAndAuthor(format),
|
|
239
|
-
{ accountId, format, voiceAuthor: userId },
|
|
240
|
-
);
|
|
241
|
-
if (hasData(personal)) {
|
|
242
|
-
result = personal;
|
|
243
|
-
status = "ok";
|
|
244
|
-
} else {
|
|
245
|
-
// No personal profile/corpus yet — borrow the org (house) voice rather
|
|
246
|
-
// than the bare default register (Task 676).
|
|
247
|
-
const org = await orgLookup();
|
|
248
|
-
result = org;
|
|
249
|
-
status = hasData(org) ? "fallback-org" : "no-data";
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
const org = await orgLookup();
|
|
253
|
-
result = org;
|
|
254
|
-
status = hasData(org) ? "ok" : "no-data";
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
process.stderr.write(
|
|
258
|
-
`[voice-retrieve] scope=${scope} format=${format} status=${status} ` +
|
|
259
|
-
`styleCardBytes=${result.styleCard?.length ?? 0} exemplarCount=${result.exemplars.length} tokenBudget=${tokenBudget}\n`,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
return { styleCard: result.styleCard, exemplars: result.exemplars, status };
|
|
263
|
-
} catch (err) {
|
|
264
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
265
|
-
process.stderr.write(`[voice-retrieve] error: ${message}\n`);
|
|
266
|
-
return errorResult(message);
|
|
267
|
-
} finally {
|
|
268
|
-
if (session) await session.close();
|
|
269
|
-
}
|
|
270
|
-
}
|