@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,457 +0,0 @@
|
|
|
1
|
-
import { initStderrTee } from "../../../../platform/lib/mcp-stderr-tee/dist/index.js";
|
|
2
|
-
import { lifelineTool } from "../../../../platform/lib/mcp-lifeline/dist/index.js";
|
|
3
|
-
initStderrTee("writer-craft");
|
|
4
|
-
|
|
5
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { voiceTagContent, resolveVoiceAuthor, AUTHORSHIP_MODES, type AuthorshipMode } from "./tools/voice-tag-content.js";
|
|
9
|
-
import type { VoiceFormat } from "./lib/voice-corpus.js";
|
|
10
|
-
import { voiceDistilProfile } from "./tools/voice-distil-profile.js";
|
|
11
|
-
import { voiceRetrieveConditioning } from "./tools/voice-retrieve-conditioning.js";
|
|
12
|
-
import { voiceRecordFeedback } from "./tools/voice-record-feedback.js";
|
|
13
|
-
import { voiceIngestSessionText } from "./tools/voice-ingest-session-text.js";
|
|
14
|
-
import { FORMAT_VALUES } from "./lib/voice-corpus.js";
|
|
15
|
-
import { closeDriver } from "./lib/neo4j.js";
|
|
16
|
-
|
|
17
|
-
const server = new McpServer({
|
|
18
|
-
name: "maxy-writer-craft",
|
|
19
|
-
version: "0.1.0",
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Plugin-system boot tolerance — module-init must never throw. Per-call
|
|
23
|
-
// refusal preserves account isolation. Mirrors the memory plugin's
|
|
24
|
-
// pattern (Task 202).
|
|
25
|
-
const accountId = process.env.ACCOUNT_ID ?? null;
|
|
26
|
-
const userIdEnv = process.env.ADMIN_USER_ID ?? null;
|
|
27
|
-
process.stderr.write(
|
|
28
|
-
`[writer-craft] boot accountId=${accountId ?? "null"} userId=${userIdEnv ?? "null"}\n`,
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
function refuseNoAccount(toolName: string) {
|
|
32
|
-
process.stderr.write(
|
|
33
|
-
`[writer-craft] tool=${toolName} refuse reason=no-account-context\n`,
|
|
34
|
-
);
|
|
35
|
-
return {
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: "text" as const,
|
|
39
|
-
text: `${toolName} cannot execute: this MCP instance was launched without ACCOUNT_ID. Per-account graph access requires ACCOUNT_ID at spawn.`,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
isError: true as const,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function resolveUserId(): string | null {
|
|
47
|
-
// ADMIN_USER_ID is stamped by the admin session manager at spawn time.
|
|
48
|
-
// When absent (e.g. specialist subagents), the operator must pass it as
|
|
49
|
-
// a tool argument or the tools refuse rather than orphan a profile.
|
|
50
|
-
return userIdEnv;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Format enum schema — shared across tools that accept VoiceFormat.
|
|
54
|
-
const zFormat = z.enum([...FORMAT_VALUES] as [string, ...string[]]).describe(
|
|
55
|
-
"Writing format: text | email | social-post | article | marketing-copy | note. One :VoiceProfile per (accountId, userId, format).",
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Scope enum schema — shared across distil / retrieve / feedback (Task 676).
|
|
59
|
-
const zScope = z
|
|
60
|
-
.enum(["personal", "org"])
|
|
61
|
-
.optional()
|
|
62
|
-
.describe(
|
|
63
|
-
"Voice scope: 'personal' (default) = the operator's own voice, distilled from their author-tagged content and anchored on :AdminUser. 'org' = the account/house voice, distilled from the whole account and anchored on :LocalBusiness under the reserved userId '__org__'.",
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
lifelineTool(server,
|
|
67
|
-
"voice-tag-content",
|
|
68
|
-
"Stamp authorshipMode and format on one or many content nodes (:KnowledgeDocument | :Message | :SocialPost) the operator owns. Email threads live as :KnowledgeDocument {source:'email'} since Task 321. Valid modes: human-only, human-led-agent-assisted, agent-led-human-reviewed, agent-only, unknown. Valid formats: text, email, social-post, article, marketing-copy, note. format is editorial — the operator chooses it per node; the tool does not infer it.",
|
|
69
|
-
{
|
|
70
|
-
nodeIds: z.array(z.string()).min(1).describe("Element IDs of nodes to tag"),
|
|
71
|
-
mode: z
|
|
72
|
-
.enum([
|
|
73
|
-
"human-only",
|
|
74
|
-
"human-led-agent-assisted",
|
|
75
|
-
"agent-led-human-reviewed",
|
|
76
|
-
"agent-only",
|
|
77
|
-
"unknown",
|
|
78
|
-
])
|
|
79
|
-
.describe("Authorship mode to apply"),
|
|
80
|
-
format: zFormat.describe(
|
|
81
|
-
"Writing format. Required — the operator's editorial classification of the content.",
|
|
82
|
-
),
|
|
83
|
-
userId: z
|
|
84
|
-
.string()
|
|
85
|
-
.optional()
|
|
86
|
-
.describe(
|
|
87
|
-
"Operator AdminUser id. Defaults to ADMIN_USER_ID env. Used as the default author when `author` is omitted.",
|
|
88
|
-
),
|
|
89
|
-
author: z
|
|
90
|
-
.string()
|
|
91
|
-
.optional()
|
|
92
|
-
.describe(
|
|
93
|
-
"voiceAuthor userId to stamp on the nodes. Omit to attribute to the tagging operator — single-operator accounts get personal attribution for free.",
|
|
94
|
-
),
|
|
95
|
-
},
|
|
96
|
-
async ({ nodeIds, mode, format, userId, author }) => {
|
|
97
|
-
if (!accountId) return refuseNoAccount("voice-tag-content");
|
|
98
|
-
let voiceAuthor: string;
|
|
99
|
-
try {
|
|
100
|
-
voiceAuthor = resolveVoiceAuthor(author, userId ?? resolveUserId());
|
|
101
|
-
} catch (err) {
|
|
102
|
-
return {
|
|
103
|
-
content: [
|
|
104
|
-
{
|
|
105
|
-
type: "text" as const,
|
|
106
|
-
text: err instanceof Error ? err.message : String(err),
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
isError: true,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const result = await voiceTagContent({
|
|
114
|
-
nodeIds,
|
|
115
|
-
mode: mode as AuthorshipMode,
|
|
116
|
-
accountId,
|
|
117
|
-
format: format as VoiceFormat,
|
|
118
|
-
voiceAuthor,
|
|
119
|
-
});
|
|
120
|
-
return {
|
|
121
|
-
content: [
|
|
122
|
-
{
|
|
123
|
-
type: "text" as const,
|
|
124
|
-
text: `voice-tag: mode=${mode} format=${format} author=${voiceAuthor} updated=${result.updated} skipped=${result.skipped}`,
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
} catch (err) {
|
|
129
|
-
return {
|
|
130
|
-
content: [
|
|
131
|
-
{
|
|
132
|
-
type: "text" as const,
|
|
133
|
-
text: `voice-tag-content failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
isError: true,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
lifelineTool(server,
|
|
143
|
-
"voice-distil-profile",
|
|
144
|
-
"Three-mode tool. mode='sample' (default) walks the operator's full per-format human-only corpus and returns exemplars + recent edit intents for the agent to compose a fresh style card; cadence-guarded (≥20% corpus growth OR ≥30 days). mode='amend' (Task 472) reads only the named documents plus the existing :VoiceProfile.styleCard so the agent can decide whether one or more specific documents move the profile — operator-initiated, no cadence guard. mode='write' persists the YAML style card the agent composed; supply amendedFromNodeIds to attribute the write to specific documents (Task 472, bypasses cadence + corpus walk; existing LEARNED_FROM edges remain via MERGE). Trashed nodes are excluded from every walk. When format is omitted on 'sample', enumerates all formats in the corpus and distils each. scope (Task 676): 'personal' (default) distils the operator's author-tagged content; 'org' distils the whole account into the house voice anchored on the business.",
|
|
145
|
-
{
|
|
146
|
-
userId: z
|
|
147
|
-
.string()
|
|
148
|
-
.optional()
|
|
149
|
-
.describe(
|
|
150
|
-
"Operator AdminUser identifier. Defaults to ADMIN_USER_ID env from spawn context.",
|
|
151
|
-
),
|
|
152
|
-
format: zFormat.optional().describe(
|
|
153
|
-
"Writing format to distil. Required for mode='amend' and mode='write'. When omitted on 'sample', enumerates corpus formats and distils each that fires the cadence guard.",
|
|
154
|
-
),
|
|
155
|
-
force: z
|
|
156
|
-
.boolean()
|
|
157
|
-
.optional()
|
|
158
|
-
.describe("Override cadence guard on full-corpus sample/write; ignored by mode='amend' (always proceeds)."),
|
|
159
|
-
mode: z
|
|
160
|
-
.enum(["sample", "amend", "write"])
|
|
161
|
-
.optional()
|
|
162
|
-
.describe("'sample' returns full-corpus exemplars + feedback for in-turn distillation; 'amend' returns existing styleCard + bodies of one or more named documents for incremental update; 'write' persists the agent-produced styleCardYaml."),
|
|
163
|
-
styleCardYaml: z
|
|
164
|
-
.string()
|
|
165
|
-
.optional()
|
|
166
|
-
.describe("Required for mode='write'. The YAML style card the agent composed (from the sample exemplars + feedback intents, or from the amend bodies + existing card)."),
|
|
167
|
-
nodeIds: z
|
|
168
|
-
.array(z.string())
|
|
169
|
-
.optional()
|
|
170
|
-
.describe(
|
|
171
|
-
"Required for mode='amend'. ElementIds of the documents the operator wants folded into the per-format profile (one or more). Each id is eligibility-checked; ineligible ones come back in the ineligible[] payload.",
|
|
172
|
-
),
|
|
173
|
-
amendedFromNodeIds: z
|
|
174
|
-
.array(z.string())
|
|
175
|
-
.optional()
|
|
176
|
-
.describe(
|
|
177
|
-
"Optional for mode='write' after an amend sample. ElementIds the agent decided actually moved the profile. Each becomes a LEARNED_FROM edge from the persisted :VoiceProfile. Setting this routes the write through the amend-write path (no corpus walk, cadence bypassed).",
|
|
178
|
-
),
|
|
179
|
-
scope: zScope,
|
|
180
|
-
},
|
|
181
|
-
async ({ userId, format, force, mode, styleCardYaml, nodeIds, amendedFromNodeIds, scope }) => {
|
|
182
|
-
if (!accountId) return refuseNoAccount("voice-distil-profile");
|
|
183
|
-
const resolvedUserId = userId ?? resolveUserId();
|
|
184
|
-
if (!resolvedUserId) {
|
|
185
|
-
return {
|
|
186
|
-
content: [
|
|
187
|
-
{
|
|
188
|
-
type: "text" as const,
|
|
189
|
-
text:
|
|
190
|
-
"voice-distil-profile: userId required (no ADMIN_USER_ID in spawn env).",
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
isError: true,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
try {
|
|
197
|
-
const result = await voiceDistilProfile({
|
|
198
|
-
accountId,
|
|
199
|
-
userId: resolvedUserId,
|
|
200
|
-
format: format as import("./lib/voice-corpus.js").VoiceFormat | undefined,
|
|
201
|
-
force: force === true,
|
|
202
|
-
mode,
|
|
203
|
-
styleCardYaml,
|
|
204
|
-
nodeIds,
|
|
205
|
-
amendedFromNodeIds,
|
|
206
|
-
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Summarise single-format or multi-format result.
|
|
210
|
-
const summariseOne = (r: import("./tools/voice-distil-profile.js").VoiceDistilProfileResult, fmt?: string): string => {
|
|
211
|
-
const tag = fmt ? ` format=${fmt}` : "";
|
|
212
|
-
if (r.eligible !== undefined) {
|
|
213
|
-
// amend sample
|
|
214
|
-
return `voice-distil amend${tag}: eligible=${r.eligible} ineligible=${r.ineligible?.length ?? 0} exemplars=${r.exemplars?.length ?? 0} existingProfile=${r.profileId ?? "none"} feedbackEntries=${r.feedbackEntries}\n${JSON.stringify({ existingStyleCard: r.existingStyleCard, exemplars: r.exemplars, feedbackIntents: r.feedbackIntents, ineligible: r.ineligible }, null, 2)}`;
|
|
215
|
-
}
|
|
216
|
-
if (r.skipped && r.exemplars) {
|
|
217
|
-
return `voice-distil sample${tag}: corpusSize=${r.corpusSize} exemplars=${r.exemplars.length} feedbackEntries=${r.feedbackEntries}\n${JSON.stringify({ exemplars: r.exemplars, feedbackIntents: r.feedbackIntents }, null, 2)}`;
|
|
218
|
-
}
|
|
219
|
-
if (r.skipped) {
|
|
220
|
-
return `voice-distil${tag}: skipped reason=${r.skipReason} corpusSize=${r.corpusSize}`;
|
|
221
|
-
}
|
|
222
|
-
return `voice-distil${tag}: ok profileId=${r.profileId} corpusSize=${r.corpusSize} feedbackEntries=${r.feedbackEntries} generatedAt=${r.generatedAt}`;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
let summary: string;
|
|
226
|
-
if (Array.isArray(result)) {
|
|
227
|
-
summary = result.map((r) => summariseOne(r, r.format)).join("\n---\n");
|
|
228
|
-
} else {
|
|
229
|
-
summary = summariseOne(result, format);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { content: [{ type: "text" as const, text: summary }] };
|
|
233
|
-
} catch (err) {
|
|
234
|
-
return {
|
|
235
|
-
content: [
|
|
236
|
-
{
|
|
237
|
-
type: "text" as const,
|
|
238
|
-
text: `voice-distil-profile failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
isError: true,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
lifelineTool(server,
|
|
248
|
-
"voice-retrieve-conditioning",
|
|
249
|
-
"Return the operator's per-format style card and K voice-matched exemplars for a drafting brief. K=5 short-form (length='short'), K=15 long-form (length='long'). Token-budget bounded. format is required (the writing type: text|email|social-post|article|marketing-copy|note). scope (Task 676): 'personal' (default) returns the caller's own voice; 'org' returns the account/house voice. A personal request with no personal profile/corpus falls back to the org profile (status 'fallback-org'). Returns {styleCard, exemplars, status} where status is one of: 'ok', 'fallback-org' (personal asked, org returned), 'no-data' (no profile and empty corpus for the scope), 'error' (lookup failed — carries a message). Drafting falls back to default register on 'no-data' and 'error' and injects the org card on 'fallback-org'; only 'no-data' means 'you have no voice profile yet' — never report no-profile on 'error'.",
|
|
250
|
-
{
|
|
251
|
-
userId: z
|
|
252
|
-
.string()
|
|
253
|
-
.optional()
|
|
254
|
-
.describe("Operator AdminUser identifier. Defaults to ADMIN_USER_ID env."),
|
|
255
|
-
format: zFormat.describe("Writing format for corpus and profile scoping."),
|
|
256
|
-
topic: z
|
|
257
|
-
.string()
|
|
258
|
-
.optional()
|
|
259
|
-
.describe("Drafting topic keyword (filters exemplars to relevant ones)"),
|
|
260
|
-
length: z
|
|
261
|
-
.enum(["short", "long"])
|
|
262
|
-
.optional()
|
|
263
|
-
.describe("K-selector: short = K=5 (16k tokens, email/post); long = K=15 (96k tokens, brochure/prospectus). Defaults to short."),
|
|
264
|
-
register: z.string().optional().describe("Optional register hint"),
|
|
265
|
-
scope: zScope,
|
|
266
|
-
tokenBudget: z.number().int().positive().optional(),
|
|
267
|
-
},
|
|
268
|
-
async ({ userId, format, topic, length, register, scope, tokenBudget }) => {
|
|
269
|
-
if (!accountId) return refuseNoAccount("voice-retrieve-conditioning");
|
|
270
|
-
const resolvedUserId = userId ?? resolveUserId();
|
|
271
|
-
// org scope is account-wide (keyed on ORG_USER_ID + accountId) and never
|
|
272
|
-
// reads a userId, so a specialist spawn that carries ACCOUNT_ID but no
|
|
273
|
-
// ADMIN_USER_ID can still retrieve the house voice. Only personal scope
|
|
274
|
-
// requires a resolved userId (Task 975).
|
|
275
|
-
if ((scope ?? "personal") !== "org" && !resolvedUserId) {
|
|
276
|
-
// Soft (no isError) so drafting degrades, but status:error keeps this
|
|
277
|
-
// distinct from genuine no-data (Task 493).
|
|
278
|
-
return {
|
|
279
|
-
content: [
|
|
280
|
-
{
|
|
281
|
-
type: "text" as const,
|
|
282
|
-
text: JSON.stringify({
|
|
283
|
-
styleCard: null,
|
|
284
|
-
exemplars: [],
|
|
285
|
-
status: "error",
|
|
286
|
-
error: "no userId (no ADMIN_USER_ID in spawn env)",
|
|
287
|
-
}),
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
try {
|
|
293
|
-
const result = await voiceRetrieveConditioning({
|
|
294
|
-
accountId,
|
|
295
|
-
// org scope ignores userId; pass through for personal scope.
|
|
296
|
-
userId: resolvedUserId ?? "",
|
|
297
|
-
brief: {
|
|
298
|
-
format: format as import("./lib/voice-corpus.js").VoiceFormat,
|
|
299
|
-
topic,
|
|
300
|
-
length: length as "short" | "long" | undefined,
|
|
301
|
-
register,
|
|
302
|
-
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
303
|
-
},
|
|
304
|
-
tokenBudget,
|
|
305
|
-
});
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{ type: "text" as const, text: JSON.stringify(result) },
|
|
309
|
-
],
|
|
310
|
-
};
|
|
311
|
-
} catch (err) {
|
|
312
|
-
// Graceful degradation: never an error response (no isError) so drafting
|
|
313
|
-
// does not crash — but the payload carries status:error so the failure
|
|
314
|
-
// is not masquerading as no-data (Task 493).
|
|
315
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
316
|
-
process.stderr.write(`[voice-retrieve] tool error: ${message}\n`);
|
|
317
|
-
return {
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: "text" as const,
|
|
321
|
-
text: JSON.stringify({
|
|
322
|
-
styleCard: null,
|
|
323
|
-
exemplars: [],
|
|
324
|
-
status: "error",
|
|
325
|
-
error: message,
|
|
326
|
-
}),
|
|
327
|
-
},
|
|
328
|
-
],
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
lifelineTool(server,
|
|
335
|
-
"voice-record-feedback",
|
|
336
|
-
"Capture an operator's edit on an agent draft. Writes :VoiceEdit linked to the per-format :VoiceProfile via :FEEDBACK_FOR. The calling agent supplies `intent` (one sentence describing what changed and what voice preference it reveals — Task 424) and `format` (the writing format of the draft). scope (Task 676): 'org' routes the edit to the account/house profile; 'personal' (default) to the operator's own. The editor is always preserved via the AUTHORED edge.",
|
|
337
|
-
{
|
|
338
|
-
userId: z.string().optional(),
|
|
339
|
-
format: zFormat.describe("Writing format of the draft being edited."),
|
|
340
|
-
originalText: z.string().min(1),
|
|
341
|
-
editedText: z.string().min(1),
|
|
342
|
-
intent: z
|
|
343
|
-
.string()
|
|
344
|
-
.min(1)
|
|
345
|
-
.describe("One sentence summarising what changed and what voice preference it reveals — produced by the calling agent in-turn from originalText vs editedText (Task 424)."),
|
|
346
|
-
skill: z.string().optional().describe("Calling skill name, for provenance"),
|
|
347
|
-
brief: z.string().optional().describe("Drafting brief that produced the original"),
|
|
348
|
-
scope: zScope,
|
|
349
|
-
},
|
|
350
|
-
async ({ userId, format, originalText, editedText, intent, skill, brief, scope }) => {
|
|
351
|
-
if (!accountId) return refuseNoAccount("voice-record-feedback");
|
|
352
|
-
const resolvedUserId = userId ?? resolveUserId();
|
|
353
|
-
if (!resolvedUserId) {
|
|
354
|
-
return {
|
|
355
|
-
content: [
|
|
356
|
-
{
|
|
357
|
-
type: "text" as const,
|
|
358
|
-
text: "voice-record-feedback: userId required (no ADMIN_USER_ID in spawn env).",
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
isError: true,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
try {
|
|
365
|
-
const result = await voiceRecordFeedback({
|
|
366
|
-
accountId,
|
|
367
|
-
userId: resolvedUserId,
|
|
368
|
-
format: format as import("./lib/voice-corpus.js").VoiceFormat,
|
|
369
|
-
originalText,
|
|
370
|
-
editedText,
|
|
371
|
-
intent,
|
|
372
|
-
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
373
|
-
context: { skill, brief },
|
|
374
|
-
});
|
|
375
|
-
return {
|
|
376
|
-
content: [
|
|
377
|
-
{
|
|
378
|
-
type: "text" as const,
|
|
379
|
-
text: `voice-record-feedback: editId=${result.editId} format=${format} intent="${result.intent}" hadProfile=${result.hadProfile}`,
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
};
|
|
383
|
-
} catch (err) {
|
|
384
|
-
return {
|
|
385
|
-
content: [
|
|
386
|
-
{
|
|
387
|
-
type: "text" as const,
|
|
388
|
-
text: `voice-record-feedback failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
isError: true,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
lifelineTool(server,
|
|
398
|
-
"voice-ingest-session-text",
|
|
399
|
-
"Write operator turns from the current session as :Message {format:'text', source:'admin-pty', authorshipMode:'human-only'} corpus nodes. The admin agent reads its own operator turns from the live conversation context and passes them as `turns`. Deduplication via SHA256 contentHash — re-running with the same turns is a no-op. Triggers voice-distil-profile {format:'text'} after ingest (cadence guard decides whether distillation runs). Invoked on demand by the voice-mirror skill when the operator asks to capture this session's voice.",
|
|
400
|
-
{
|
|
401
|
-
turns: z.array(z.string()).describe("Operator turns the agent read from its own session context (one string per operator message). Slash commands, harness injections, and large paste blocks are filtered out by the tool."),
|
|
402
|
-
sessionId: z.string().optional().describe("Session ID for provenance."),
|
|
403
|
-
userId: z.string().optional().describe("Operator AdminUser identifier. Defaults to ADMIN_USER_ID env."),
|
|
404
|
-
},
|
|
405
|
-
async ({ turns, sessionId, userId }) => {
|
|
406
|
-
if (!accountId) return refuseNoAccount("voice-ingest-session-text");
|
|
407
|
-
const resolvedUserId = userId ?? resolveUserId();
|
|
408
|
-
if (!resolvedUserId) {
|
|
409
|
-
return {
|
|
410
|
-
content: [
|
|
411
|
-
{
|
|
412
|
-
type: "text" as const,
|
|
413
|
-
text: "voice-ingest-session-text: userId required (no ADMIN_USER_ID in spawn env).",
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
isError: true,
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
try {
|
|
420
|
-
const result = await voiceIngestSessionText({
|
|
421
|
-
turns,
|
|
422
|
-
accountId,
|
|
423
|
-
userId: resolvedUserId,
|
|
424
|
-
sessionId,
|
|
425
|
-
});
|
|
426
|
-
return {
|
|
427
|
-
content: [
|
|
428
|
-
{
|
|
429
|
-
type: "text" as const,
|
|
430
|
-
text: `voice-ingest-session-text: ingestedCount=${result.ingestedCount} skippedCount=${result.skippedCount} sessionId=${result.sessionId ?? "unknown"}`,
|
|
431
|
-
},
|
|
432
|
-
],
|
|
433
|
-
};
|
|
434
|
-
} catch (err) {
|
|
435
|
-
return {
|
|
436
|
-
content: [
|
|
437
|
-
{
|
|
438
|
-
type: "text" as const,
|
|
439
|
-
text: `voice-ingest-session-text failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
440
|
-
},
|
|
441
|
-
],
|
|
442
|
-
isError: true,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
const transport = new StdioServerTransport();
|
|
449
|
-
await server.connect(transport);
|
|
450
|
-
|
|
451
|
-
process.on("SIGTERM", async () => {
|
|
452
|
-
await closeDriver();
|
|
453
|
-
process.exit(0);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// Surface AUTHORSHIP_MODES for downstream callers / docs.
|
|
457
|
-
export { AUTHORSHIP_MODES };
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal neo4j driver wrapper for the writer-craft MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the public surface of `platform/plugins/memory/mcp/src/lib/neo4j.ts`:
|
|
5
|
-
* `getSession()` returns a session, `closeDriver()` releases the singleton.
|
|
6
|
-
* Password resolution follows the same `PLATFORM_ROOT/config/.neo4j-password`
|
|
7
|
-
* convention, with an explicit `NEO4J_URI` requirement to avoid silent
|
|
8
|
-
* cross-account orphaning.
|
|
9
|
-
*/
|
|
10
|
-
import neo4j, { Driver, Session } from "neo4j-driver";
|
|
11
|
-
import { readFileSync } from "node:fs";
|
|
12
|
-
import { resolve } from "node:path";
|
|
13
|
-
|
|
14
|
-
let driver: Driver | null = null;
|
|
15
|
-
|
|
16
|
-
function readPassword(): string {
|
|
17
|
-
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
18
|
-
const passwordFile = resolve(
|
|
19
|
-
process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../../../platform"),
|
|
20
|
-
"config/.neo4j-password",
|
|
21
|
-
);
|
|
22
|
-
try {
|
|
23
|
-
return readFileSync(passwordFile, "utf-8").trim();
|
|
24
|
-
} catch {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function getDriver(): Driver {
|
|
32
|
-
if (!driver) {
|
|
33
|
-
const uri = process.env.NEO4J_URI;
|
|
34
|
-
if (!uri) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
"[writer-craft] NEO4J_URI unset — refusing to default to bolt://localhost:7687",
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
40
|
-
const password = readPassword();
|
|
41
|
-
process.stderr.write(`[writer-craft] resolved neo4j_uri=${uri}\n`);
|
|
42
|
-
driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
|
|
43
|
-
}
|
|
44
|
-
return driver;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function getSession(): Session {
|
|
48
|
-
return getDriver().session();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function closeDriver(): Promise<void> {
|
|
52
|
-
if (driver) {
|
|
53
|
-
await driver.close();
|
|
54
|
-
driver = null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cypher fragment that excludes trashed nodes for the given alias.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the platform's soft-delete model: a node is trashed if it
|
|
5
|
-
* carries the `:Trashed` label OR a non-null `deletedAt` timestamp.
|
|
6
|
-
* Inlined here (Task 473) instead of imported from
|
|
7
|
-
* `platform/lib/graph-trash` — the cross-package import path did not
|
|
8
|
-
* survive `tsc` emit (the `rootDir: "src"` config collapsed
|
|
9
|
-
* `../../../../../platform/...` to `../../...`, pointing the dist
|
|
10
|
-
* file at a non-existent location and crashing the MCP on boot). The
|
|
11
|
-
* predicate is one line; copying it costs less than the build coupling.
|
|
12
|
-
*/
|
|
13
|
-
export function notTrashed(alias: string): string {
|
|
14
|
-
return `(NOT \`${alias}\`:Trashed AND \`${alias}\`.deletedAt IS NULL)`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Shared corpus-filter clauses and format helpers for the voice-mirror tools.
|
|
19
|
-
*
|
|
20
|
-
* Imported by `voice-distil-profile` (count + sample walks),
|
|
21
|
-
* `voice-retrieve-conditioning` (exemplar walk), `voice-tag-content` (tag
|
|
22
|
-
* writes), and `voice-ingest-session-text` (on-demand session-text ingest). The single
|
|
23
|
-
* load-bearing filter — when the corpus rules change, this is the only place
|
|
24
|
-
* to edit.
|
|
25
|
-
*
|
|
26
|
-
* Three predicates in VOICE_CORPUS_WHERE:
|
|
27
|
-
*
|
|
28
|
-
* 1. `authorshipMode IN ['human-only', 'human-led-agent-assisted']`.
|
|
29
|
-
* `human-only` is the operator's own writing verbatim;
|
|
30
|
-
* `human-led-agent-assisted` is operator-authored with light agent
|
|
31
|
-
* polish, which still carries voice signal (the operator chose the
|
|
32
|
-
* claims and the structure). Including this expands the available
|
|
33
|
-
* corpus to match how most modern writing actually happens.
|
|
34
|
-
*
|
|
35
|
-
* 2. Label is one of the four primary content labels OR is a
|
|
36
|
-
* conversation-document `:Section` chunk (unguarded chat voice from
|
|
37
|
-
* WhatsApp / Telegram / Signal / Granola / Otter / Circleback etc.
|
|
38
|
-
* archives). After the Task 324 collapse, conversation transcripts
|
|
39
|
-
* land as `:KnowledgeDocument` parents carrying `conversationIdentity`
|
|
40
|
-
* with plain `:Section` children — no `:Conversation` secondary label.
|
|
41
|
-
* Conversation-document sections are identified by their parent's
|
|
42
|
-
* `conversationIdentity` property via an EXISTS sub-pattern. The
|
|
43
|
-
* conversation parent must itself be live — a trashed parent excludes
|
|
44
|
-
* its child chunks.
|
|
45
|
-
* Chat captures voice in a way email rarely does — this is the
|
|
46
|
-
* single highest-leverage corpus addition for voice fidelity.
|
|
47
|
-
*
|
|
48
|
-
* 3. The node is not trashed — `notTrashed("n")` filters both the
|
|
49
|
-
* `:Trashed` label and the `deletedAt` property (Task 472). This is
|
|
50
|
-
* the read-side equivalent of `graph-trash`'s soft-delete model: a
|
|
51
|
-
* node marked by either signal is excluded from every voice-mirror
|
|
52
|
-
* walk that goes through VOICE_CORPUS_WHERE.
|
|
53
|
-
*
|
|
54
|
-
* The clause expects `$accountId` to be bound in the calling Cypher
|
|
55
|
-
* parameters. It does not bind any other parameters.
|
|
56
|
-
*/
|
|
57
|
-
export const VOICE_CORPUS_WHERE = `n.accountId = $accountId
|
|
58
|
-
AND n.authorshipMode IN ['human-only', 'human-led-agent-assisted']
|
|
59
|
-
AND ${notTrashed("n")}
|
|
60
|
-
AND (
|
|
61
|
-
any(lbl IN labels(n) WHERE lbl IN ['KnowledgeDocument', 'Message', 'SocialPost'])
|
|
62
|
-
OR ('Section' IN labels(n) AND EXISTS {
|
|
63
|
-
MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
|
|
64
|
-
WHERE parent.conversationIdentity IS NOT NULL
|
|
65
|
-
AND ${notTrashed("parent")}
|
|
66
|
-
})
|
|
67
|
-
)`;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The label set the tag tool accepts when stamping `authorshipMode`.
|
|
71
|
-
* Mirrors the label predicate inside VOICE_CORPUS_WHERE so the tag tool
|
|
72
|
-
* and the corpus walk stay in lockstep. The tag tool does not filter
|
|
73
|
-
* by authorshipMode (that's the property it's setting).
|
|
74
|
-
*
|
|
75
|
-
* `:Email` retired with Task 321 — email threads now land as
|
|
76
|
-
* `:KnowledgeDocument {source:'email'}` and are already covered.
|
|
77
|
-
*/
|
|
78
|
-
export const TAGGABLE_PRIMARY_LABELS = [
|
|
79
|
-
"KnowledgeDocument",
|
|
80
|
-
"Message",
|
|
81
|
-
"SocialPost",
|
|
82
|
-
] as const;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Closed enum of writing-format values. One `:VoiceProfile` exists per
|
|
86
|
-
* `(accountId, userId, format)`. Adding a new value is a separate task.
|
|
87
|
-
*
|
|
88
|
-
* Format is an editorial classification set by the operator at tag time.
|
|
89
|
-
* The only auto-tagged path is `voice-ingest-session-text`, which hard-codes
|
|
90
|
-
* `format='text'` on `:Message` because chat turns are unambiguously text and
|
|
91
|
-
* the operator is not in the loop on session end.
|
|
92
|
-
*/
|
|
93
|
-
export const FORMAT_VALUES = [
|
|
94
|
-
"text",
|
|
95
|
-
"email",
|
|
96
|
-
"social-post",
|
|
97
|
-
"article",
|
|
98
|
-
"marketing-copy",
|
|
99
|
-
"note",
|
|
100
|
-
] as const;
|
|
101
|
-
|
|
102
|
-
export type VoiceFormat = (typeof FORMAT_VALUES)[number];
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Returns VOICE_CORPUS_WHERE with an additional `n.format = $format`
|
|
106
|
-
* predicate appended. Requires `$format` to be bound alongside `$accountId`
|
|
107
|
-
* in the calling Cypher parameters.
|
|
108
|
-
*
|
|
109
|
-
* Format-discovery queries (which enumerate distinct formats from the
|
|
110
|
-
* corpus) use the base `VOICE_CORPUS_WHERE` directly; per-format corpus
|
|
111
|
-
* walks use this function.
|
|
112
|
-
*/
|
|
113
|
-
export function voiceCorpusWhereWithFormat(format: VoiceFormat): string {
|
|
114
|
-
return `${VOICE_CORPUS_WHERE}\n AND n.format = $format`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Reserved sentinel userId for the per-account org voice profile (Task 676).
|
|
119
|
-
*
|
|
120
|
-
* Keeps the `(accountId, userId, format)` UNIQUE constraint valid with no
|
|
121
|
-
* migration: exactly one org profile per `(account, format)`. A real
|
|
122
|
-
* `:AdminUser` must never hold this userId — `voice-distil-profile` surfaces
|
|
123
|
-
* a Neo4j constraint error (does not swallow it) if one does.
|
|
124
|
-
*/
|
|
125
|
-
export const ORG_USER_ID = "__org__";
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Voice profile scope. `personal` = one operator's own voice, keyed
|
|
129
|
-
* `(accountId, userId, format)` and anchored on `:AdminUser`. `org` = the
|
|
130
|
-
* account/house voice, keyed `(accountId, '__org__', format)` and anchored on
|
|
131
|
-
* `:LocalBusiness`.
|
|
132
|
-
*/
|
|
133
|
-
export type VoiceScope = "personal" | "org";
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* The profile-key userId for a scope: the operator's own id for personal, the
|
|
137
|
-
* reserved sentinel for org. The single place that maps scope → key userId, so
|
|
138
|
-
* distillation, retrieval, and feedback agree on where an org profile lives.
|
|
139
|
-
*/
|
|
140
|
-
export function profileUserIdForScope(scope: VoiceScope, userId: string): string {
|
|
141
|
-
return scope === "org" ? ORG_USER_ID : userId;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* `VOICE_CORPUS_WHERE` + format predicate + author predicate. Requires both
|
|
146
|
-
* `$format` and `$voiceAuthor` bound in the calling Cypher parameters.
|
|
147
|
-
*
|
|
148
|
-
* Personal distillation and personal retrieval use this to narrow the
|
|
149
|
-
* account-wide corpus to one author's content; org walks use the unchanged
|
|
150
|
-
* account-wide `voiceCorpusWhereWithFormat`.
|
|
151
|
-
*/
|
|
152
|
-
export function voiceCorpusWhereWithFormatAndAuthor(format: VoiceFormat): string {
|
|
153
|
-
return `${VOICE_CORPUS_WHERE}\n AND n.format = $format\n AND n.voiceAuthor = $voiceAuthor`;
|
|
154
|
-
}
|