@rubytech/create-maxy-code 0.1.264 → 0.1.266
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/models/dist/index.d.ts +1 -1
- package/payload/platform/lib/models/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/models/dist/index.js +5 -2
- package/payload/platform/lib/models/dist/index.js.map +1 -1
- package/payload/platform/lib/models/src/index.ts +5 -2
- package/payload/platform/neo4j/schema.cypher +13 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +6 -1
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.js +106 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-search.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +36 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +12 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +175 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -3
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +2 -2
- package/payload/platform/plugins/business-assistant/PLUGIN.md +1 -5
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +9 -1
- package/payload/platform/plugins/memory/references/schema-base.md +1 -1
- package/payload/platform/plugins/memory/references/schema-construction.md +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-login-start.test.d.ts +2 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-login-start.test.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-login-start.test.js +16 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/format-login-start.test.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts +7 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.js +21 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/format.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +11 -19
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +15 -13
- package/payload/platform/scripts/check-skill-frontmatter.mjs +8 -5
- 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 +1 -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/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +15 -15
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +59 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/templates/account.json +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -3
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
- package/payload/platform/templates/specialists/agents/librarian.md +1 -1
- package/payload/platform/templates/specialists/agents/research-assistant.md +1 -1
- package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +1 -1
- package/payload/premium-plugins/writer-craft/PLUGIN.md +4 -4
- package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js +44 -9
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts +31 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js +28 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +7 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +93 -44
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.js +1 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts +7 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js +14 -3
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts +22 -8
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +93 -84
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts +18 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js +32 -3
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/scripts/smoke.mjs +35 -2
- package/payload/premium-plugins/writer-craft/mcp/src/index.ts +52 -10
- package/payload/premium-plugins/writer-craft/mcp/src/lib/voice-corpus.ts +39 -0
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +108 -44
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-ingest-session-text.ts +1 -0
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-record-feedback.ts +24 -4
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +136 -102
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-tag-content.ts +45 -3
- package/payload/premium-plugins/writer-craft/skills/voice-mirror/SKILL.md +34 -23
- package/payload/server/{adminuser-self-heal-YC47O34W.js → adminuser-self-heal-LC7HZAC6.js} +17 -9
- package/payload/server/{chunk-HYQNUVGO.js → chunk-PFF6I7KP.js} +1 -8
- package/payload/server/{chunk-ZC63DFWF.js → chunk-SOLVVUST.js} +1 -1
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{AdminShell-8JLQM1Mz.js → AdminShell-BcHJy_Y2.js} +1 -1
- package/payload/server/public/assets/{Checkbox-CuFJh7lI.js → Checkbox-BY2lndUH.js} +1 -1
- package/payload/server/public/assets/{admin-D4nsdmKU.js → admin-C364q_-g.js} +1 -1
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-D-ODjJS5.js → architectureDiagram-Q4EWVU46-B9ik0JyO.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DFuVe8f9.js → blockDiagram-DXYQGD6D-C-w-AMRJ.js} +1 -1
- package/payload/server/public/assets/{browser-DIKmvDl5.js → browser-D66JrDpx.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-CeVLfznT.js → c4Diagram-AHTNJAMY-BH-b0NZ1.js} +1 -1
- package/payload/server/public/assets/channel-5xoW3jfU.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-BTaLfR3a.js → chunk-336JU56O-CZHLbmJj.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BzTbm4pP.js → chunk-426QAEUC-Qx6Ggp9s.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-SiUyOXVG.js → chunk-4TB4RGXK-J1Raz9l0.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-BcQ33LnT.js → chunk-5FUZZQ4R-D6TFnJVF.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-CT7C1Tik.js → chunk-5PVQY5BW-CC5aEdzK.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-CK3uI2u6.js → chunk-EDXVE4YY-Bfbcdcbt.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-Ddp8Otnw.js → chunk-ENJZ2VHE-7EKi84Dw.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DHTxymk_.js → chunk-ICPOFSXX-DIH1FT8n.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-Heg3hegz.js → chunk-OYMX7WX6-MgP02n7p.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-Cw2PR7aJ.js → chunk-U2HBQHQK-Cwmbq3M7.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-C7vBCAQQ.js → chunk-X2U36JSP-D65N3MkW.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-BS8Mybh1.js → chunk-YZCP3GAM-BkCiRhSG.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DmnbQp47.js → chunk-ZZ45TVLE-3BQY1FWN.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DRSNlIL5.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CYvRRfMp.js +1 -0
- package/payload/server/public/assets/clone-DvQUt_lq.js +1 -0
- package/payload/server/public/assets/{dagre-D4i6r8zi.js → dagre-DVhW13RD.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-znWS6Wh2.js → dagre-KV5264BT-CtfjEgDz.js} +1 -1
- package/payload/server/public/assets/{data-CHqF4bkS.js → data-uiOcPh8T.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-B6tQBN1Y.js → diagram-5BDNPKRD-C8AXEFch.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-t73WpYIY.js → diagram-G4DWMVQ6-B1P8Cwuz.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-Cm63UOJm.js → diagram-MMDJMWI5-QBVBnPmO.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-DuTSLjVB.js → diagram-TYMM5635-D2xE8fN8.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-sbHunitX.js → erDiagram-SMLLAGMA-DMee1Yih.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BWOrPuqz.js → flowDiagram-DWJPFMVM-DAd3JjoB.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-WaLMGard.js → ganttDiagram-T4ZO3ILL-Bs33CmKo.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CsbIgKDn.js → gitGraphDiagram-UUTBAWPF-NXc8aeps.js} +1 -1
- package/payload/server/public/assets/{graph-kwyPyf_b.js → graph-BSHyF267.js} +1 -1
- package/payload/server/public/assets/{graph-labels-pX3uIQys.js → graph-labels-D5ecmK-Z.js} +1 -1
- package/payload/server/public/assets/{graphlib-B8yKFZvc.js → graphlib-DN7sm5nK.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-Bd55TzsD.js → infoDiagram-42DDH7IO-D2g5STFr.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BFdOsA9f.js → ishikawaDiagram-UXIWVN3A-DBgfV-LB.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CMQ5XWO9.js → journeyDiagram-VCZTEJTY-Chh9lW9P.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-_RpNNRN7.js → kanban-definition-6JOO6SKY-buI2AXpi.js} +1 -1
- package/payload/server/public/assets/{line-SkFxjGJ8.js → line-C8OFYjrW.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-Ddok2IAe.js → mermaid-parser.core-BI5Ythbv.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-CihAjXBG.js → mermaid.core-C4dyl1Xw.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CJsq3hrO.js → mindmap-definition-QFDTVHPH-BbXKSpiv.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-CSgg8lBB.js → pieDiagram-DEJITSTG-C8-OIoUW.js} +1 -1
- package/payload/server/public/assets/{public-BA9P-3ZV.js → public-DGlZwbyw.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-TtHxMQtS.js → quadrantDiagram-34T5L4WZ-HOEtpu4n.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DdYupKtL.js → requirementDiagram-MS252O5E-DLxjohCO.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-BVfdyUnO.js → sankeyDiagram-XADWPNL6-E_O_HiFL.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-aFLOua6n.js → sequenceDiagram-FGHM5R23-C3-hhAup.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-_3Ix8SPZ.js → stateDiagram-FHFEXIEX-BhAmQ0XX.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Dhrf9hq_.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Bs96WqZ7.js → timeline-definition-GMOUNBTQ-DpfZt7xI.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-L9uAHstA.css → useSelectionMode-98jrB9kD.css} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DE_SxsN2.js → vennDiagram-DHZGUBPP-Ck2Ago5q.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-DTCdKs6v.js → wardleyDiagram-NUSXRM2D-Dgk8C9kF.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CNIrxADs.js → xychartDiagram-5P7HB3ND-BRBSl8ID.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +5 -5
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +607 -5064
- package/payload/platform/plugins/business-assistant/references/quote-engine.md +0 -122
- package/payload/platform/plugins/business-assistant/references/quote-generation.md +0 -94
- package/payload/platform/plugins/business-assistant/references/quoting.md +0 -85
- package/payload/platform/plugins/business-assistant/skills/pricing-method/SKILL.md +0 -78
- package/payload/platform/plugins/business-assistant/skills/pricing-method/references/learning-from-history.md +0 -51
- package/payload/platform/plugins/business-assistant/skills/pricing-method/references/maintenance.md +0 -32
- package/payload/platform/plugins/business-assistant/skills/pricing-method/references/manual-definition.md +0 -42
- package/payload/platform/plugins/business-assistant/skills/pricing-method/references/verification.md +0 -37
- package/payload/server/public/assets/channel-Db8J_1Rl.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DjquNGFr.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CcE8sWEP.js +0 -1
- package/payload/server/public/assets/clone-xa7JZYoY.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-eVQQEyxx.js +0 -1
- /package/payload/server/public/assets/{useSelectionMode-Da9Glxvu.js → useSelectionMode-80iYuGPa.js} +0 -0
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
import neo4j from "neo4j-driver";
|
|
30
30
|
import { getSession } from "../lib/neo4j.js";
|
|
31
31
|
import { notTrashed } from "../lib/voice-corpus.js";
|
|
32
|
-
import { voiceCorpusWhereWithFormat, } from "../lib/voice-corpus.js";
|
|
32
|
+
import { voiceCorpusWhereWithFormat, voiceCorpusWhereWithFormatAndAuthor, ORG_USER_ID, } from "../lib/voice-corpus.js";
|
|
33
33
|
const DEFAULT_TOKEN_BUDGET_SHORT = 16_000;
|
|
34
34
|
const DEFAULT_TOKEN_BUDGET_LONG = 96_000;
|
|
35
35
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -52,33 +52,14 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
52
52
|
(length === "long" ? DEFAULT_TOKEN_BUDGET_LONG : DEFAULT_TOKEN_BUDGET_SHORT);
|
|
53
53
|
const charBudget = tokenBudget * CHARS_PER_TOKEN;
|
|
54
54
|
const format = brief.format;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
// (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// 1. Style card — per-format profile.
|
|
64
|
-
const profile = await session.run(`MATCH (a:AdminUser {accountId: $accountId, userId: $userId})
|
|
65
|
-
OPTIONAL MATCH (a)-[:HAS_VOICE_PROFILE]->(p:VoiceProfile {accountId: $accountId, userId: $userId, format: $format})
|
|
66
|
-
RETURN p.styleCard AS styleCard`, { accountId, userId, format });
|
|
67
|
-
const styleCard = profile.records[0]?.get("styleCard") ?? null;
|
|
68
|
-
// 2. Exemplars — filtered to brief.format. Keyword-aware when topic is set.
|
|
69
|
-
//
|
|
70
|
-
// Body resolution (Task 471):
|
|
71
|
-
// - For :KnowledgeDocument with HAS_SECTION children, concatenate
|
|
72
|
-
// child :Section bodies in `position` order. KD.body is null after
|
|
73
|
-
// Task 465 server-slicing.
|
|
74
|
-
// - Other labels read n.body directly via the COALESCE fallback chain.
|
|
75
|
-
//
|
|
76
|
-
// The topic filter runs against the computed body, so a topic that
|
|
77
|
-
// appears only in n.summary (LLM abstract) no longer matches — by
|
|
78
|
-
// design: the corpus surfaces verbatim prose, not the abstract.
|
|
79
|
-
const topic = (brief.topic ?? "").trim();
|
|
80
|
-
const usesTopic = topic.length > 0;
|
|
81
|
-
const bodyComputeBlock = `WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
|
|
55
|
+
const scope = brief.scope ?? "personal";
|
|
56
|
+
const topic = (brief.topic ?? "").trim();
|
|
57
|
+
const usesTopic = topic.length > 0;
|
|
58
|
+
// Body resolution (Task 471): for :KnowledgeDocument with HAS_SECTION
|
|
59
|
+
// children, concatenate child :Section bodies in `position` order (KD.body is
|
|
60
|
+
// null after Task 465 server-slicing); other labels read n.body via the
|
|
61
|
+
// COALESCE chain. The topic filter runs against the computed body.
|
|
62
|
+
const bodyComputeBlock = `WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
|
|
82
63
|
OPTIONAL MATCH (n)-[:HAS_SECTION]->(s:Section)
|
|
83
64
|
WHERE ${notTrashed("s")}
|
|
84
65
|
WITH n, ts, s ORDER BY s.position
|
|
@@ -88,64 +69,92 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
88
69
|
THEN reduce(acc = '', b IN sectionBodies | acc + b + '\n\n')
|
|
89
70
|
ELSE coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '')
|
|
90
71
|
END AS body`;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const labels = r.get("labels") ?? [];
|
|
129
|
-
const label = labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost", "Conversation"].includes(l)) ?? labels[0] ?? "Unknown";
|
|
130
|
-
const remaining = charBudget - charsUsed;
|
|
131
|
-
if (remaining <= 0)
|
|
132
|
-
break;
|
|
133
|
-
const truncated = body.length > remaining ? body.slice(0, remaining) : body;
|
|
134
|
-
exemplars.push({
|
|
135
|
-
nodeId: r.get("id"),
|
|
136
|
-
label,
|
|
137
|
-
body: truncated,
|
|
138
|
-
source: (r.get("source") ?? "").slice(0, 200),
|
|
72
|
+
// getSession() is inside the try so driver-construction / connection-config
|
|
73
|
+
// faults (NEO4J_URI unset, password file missing) are caught, logged, and
|
|
74
|
+
// returned as the error state rather than escaping the function silently
|
|
75
|
+
// (Task 493).
|
|
76
|
+
let session;
|
|
77
|
+
try {
|
|
78
|
+
session = getSession();
|
|
79
|
+
// One scoped lookup: the profile style card (keyed on the profile's userId)
|
|
80
|
+
// plus K voice-matched exemplars over the scope's corpus. Org passes the
|
|
81
|
+
// account-wide corpus + `__org__`; personal passes the author-filtered
|
|
82
|
+
// corpus + the operator's userId. Each call gets a fresh char budget.
|
|
83
|
+
const lookup = async (profileUserId, exemplarWhere, exemplarParams) => {
|
|
84
|
+
const profile = await session.run(`OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
|
|
85
|
+
RETURN p.styleCard AS styleCard`, { accountId, profileUserId, format });
|
|
86
|
+
const styleCard = profile.records[0]?.get("styleCard") ?? null;
|
|
87
|
+
const cypher = usesTopic
|
|
88
|
+
? `MATCH (n)
|
|
89
|
+
WHERE ${exemplarWhere}
|
|
90
|
+
${bodyComputeBlock}
|
|
91
|
+
WHERE toLower(body) CONTAINS toLower($topic)
|
|
92
|
+
RETURN elementId(n) AS id, labels(n) AS labels, body,
|
|
93
|
+
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
94
|
+
ORDER BY ts IS NULL, ts DESC
|
|
95
|
+
LIMIT $k`
|
|
96
|
+
: `MATCH (n)
|
|
97
|
+
WHERE ${exemplarWhere}
|
|
98
|
+
${bodyComputeBlock}
|
|
99
|
+
RETURN elementId(n) AS id, labels(n) AS labels, body,
|
|
100
|
+
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
101
|
+
ORDER BY ts IS NULL, ts DESC
|
|
102
|
+
LIMIT $k`;
|
|
103
|
+
const result = await session.run(cypher, {
|
|
104
|
+
...exemplarParams,
|
|
105
|
+
topic,
|
|
106
|
+
// neo4j-driver serializes a plain JS number as a Cypher float (15 →
|
|
107
|
+
// 15.0), which LIMIT rejects. Send a Neo4j integer.
|
|
108
|
+
k: neo4j.int(k),
|
|
139
109
|
});
|
|
140
|
-
charsUsed
|
|
110
|
+
let charsUsed = 0;
|
|
111
|
+
const exemplars = [];
|
|
112
|
+
for (const r of result.records) {
|
|
113
|
+
const body = r.get("body") ?? "";
|
|
114
|
+
if (body.length === 0)
|
|
115
|
+
continue;
|
|
116
|
+
const labels = r.get("labels") ?? [];
|
|
117
|
+
const label = labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost", "Conversation"].includes(l)) ?? labels[0] ?? "Unknown";
|
|
118
|
+
const remaining = charBudget - charsUsed;
|
|
119
|
+
if (remaining <= 0)
|
|
120
|
+
break;
|
|
121
|
+
const truncated = body.length > remaining ? body.slice(0, remaining) : body;
|
|
122
|
+
exemplars.push({
|
|
123
|
+
nodeId: r.get("id"),
|
|
124
|
+
label,
|
|
125
|
+
body: truncated,
|
|
126
|
+
source: (r.get("source") ?? "").slice(0, 200),
|
|
127
|
+
});
|
|
128
|
+
charsUsed += truncated.length;
|
|
129
|
+
}
|
|
130
|
+
return { styleCard, exemplars };
|
|
131
|
+
};
|
|
132
|
+
const hasData = (r) => (r.styleCard !== null && r.styleCard.length > 0) || r.exemplars.length > 0;
|
|
133
|
+
const orgLookup = () => lookup(ORG_USER_ID, voiceCorpusWhereWithFormat(format), { accountId, format });
|
|
134
|
+
let result;
|
|
135
|
+
let status;
|
|
136
|
+
if (scope === "personal") {
|
|
137
|
+
const personal = await lookup(userId, voiceCorpusWhereWithFormatAndAuthor(format), { accountId, format, voiceAuthor: userId });
|
|
138
|
+
if (hasData(personal)) {
|
|
139
|
+
result = personal;
|
|
140
|
+
status = "ok";
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// No personal profile/corpus yet — borrow the org (house) voice rather
|
|
144
|
+
// than the bare default register (Task 676).
|
|
145
|
+
const org = await orgLookup();
|
|
146
|
+
result = org;
|
|
147
|
+
status = hasData(org) ? "fallback-org" : "no-data";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const org = await orgLookup();
|
|
152
|
+
result = org;
|
|
153
|
+
status = hasData(org) ? "ok" : "no-data";
|
|
141
154
|
}
|
|
142
|
-
process.stderr.write(`[voice-retrieve]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// discriminator exists to prevent (Task 493).
|
|
146
|
-
const hasStyleCard = styleCard !== null && styleCard.length > 0;
|
|
147
|
-
const status = hasStyleCard || exemplars.length > 0 ? "ok" : "no-data";
|
|
148
|
-
return { styleCard, exemplars, status };
|
|
155
|
+
process.stderr.write(`[voice-retrieve] scope=${scope} format=${format} status=${status} ` +
|
|
156
|
+
`styleCardBytes=${result.styleCard?.length ?? 0} exemplarCount=${result.exemplars.length} tokenBudget=${tokenBudget}\n`);
|
|
157
|
+
return { styleCard: result.styleCard, exemplars: result.exemplars, status };
|
|
149
158
|
}
|
|
150
159
|
catch (err) {
|
|
151
160
|
const message = err instanceof Error ? err.message : String(err);
|
package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-retrieve-conditioning.js","sourceRoot":"","sources":["../../src/tools/voice-retrieve-conditioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAuB,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,
|
|
1
|
+
{"version":3,"file":"voice-retrieve-conditioning.js","sourceRoot":"","sources":["../../src/tools/voice-retrieve-conditioning.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import { type VoiceFormat } from "../lib/voice-corpus.js";
|
|
2
2
|
export type AuthorshipMode = "human-only" | "human-led-agent-assisted" | "agent-led-human-reviewed" | "agent-only" | "unknown";
|
|
3
3
|
export declare const AUTHORSHIP_MODES: ReadonlySet<AuthorshipMode>;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the corpus author for a tag write (Task 676). An explicit, non-blank
|
|
6
|
+
* `author` wins; otherwise the tagging operator's `userId`. Throws when neither
|
|
7
|
+
* is available (no `author` argument and no `ADMIN_USER_ID` in spawn env), so a
|
|
8
|
+
* tag can never silently land with a null author.
|
|
9
|
+
*
|
|
10
|
+
* This is why single-operator accounts get personal attribution for free: with
|
|
11
|
+
* `author` omitted, the sole operator is the implicit author, so their personal
|
|
12
|
+
* corpus equals the account-wide corpus and prior behaviour is reproduced.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveVoiceAuthor(author: string | undefined, operatorUserId: string | null): string;
|
|
4
15
|
export interface VoiceTagContentParams {
|
|
5
16
|
nodeIds: string[];
|
|
6
17
|
mode: AuthorshipMode;
|
|
7
18
|
accountId: string;
|
|
8
19
|
/** Writing format (required). Editorial classification — operator-supplied. */
|
|
9
20
|
format: VoiceFormat;
|
|
21
|
+
/**
|
|
22
|
+
* The corpus author (a userId) to stamp on each node (Task 676). Resolve via
|
|
23
|
+
* `resolveVoiceAuthor` before calling — explicit author, else the tagging
|
|
24
|
+
* operator. Personal distillation filters on this; org distillation ignores
|
|
25
|
+
* it (account-wide).
|
|
26
|
+
*/
|
|
27
|
+
voiceAuthor: string;
|
|
10
28
|
}
|
|
11
29
|
export interface VoiceTagContentResult {
|
|
12
30
|
updated: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-tag-content.d.ts","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,0BAA0B,GAC1B,0BAA0B,GAC1B,YAAY,GACZ,SAAS,CAAC;AAEd,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,cAAc,CAMvD,CAAC;AAEH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"voice-tag-content.d.ts","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,0BAA0B,GAC1B,0BAA0B,GAC1B,YAAY,GACZ,SAAS,CAAC;AAEd,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,cAAc,CAMvD,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,MAAM,CAOR;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAmGhC"}
|
|
@@ -21,8 +21,26 @@ export const AUTHORSHIP_MODES = new Set([
|
|
|
21
21
|
"agent-only",
|
|
22
22
|
"unknown",
|
|
23
23
|
]);
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the corpus author for a tag write (Task 676). An explicit, non-blank
|
|
26
|
+
* `author` wins; otherwise the tagging operator's `userId`. Throws when neither
|
|
27
|
+
* is available (no `author` argument and no `ADMIN_USER_ID` in spawn env), so a
|
|
28
|
+
* tag can never silently land with a null author.
|
|
29
|
+
*
|
|
30
|
+
* This is why single-operator accounts get personal attribution for free: with
|
|
31
|
+
* `author` omitted, the sole operator is the implicit author, so their personal
|
|
32
|
+
* corpus equals the account-wide corpus and prior behaviour is reproduced.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveVoiceAuthor(author, operatorUserId) {
|
|
35
|
+
const explicit = (author ?? "").trim();
|
|
36
|
+
if (explicit)
|
|
37
|
+
return explicit;
|
|
38
|
+
if (operatorUserId)
|
|
39
|
+
return operatorUserId;
|
|
40
|
+
throw new Error("voice-tag-content: author required — no ADMIN_USER_ID in spawn env and no author given.");
|
|
41
|
+
}
|
|
24
42
|
export async function voiceTagContent(params) {
|
|
25
|
-
const { nodeIds, mode, accountId, format } = params;
|
|
43
|
+
const { nodeIds, mode, accountId, format, voiceAuthor } = params;
|
|
26
44
|
if (!AUTHORSHIP_MODES.has(mode)) {
|
|
27
45
|
throw new Error(`voice-tag-content: invalid authorshipMode '${mode}'. Valid values: ${[
|
|
28
46
|
...AUTHORSHIP_MODES,
|
|
@@ -40,6 +58,12 @@ export async function voiceTagContent(params) {
|
|
|
40
58
|
if (!Array.isArray(nodeIds) || nodeIds.length === 0) {
|
|
41
59
|
return { updated: 0, skipped: 0, skippedReasons: {} };
|
|
42
60
|
}
|
|
61
|
+
// voiceAuthor is checked after the empty-nodeIds early return: an empty tag is
|
|
62
|
+
// a no-op, so there is nothing to attribute. With nodes to tag, the author
|
|
63
|
+
// must be resolved (index.ts always supplies it via resolveVoiceAuthor).
|
|
64
|
+
if (!voiceAuthor) {
|
|
65
|
+
throw new Error("voice-tag-content: voiceAuthor is required (resolve via resolveVoiceAuthor before calling).");
|
|
66
|
+
}
|
|
43
67
|
const session = getSession();
|
|
44
68
|
const now = new Date().toISOString();
|
|
45
69
|
const skippedReasons = {};
|
|
@@ -66,13 +90,15 @@ export async function voiceTagContent(params) {
|
|
|
66
90
|
})
|
|
67
91
|
SET n.authorshipMode = $mode,
|
|
68
92
|
n.authorshipTaggedAt = $now,
|
|
69
|
-
n.format = $format
|
|
93
|
+
n.format = $format,
|
|
94
|
+
n.voiceAuthor = $voiceAuthor
|
|
70
95
|
RETURN elementId(n) AS id`, {
|
|
71
96
|
ids: batch,
|
|
72
97
|
accountId,
|
|
73
98
|
mode,
|
|
74
99
|
now,
|
|
75
100
|
format,
|
|
101
|
+
voiceAuthor,
|
|
76
102
|
primaryLabels: [...TAGGABLE_PRIMARY_LABELS],
|
|
77
103
|
});
|
|
78
104
|
const matched = new Set(result.records.map((r) => r.get("id")));
|
|
@@ -88,7 +114,10 @@ export async function voiceTagContent(params) {
|
|
|
88
114
|
finally {
|
|
89
115
|
await session.close();
|
|
90
116
|
}
|
|
91
|
-
|
|
117
|
+
// Tagging is always a personal-attribution act: every tagged node carries an
|
|
118
|
+
// author and feeds both that author's personal walk and the account-wide org
|
|
119
|
+
// walk. There is no org-only corpus node, so scope is constant `personal`.
|
|
120
|
+
process.stderr.write(`[voice-tag] mode=${mode} format=${format} scope=personal author=${voiceAuthor} accountId=${accountId} updated=${updated} skipped=${nodeIds.length - updated}\n`);
|
|
92
121
|
return {
|
|
93
122
|
updated,
|
|
94
123
|
skipped: nodeIds.length - updated,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-tag-content.js","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,uBAAuB,EACvB,aAAa,GAEd,MAAM,wBAAwB,CAAC;AAShC,MAAM,CAAC,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IACnE,YAAY;IACZ,0BAA0B;IAC1B,0BAA0B;IAC1B,YAAY;IACZ,SAAS;CACV,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"voice-tag-content.js","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
* 4. AUTHORSHIP_MODES enum covers the five spec-required values.
|
|
11
11
|
* 5. TAGGABLE_PRIMARY_LABELS covers the three content labels.
|
|
12
12
|
* 6. FORMAT_VALUES covers the six writing-format values (Task 462).
|
|
13
|
-
* 7. voiceCorpusWhereWithFormat() appends the format predicate
|
|
13
|
+
* 7. voiceCorpusWhereWithFormat() appends the format predicate;
|
|
14
|
+
* voiceCorpusWhereWithFormatAndAuthor() also appends the author predicate,
|
|
15
|
+
* and ORG_USER_ID / profileUserIdForScope map scope→userId (Task 676).
|
|
14
16
|
* * 8. voice-retrieve-conditioning discriminates error from no-data (Task 493):
|
|
15
17
|
* status:"error" on blank identity and on a forced lookup throw, never
|
|
16
18
|
* collapsing a failed lookup into the no-data payload.
|
|
17
19
|
* 9. voice-tag-content validates mode and format enums, requires format,
|
|
18
|
-
*
|
|
20
|
+
* returns zero counts on empty nodeIds, and requires voiceAuthor when
|
|
21
|
+
* there are nodes to tag; resolveVoiceAuthor defaults to the operator
|
|
22
|
+
* (Task 676).
|
|
19
23
|
* 11. voice-ingest-session-text: filterOperatorText() keeps real turns
|
|
20
24
|
* and excludes slash commands, paste blocks, and system reminders.
|
|
21
25
|
*
|
|
@@ -160,6 +164,17 @@ await check("voiceCorpusWhereWithFormat appends format predicate", () => {
|
|
|
160
164
|
assert.ok(result.includes("n.format = $format"));
|
|
161
165
|
assert.ok(result.includes("email") || result.includes("$format"));
|
|
162
166
|
});
|
|
167
|
+
await check("voiceCorpusWhereWithFormatAndAuthor appends format + author predicates (Task 676)", () => {
|
|
168
|
+
const result = corpusMod.voiceCorpusWhereWithFormatAndAuthor("article");
|
|
169
|
+
assert.ok(result.includes(corpusMod.VOICE_CORPUS_WHERE));
|
|
170
|
+
assert.ok(result.includes("n.format = $format"));
|
|
171
|
+
assert.ok(result.includes("n.voiceAuthor = $voiceAuthor"));
|
|
172
|
+
});
|
|
173
|
+
await check("ORG_USER_ID sentinel + profileUserIdForScope map scope→userId (Task 676)", () => {
|
|
174
|
+
assert.equal(corpusMod.ORG_USER_ID, "__org__");
|
|
175
|
+
assert.equal(corpusMod.profileUserIdForScope("org", "ua"), "__org__");
|
|
176
|
+
assert.equal(corpusMod.profileUserIdForScope("personal", "ua"), "ua");
|
|
177
|
+
});
|
|
163
178
|
|
|
164
179
|
// ---------------------------------------------------------------------------
|
|
165
180
|
// 4. deriveFormat is gone (Task 471) — format is operator-supplied editorial.
|
|
@@ -290,6 +305,24 @@ await check("voice-tag-content returns zero counts on empty nodeIds (no DB hit)"
|
|
|
290
305
|
});
|
|
291
306
|
assert.deepEqual(result, { updated: 0, skipped: 0, skippedReasons: {} });
|
|
292
307
|
});
|
|
308
|
+
await check("voice-tag-content requires voiceAuthor when there are nodes to tag (Task 676)", async () => {
|
|
309
|
+
await assert.rejects(
|
|
310
|
+
() =>
|
|
311
|
+
tagMod.voiceTagContent({
|
|
312
|
+
nodeIds: ["x"],
|
|
313
|
+
mode: "human-only",
|
|
314
|
+
accountId: "acc",
|
|
315
|
+
format: "text",
|
|
316
|
+
}),
|
|
317
|
+
/voiceAuthor is required/,
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
await check("resolveVoiceAuthor: explicit author wins, omitted defaults to operator, neither throws (Task 676)", () => {
|
|
321
|
+
assert.equal(tagMod.resolveVoiceAuthor("B", "ua"), "B");
|
|
322
|
+
assert.equal(tagMod.resolveVoiceAuthor(undefined, "ua"), "ua");
|
|
323
|
+
assert.equal(tagMod.resolveVoiceAuthor(" ", "ua"), "ua");
|
|
324
|
+
assert.throws(() => tagMod.resolveVoiceAuthor(undefined, null));
|
|
325
|
+
});
|
|
293
326
|
|
|
294
327
|
// ---------------------------------------------------------------------------
|
|
295
328
|
// 7. voice-ingest-session-text: operator-turn filter (no DB required).
|
|
@@ -4,7 +4,8 @@ initStderrTee("writer-craft");
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { voiceTagContent, AUTHORSHIP_MODES, type AuthorshipMode } from "./tools/voice-tag-content.js";
|
|
7
|
+
import { voiceTagContent, resolveVoiceAuthor, AUTHORSHIP_MODES, type AuthorshipMode } from "./tools/voice-tag-content.js";
|
|
8
|
+
import type { VoiceFormat } from "./lib/voice-corpus.js";
|
|
8
9
|
import { voiceDistilProfile } from "./tools/voice-distil-profile.js";
|
|
9
10
|
import { voiceRetrieveConditioning } from "./tools/voice-retrieve-conditioning.js";
|
|
10
11
|
import { voiceRecordFeedback } from "./tools/voice-record-feedback.js";
|
|
@@ -53,6 +54,14 @@ const zFormat = z.enum([...FORMAT_VALUES] as [string, ...string[]]).describe(
|
|
|
53
54
|
"Writing format: text | email | social-post | article | marketing-copy | note. One :VoiceProfile per (accountId, userId, format).",
|
|
54
55
|
);
|
|
55
56
|
|
|
57
|
+
// Scope enum schema — shared across distil / retrieve / feedback (Task 676).
|
|
58
|
+
const zScope = z
|
|
59
|
+
.enum(["personal", "org"])
|
|
60
|
+
.optional()
|
|
61
|
+
.describe(
|
|
62
|
+
"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__'.",
|
|
63
|
+
);
|
|
64
|
+
|
|
56
65
|
server.tool(
|
|
57
66
|
"voice-tag-content",
|
|
58
67
|
"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.",
|
|
@@ -70,21 +79,48 @@ server.tool(
|
|
|
70
79
|
format: zFormat.describe(
|
|
71
80
|
"Writing format. Required — the operator's editorial classification of the content.",
|
|
72
81
|
),
|
|
82
|
+
userId: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe(
|
|
86
|
+
"Operator AdminUser id. Defaults to ADMIN_USER_ID env. Used as the default author when `author` is omitted.",
|
|
87
|
+
),
|
|
88
|
+
author: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe(
|
|
92
|
+
"voiceAuthor userId to stamp on the nodes. Omit to attribute to the tagging operator — single-operator accounts get personal attribution for free.",
|
|
93
|
+
),
|
|
73
94
|
},
|
|
74
|
-
async ({ nodeIds, mode, format }) => {
|
|
95
|
+
async ({ nodeIds, mode, format, userId, author }) => {
|
|
75
96
|
if (!accountId) return refuseNoAccount("voice-tag-content");
|
|
97
|
+
let voiceAuthor: string;
|
|
98
|
+
try {
|
|
99
|
+
voiceAuthor = resolveVoiceAuthor(author, userId ?? resolveUserId());
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text" as const,
|
|
105
|
+
text: err instanceof Error ? err.message : String(err),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
76
111
|
try {
|
|
77
112
|
const result = await voiceTagContent({
|
|
78
113
|
nodeIds,
|
|
79
114
|
mode: mode as AuthorshipMode,
|
|
80
115
|
accountId,
|
|
81
|
-
format: format as
|
|
116
|
+
format: format as VoiceFormat,
|
|
117
|
+
voiceAuthor,
|
|
82
118
|
});
|
|
83
119
|
return {
|
|
84
120
|
content: [
|
|
85
121
|
{
|
|
86
122
|
type: "text" as const,
|
|
87
|
-
text: `voice-tag: mode=${mode} format=${format} updated=${result.updated} skipped=${result.skipped}`,
|
|
123
|
+
text: `voice-tag: mode=${mode} format=${format} author=${voiceAuthor} updated=${result.updated} skipped=${result.skipped}`,
|
|
88
124
|
},
|
|
89
125
|
],
|
|
90
126
|
};
|
|
@@ -104,7 +140,7 @@ server.tool(
|
|
|
104
140
|
|
|
105
141
|
server.tool(
|
|
106
142
|
"voice-distil-profile",
|
|
107
|
-
"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.",
|
|
143
|
+
"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.",
|
|
108
144
|
{
|
|
109
145
|
userId: z
|
|
110
146
|
.string()
|
|
@@ -139,8 +175,9 @@ server.tool(
|
|
|
139
175
|
.describe(
|
|
140
176
|
"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).",
|
|
141
177
|
),
|
|
178
|
+
scope: zScope,
|
|
142
179
|
},
|
|
143
|
-
async ({ userId, format, force, mode, styleCardYaml, nodeIds, amendedFromNodeIds }) => {
|
|
180
|
+
async ({ userId, format, force, mode, styleCardYaml, nodeIds, amendedFromNodeIds, scope }) => {
|
|
144
181
|
if (!accountId) return refuseNoAccount("voice-distil-profile");
|
|
145
182
|
const resolvedUserId = userId ?? resolveUserId();
|
|
146
183
|
if (!resolvedUserId) {
|
|
@@ -165,6 +202,7 @@ server.tool(
|
|
|
165
202
|
styleCardYaml,
|
|
166
203
|
nodeIds,
|
|
167
204
|
amendedFromNodeIds,
|
|
205
|
+
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
168
206
|
});
|
|
169
207
|
|
|
170
208
|
// Summarise single-format or multi-format result.
|
|
@@ -207,7 +245,7 @@ server.tool(
|
|
|
207
245
|
|
|
208
246
|
server.tool(
|
|
209
247
|
"voice-retrieve-conditioning",
|
|
210
|
-
"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). Returns {styleCard, exemplars, status} where status is one of: 'ok' (
|
|
248
|
+
"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'.",
|
|
211
249
|
{
|
|
212
250
|
userId: z
|
|
213
251
|
.string()
|
|
@@ -223,9 +261,10 @@ server.tool(
|
|
|
223
261
|
.optional()
|
|
224
262
|
.describe("K-selector: short = K=5 (16k tokens, email/post); long = K=15 (96k tokens, brochure/prospectus). Defaults to short."),
|
|
225
263
|
register: z.string().optional().describe("Optional register hint"),
|
|
264
|
+
scope: zScope,
|
|
226
265
|
tokenBudget: z.number().int().positive().optional(),
|
|
227
266
|
},
|
|
228
|
-
async ({ userId, format, topic, length, register, tokenBudget }) => {
|
|
267
|
+
async ({ userId, format, topic, length, register, scope, tokenBudget }) => {
|
|
229
268
|
if (!accountId) return refuseNoAccount("voice-retrieve-conditioning");
|
|
230
269
|
const resolvedUserId = userId ?? resolveUserId();
|
|
231
270
|
if (!resolvedUserId) {
|
|
@@ -254,6 +293,7 @@ server.tool(
|
|
|
254
293
|
topic,
|
|
255
294
|
length: length as "short" | "long" | undefined,
|
|
256
295
|
register,
|
|
296
|
+
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
257
297
|
},
|
|
258
298
|
tokenBudget,
|
|
259
299
|
});
|
|
@@ -287,7 +327,7 @@ server.tool(
|
|
|
287
327
|
|
|
288
328
|
server.tool(
|
|
289
329
|
"voice-record-feedback",
|
|
290
|
-
"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).",
|
|
330
|
+
"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.",
|
|
291
331
|
{
|
|
292
332
|
userId: z.string().optional(),
|
|
293
333
|
format: zFormat.describe("Writing format of the draft being edited."),
|
|
@@ -299,8 +339,9 @@ server.tool(
|
|
|
299
339
|
.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)."),
|
|
300
340
|
skill: z.string().optional().describe("Calling skill name, for provenance"),
|
|
301
341
|
brief: z.string().optional().describe("Drafting brief that produced the original"),
|
|
342
|
+
scope: zScope,
|
|
302
343
|
},
|
|
303
|
-
async ({ userId, format, originalText, editedText, intent, skill, brief }) => {
|
|
344
|
+
async ({ userId, format, originalText, editedText, intent, skill, brief, scope }) => {
|
|
304
345
|
if (!accountId) return refuseNoAccount("voice-record-feedback");
|
|
305
346
|
const resolvedUserId = userId ?? resolveUserId();
|
|
306
347
|
if (!resolvedUserId) {
|
|
@@ -322,6 +363,7 @@ server.tool(
|
|
|
322
363
|
originalText,
|
|
323
364
|
editedText,
|
|
324
365
|
intent,
|
|
366
|
+
scope: scope as import("./lib/voice-corpus.js").VoiceScope | undefined,
|
|
325
367
|
context: { skill, brief },
|
|
326
368
|
});
|
|
327
369
|
return {
|
|
@@ -113,3 +113,42 @@ export type VoiceFormat = (typeof FORMAT_VALUES)[number];
|
|
|
113
113
|
export function voiceCorpusWhereWithFormat(format: VoiceFormat): string {
|
|
114
114
|
return `${VOICE_CORPUS_WHERE}\n AND n.format = $format`;
|
|
115
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
|
+
}
|