@rubytech/create-maxy-code 0.1.170 → 0.1.172

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.
Files changed (58) hide show
  1. package/dist/__tests__/premium-mcp-discover.test.js +42 -0
  2. package/dist/lib/premium-mcp-discover.js +9 -2
  3. package/package.json +1 -1
  4. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  5. package/payload/platform/services/claude-session-manager/dist/http-server.js +24 -8
  6. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  7. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +15 -1
  8. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  9. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +78 -8
  10. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  11. package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.d.ts +51 -0
  12. package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -0
  13. package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.js +196 -0
  14. package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/dist/index.js.map +1 -0
  15. package/payload/premium-plugins/writer-craft/lib/mcp-stderr-tee/package.json +7 -0
  16. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts +3 -0
  17. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -0
  18. package/payload/premium-plugins/writer-craft/mcp/dist/index.js +257 -0
  19. package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -0
  20. package/payload/premium-plugins/writer-craft/mcp/dist/lib/neo4j.d.ts +14 -0
  21. package/payload/premium-plugins/writer-craft/mcp/dist/lib/neo4j.d.ts.map +1 -0
  22. package/payload/premium-plugins/writer-craft/mcp/dist/lib/neo4j.js +47 -0
  23. package/payload/premium-plugins/writer-craft/mcp/dist/lib/neo4j.js.map +1 -0
  24. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts +42 -0
  25. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts.map +1 -0
  26. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js +54 -0
  27. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js.map +1 -0
  28. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +36 -0
  29. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -0
  30. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +225 -0
  31. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -0
  32. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts +26 -0
  33. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -0
  34. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js +74 -0
  35. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js.map +1 -0
  36. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts +22 -0
  37. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -0
  38. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +102 -0
  39. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -0
  40. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts +14 -0
  41. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts.map +1 -0
  42. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js +84 -0
  43. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js.map +1 -0
  44. package/payload/premium-plugins/writer-craft/mcp/package-lock.json +1327 -0
  45. package/payload/premium-plugins/writer-craft/mcp/scripts/smoke.mjs +2 -2
  46. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +1 -1
  47. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +2 -2
  48. package/payload/server/public/assets/{admin-Dal5FWaD.js → admin-DH5jp-1u.js} +1 -1
  49. package/payload/server/public/assets/{data-DbSORCl4.js → data-CtanHhM2.js} +1 -1
  50. package/payload/server/public/assets/{graph-ys_joP2o.js → graph-6DPD6be1.js} +1 -1
  51. package/payload/server/public/assets/graph-labels-BMU8LsOD.js +1 -0
  52. package/payload/server/public/assets/{page-CwmZ76U7.js → page-0lzgQWnS.js} +1 -1
  53. package/payload/server/public/assets/{page--bl5Y7ZI.js → page-C29S3cBt.js} +1 -1
  54. package/payload/server/public/data.html +3 -3
  55. package/payload/server/public/graph.html +3 -3
  56. package/payload/server/public/index.html +4 -4
  57. package/payload/server/server.js +15 -0
  58. package/payload/server/public/assets/graph-labels-BBB5jsZk.js +0 -1
@@ -0,0 +1,225 @@
1
+ /**
2
+ * voice-distil-profile — walk the operator's `human-only` corpus and
3
+ * persist a `:VoiceProfile` style card.
4
+ *
5
+ * Cadence guard: re-runs on ≥20% corpus growth since last profile, OR
6
+ * ≥30 days since `generatedAt`. `force: true` overrides both.
7
+ *
8
+ * Body-text sampling caps at ~120k chars (rough proxy for tokens at
9
+ * 4 chars/token = 30k tokens) — well inside Haiku's 200k context window
10
+ * with headroom for the system prompt + tool schema.
11
+ */
12
+ import neo4j from "neo4j-driver";
13
+ import { getSession } from "../lib/neo4j.js";
14
+ import { VOICE_CORPUS_WHERE } from "../lib/voice-corpus.js";
15
+ // Task 424 — the Haiku style-card composition moved into the calling agent's
16
+ // turn. The agent reads the corpus exemplars and feedback intents (this tool
17
+ // returns them when called with `mode='sample'`), composes the style-card YAML,
18
+ // then calls this tool again with `mode='write'` + `styleCardYaml` set.
19
+ /**
20
+ * Coerce a neo4j cypher RETURN value to a plain JS number.
21
+ *
22
+ * Neo4j-driver represents INTs as `Integer` objects (high/low 32-bit
23
+ * pair to safely carry int64). `Number()` *usually* works via valueOf()
24
+ * but the safe path is `Integer#toNumber()`. Returns null when the
25
+ * value is null/undefined so cadence-guard arithmetic stays explicit
26
+ * about the "no prior profile" case.
27
+ */
28
+ function toJsNumber(value) {
29
+ if (value === null || value === undefined)
30
+ return null;
31
+ if (typeof value === "number")
32
+ return value;
33
+ if (neo4j.isInt(value))
34
+ return value.toNumber();
35
+ if (typeof value === "string") {
36
+ const parsed = Number(value);
37
+ return Number.isFinite(parsed) ? parsed : null;
38
+ }
39
+ return null;
40
+ }
41
+ const SAMPLE_CHAR_CAP = 120_000;
42
+ const RE_RUN_GROWTH_FRACTION = 0.2;
43
+ const RE_RUN_AGE_DAYS = 30;
44
+ // The agent produces a YAML style card with at minimum these keys:
45
+ // sentenceLengthDistribution, register, favoured, avoided, taboos,
46
+ // openingPattern, closingPattern, exemplarSentences
47
+ // Exemplar sentences should be quoted verbatim from the returned corpus.
48
+ export async function voiceDistilProfile(params) {
49
+ const { accountId, adminUserId, force = false } = params;
50
+ if (!accountId || !adminUserId) {
51
+ throw new Error("voice-distil-profile: accountId and adminUserId required");
52
+ }
53
+ const session = getSession();
54
+ const now = new Date();
55
+ const nowIso = now.toISOString();
56
+ try {
57
+ // 1. Read existing profile (if any) for cadence-guard comparison.
58
+ const existing = await session.run(`MATCH (a:AdminUser {accountId: $accountId, adminUserId: $adminUserId})
59
+ OPTIONAL MATCH (a)-[:HAS_VOICE_PROFILE]->(p:VoiceProfile)
60
+ RETURN elementId(p) AS profileId, p.corpusSize AS corpusSize,
61
+ p.generatedAt AS generatedAt`, { accountId, adminUserId });
62
+ const existingRow = existing.records[0];
63
+ const prevProfileId = existingRow?.get("profileId") ?? null;
64
+ const prevCorpusSize = toJsNumber(existingRow?.get("corpusSize"));
65
+ const prevGeneratedAt = existingRow?.get("generatedAt") ?? null;
66
+ // 2. Count corpus. Filter matches human-only + human-led-agent-assisted
67
+ // across the four primary content labels plus conversation-document
68
+ // :Section chunks (chat archives — children of a :KnowledgeDocument
69
+ // with `conversationIdentity` set). See VOICE_CORPUS_WHERE above.
70
+ const countResult = await session.run(`MATCH (n)
71
+ WHERE ${VOICE_CORPUS_WHERE}
72
+ RETURN count(n) AS c`, { accountId });
73
+ const corpusSize = toJsNumber(countResult.records[0]?.get("c")) ?? 0;
74
+ if (corpusSize === 0) {
75
+ process.stderr.write(`[voice-distil] adminUser=${adminUserId} skip reason=empty-corpus\n`);
76
+ return {
77
+ profileId: prevProfileId,
78
+ corpusSize: 0,
79
+ generatedAt: prevGeneratedAt,
80
+ feedbackEntries: 0,
81
+ skipped: true,
82
+ skipReason: "empty-corpus",
83
+ };
84
+ }
85
+ // 3. Cadence guard.
86
+ if (!force && prevProfileId && prevCorpusSize !== null && prevGeneratedAt) {
87
+ const ageMs = now.getTime() - new Date(prevGeneratedAt).getTime();
88
+ const ageDays = ageMs / (24 * 60 * 60 * 1000);
89
+ // prevCorpusSize is non-null here (the guard above checks). When it's
90
+ // zero we treat growth as infinite — first real corpus always triggers
91
+ // a re-run.
92
+ const growth = prevCorpusSize > 0
93
+ ? (corpusSize - prevCorpusSize) / prevCorpusSize
94
+ : 1;
95
+ const ageTrigger = ageDays >= RE_RUN_AGE_DAYS;
96
+ const growthTrigger = growth >= RE_RUN_GROWTH_FRACTION;
97
+ if (!ageTrigger && !growthTrigger) {
98
+ const reason = ageDays < 1 ? "recent" : "below-threshold";
99
+ process.stderr.write(`[voice-distil] adminUser=${adminUserId} skip reason=${reason} corpusSize=${corpusSize} prevCorpus=${prevCorpusSize} ageDays=${ageDays.toFixed(1)}\n`);
100
+ return {
101
+ profileId: prevProfileId,
102
+ corpusSize,
103
+ generatedAt: prevGeneratedAt,
104
+ feedbackEntries: 0,
105
+ skipped: true,
106
+ skipReason: reason,
107
+ };
108
+ }
109
+ }
110
+ // 4. Sample corpus bodies up to the char cap. Pick recent first.
111
+ // Timestamp coalesces include `firstMessageAt` for conversation-document
112
+ // :Section chunks so chat archives sort by when the conversation
113
+ // actually happened.
114
+ const sampled = await session.run(`MATCH (n)
115
+ WHERE ${VOICE_CORPUS_WHERE}
116
+ WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
117
+ OPTIONAL MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
118
+ WHERE 'Section' IN labels(n) AND parent.conversationIdentity IS NOT NULL
119
+ RETURN elementId(n) AS id,
120
+ labels(n) AS labels,
121
+ coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '') AS body,
122
+ parent.conversationIdentity IS NOT NULL AS isConversationChunk,
123
+ ts
124
+ ORDER BY ts IS NULL, ts DESC
125
+ LIMIT 500`, { accountId });
126
+ const samples = [];
127
+ let charBudget = SAMPLE_CHAR_CAP;
128
+ for (const r of sampled.records) {
129
+ const body = r.get("body") ?? "";
130
+ const labels = r.get("labels") ?? [];
131
+ // Conversation-document sections carry `:Section` only after the Task
132
+ // 324 collapse; surface "Conversation" via the parent's
133
+ // `conversationIdentity` so the LLM exemplar header still says "this
134
+ // is chat" rather than the bare base label.
135
+ const isConversationChunk = Boolean(r.get("isConversationChunk"));
136
+ const label = isConversationChunk
137
+ ? "Conversation"
138
+ : (labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost"].includes(l)) ?? labels[0] ?? "Unknown");
139
+ if (body.length === 0)
140
+ continue;
141
+ const trimmed = body.length > charBudget ? body.slice(0, charBudget) : body;
142
+ samples.push({ id: r.get("id"), label, body: trimmed });
143
+ charBudget -= trimmed.length;
144
+ if (charBudget <= 0)
145
+ break;
146
+ }
147
+ // 5. Pull feedback intents from existing :VoiceEdit nodes for this operator.
148
+ const feedback = await session.run(`MATCH (a:AdminUser {accountId: $accountId, adminUserId: $adminUserId})
149
+ MATCH (e:VoiceEdit {adminUserId: $adminUserId, accountId: $accountId})
150
+ RETURN e.intent AS intent ORDER BY e.occurredAt DESC LIMIT 50`, { accountId, adminUserId });
151
+ const feedbackIntents = feedback.records
152
+ .map((r) => r.get("intent"))
153
+ .filter((x) => typeof x === "string" && x.length > 0);
154
+ // 6. Mode branch (Task 424).
155
+ // - `sample` (default): return the exemplars + feedback intents to the
156
+ // calling agent. The agent composes the YAML style card in its own
157
+ // turn and re-invokes with mode='write'.
158
+ // - `write`: persist the agent-produced style card.
159
+ const mode = params.mode ?? "sample";
160
+ if (mode === "sample") {
161
+ process.stderr.write(`[voice-distil] adminUser=${adminUserId} mode=sample corpusSize=${corpusSize} ` +
162
+ `exemplars=${samples.length} feedbackEntries=${feedbackIntents.length}\n`);
163
+ return {
164
+ profileId: prevProfileId,
165
+ corpusSize,
166
+ generatedAt: prevGeneratedAt,
167
+ feedbackEntries: feedbackIntents.length,
168
+ skipped: true,
169
+ skipReason: "missing-style-card",
170
+ exemplars: samples,
171
+ feedbackIntents,
172
+ };
173
+ }
174
+ const styleCardYaml = (params.styleCardYaml ?? "").trim();
175
+ if (!styleCardYaml) {
176
+ throw new Error("voice-distil-profile: mode='write' requires styleCardYaml (the YAML you composed from the sample-mode response).");
177
+ }
178
+ // 7. Write the :VoiceProfile node + LEARNED_FROM edges to every sampled node.
179
+ //
180
+ // The :AdminUser must already exist (onboarding/auth is the only
181
+ // legitimate origin for :AdminUser). MATCH not MERGE: a typo in
182
+ // adminUserId, an env mismatch, or a dropped credential surfaces
183
+ // here as a typed error rather than silently minting an orphan
184
+ // AdminUser that bypasses the platform's label-origin protection.
185
+ const writeResult = await session.run(`MATCH (a:AdminUser {accountId: $accountId, adminUserId: $adminUserId})
186
+ MERGE (a)-[:HAS_VOICE_PROFILE]->(p:VoiceProfile {accountId: $accountId, adminUserId: $adminUserId})
187
+ ON CREATE SET p.createdAt = $now
188
+ SET p.styleCard = $styleCard,
189
+ p.generatedAt = $now,
190
+ p.corpusSize = $corpusSize,
191
+ p.feedbackEntries = $feedbackEntries,
192
+ p.updatedAt = $now
193
+ WITH p
194
+ UNWIND $sampleIds AS sid
195
+ MATCH (s) WHERE elementId(s) = sid
196
+ MERGE (p)-[:LEARNED_FROM]->(s)
197
+ RETURN elementId(p) AS profileId`, {
198
+ accountId,
199
+ adminUserId,
200
+ styleCard: styleCardYaml,
201
+ now: nowIso,
202
+ corpusSize,
203
+ feedbackEntries: feedbackIntents.length,
204
+ sampleIds: samples.map((s) => s.id),
205
+ });
206
+ // MATCH-on-:AdminUser failure yields zero records. Throw the typed
207
+ // error here rather than returning a half-baked result.
208
+ if (writeResult.records.length === 0) {
209
+ throw new Error(`voice-mirror: :AdminUser {accountId='${accountId}', adminUserId='${adminUserId}'} not found. Onboarding must promote the operator to :AdminUser before distillation.`);
210
+ }
211
+ const profileId = writeResult.records[0].get("profileId");
212
+ process.stderr.write(`[voice-distil] adminUser=${adminUserId} mode=write corpusSize=${corpusSize} generatedAt=${nowIso} feedbackEntries=${feedbackIntents.length}\n`);
213
+ return {
214
+ profileId,
215
+ corpusSize,
216
+ generatedAt: nowIso,
217
+ feedbackEntries: feedbackIntents.length,
218
+ skipped: false,
219
+ };
220
+ }
221
+ finally {
222
+ await session.close();
223
+ }
224
+ }
225
+ //# sourceMappingURL=voice-distil-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-distil-profile.js","sourceRoot":"","sources":["../../src/tools/voice-distil-profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface VoiceRecordFeedbackParams {
2
+ accountId: string;
3
+ adminUserId: string;
4
+ originalText: string;
5
+ editedText: string;
6
+ /**
7
+ * One sentence describing what changed and what voice preference it reveals.
8
+ * The calling agent summarises this from `originalText` vs `editedText` in
9
+ * its own turn before invoking the tool (Task 424). Examples: "shorter and
10
+ * dropped the apology", "switched to first person", "removed corporate
11
+ * sign-off", "added a specific date".
12
+ */
13
+ intent: string;
14
+ context?: {
15
+ skill?: string;
16
+ brief?: string;
17
+ };
18
+ }
19
+ export interface VoiceRecordFeedbackResult {
20
+ editId: string;
21
+ intent: string;
22
+ occurredAt: string;
23
+ hadProfile: boolean;
24
+ }
25
+ export declare function voiceRecordFeedback(params: VoiceRecordFeedbackParams): Promise<VoiceRecordFeedbackResult>;
26
+ //# sourceMappingURL=voice-record-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-record-feedback.d.ts","sourceRoot":"","sources":["../../src/tools/voice-record-feedback.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,yBAAyB,CAAC,CAoEpC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * voice-record-feedback — capture an operator's edit on an agent draft
3
+ * as feedback for the next distillation.
4
+ *
5
+ * Task 424: the Haiku diff-intent summarisation moved into the calling
6
+ * agent's turn. The agent compares `originalText` to `editedText` itself
7
+ * and passes the resulting `intent` string as a required parameter. The
8
+ * tool now does the graph writes only.
9
+ *
10
+ * Side effects:
11
+ * 1. Write `(:VoiceEdit)-[:FEEDBACK_FOR]->(:VoiceProfile)` if the profile
12
+ * exists; otherwise write the :VoiceEdit alone so the next distillation
13
+ * back-fills the edge.
14
+ */
15
+ import { randomUUID } from "node:crypto";
16
+ import { getSession } from "../lib/neo4j.js";
17
+ export async function voiceRecordFeedback(params) {
18
+ const { accountId, adminUserId, originalText, editedText, intent: rawIntent, context } = params;
19
+ if (!accountId || !adminUserId) {
20
+ throw new Error("voice-record-feedback: accountId and adminUserId required");
21
+ }
22
+ if (!originalText || !editedText) {
23
+ throw new Error("voice-record-feedback: originalText and editedText required");
24
+ }
25
+ const intent = (rawIntent ?? "").trim();
26
+ if (!intent) {
27
+ throw new Error("voice-record-feedback: intent is required — summarise what the operator changed in-turn before calling this tool (Task 424).");
28
+ }
29
+ const editId = randomUUID();
30
+ const occurredAt = new Date().toISOString();
31
+ const cappedIntent = intent.slice(0, 500);
32
+ const session = getSession();
33
+ try {
34
+ const result = await session.run(`MATCH (a:AdminUser {accountId: $accountId, adminUserId: $adminUserId})
35
+ CREATE (e:VoiceEdit {
36
+ editId: $editId,
37
+ accountId: $accountId,
38
+ adminUserId: $adminUserId,
39
+ originalText: $originalText,
40
+ editedText: $editedText,
41
+ intent: $intent,
42
+ occurredAt: $occurredAt,
43
+ skill: $skill,
44
+ brief: $brief
45
+ })
46
+ CREATE (a)-[:AUTHORED]->(e)
47
+ WITH e
48
+ OPTIONAL MATCH (:AdminUser {accountId: $accountId, adminUserId: $adminUserId})-[:HAS_VOICE_PROFILE]->(p:VoiceProfile)
49
+ FOREACH (_ IN CASE WHEN p IS NULL THEN [] ELSE [1] END |
50
+ MERGE (e)-[:FEEDBACK_FOR]->(p)
51
+ )
52
+ RETURN elementId(e) AS editId, (p IS NOT NULL) AS hadProfile`, {
53
+ accountId,
54
+ adminUserId,
55
+ editId,
56
+ originalText,
57
+ editedText,
58
+ intent: cappedIntent,
59
+ occurredAt,
60
+ skill: context?.skill ?? null,
61
+ brief: context?.brief ?? null,
62
+ });
63
+ if (result.records.length === 0) {
64
+ throw new Error(`voice-mirror: :AdminUser {accountId='${accountId}', adminUserId='${adminUserId}'} not found. Onboarding must promote the operator to :AdminUser before feedback can be recorded.`);
65
+ }
66
+ const hadProfile = result.records[0].get("hadProfile") === true;
67
+ process.stderr.write(`[voice-record-feedback] adminUser=${adminUserId} intent="${cappedIntent.replace(/"/g, "'")}" diffBytes=${editedText.length - originalText.length}\n`);
68
+ return { editId, intent: cappedIntent, occurredAt, hadProfile };
69
+ }
70
+ finally {
71
+ await session.close();
72
+ }
73
+ }
74
+ //# sourceMappingURL=voice-record-feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-record-feedback.js","sourceRoot":"","sources":["../../src/tools/voice-record-feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4B7C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAiC;IAEjC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAChG,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,YAAY,IAAI,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,8HAA8H,CAC/H,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;;;;;;;;;;oEAkB8D,EAC9D;YACE,SAAS;YACT,WAAW;YACX,MAAM;YACN,YAAY;YACZ,UAAU;YACV,MAAM,EAAE,YAAY;YACpB,UAAU;YACV,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;YAC7B,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;SAC9B,CACF,CAAC;QACF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,wCAAwC,SAAS,mBAAmB,WAAW,mGAAmG,CACnL,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;QAEhE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qCAAqC,WAAW,YAAY,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,eAAe,UAAU,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,IAAI,CACtJ,CAAC;QAEF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;IAClE,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ export interface VoiceRetrieveConditioningParams {
2
+ accountId: string;
3
+ adminUserId: string;
4
+ brief: {
5
+ topic?: string;
6
+ format: "short" | "long";
7
+ register?: string;
8
+ };
9
+ tokenBudget?: number;
10
+ }
11
+ export interface VoiceExemplar {
12
+ nodeId: string;
13
+ label: string;
14
+ body: string;
15
+ source: string;
16
+ }
17
+ export interface VoiceRetrieveConditioningResult {
18
+ styleCard: string | null;
19
+ exemplars: VoiceExemplar[];
20
+ }
21
+ export declare function voiceRetrieveConditioning(params: VoiceRetrieveConditioningParams): Promise<VoiceRetrieveConditioningResult>;
22
+ //# sourceMappingURL=voice-retrieve-conditioning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-retrieve-conditioning.d.ts","sourceRoot":"","sources":["../../src/tools/voice-retrieve-conditioning.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAID,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,+BAA+B,CAAC,CA2F1C"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * voice-retrieve-conditioning — return the operator's style card plus K
3
+ * voice-matched exemplars for a drafting brief.
4
+ *
5
+ * Retrieval order (best to worst):
6
+ * 1. If brief.topic is provided AND keyword search on the human-only corpus
7
+ * returns ≥K matches → use those (ordered by recency).
8
+ * 2. Otherwise, return the K most-recent human-only nodes.
9
+ *
10
+ * Embeddings are not used in v1: the corpus is small (single operator's
11
+ * personal writing — typically <500 docs), and recency-with-keyword-filter
12
+ * is both faster and more predictable than vector ANN at that scale.
13
+ * Vector-search is a Task 365-follow-up if the corpus grows beyond the
14
+ * 500-doc breakeven point.
15
+ *
16
+ * Graceful degradation: missing profile, empty corpus, and DB errors all
17
+ * return `{styleCard: null, exemplars: []}` rather than throwing — the
18
+ * calling drafting skill falls back to its existing default register.
19
+ */
20
+ import { getSession } from "../lib/neo4j.js";
21
+ import { VOICE_CORPUS_WHERE } from "../lib/voice-corpus.js";
22
+ const DEFAULT_TOKEN_BUDGET_SHORT = 16_000;
23
+ const DEFAULT_TOKEN_BUDGET_LONG = 96_000;
24
+ const CHARS_PER_TOKEN = 4;
25
+ const EMPTY = { styleCard: null, exemplars: [] };
26
+ export async function voiceRetrieveConditioning(params) {
27
+ const { accountId, adminUserId, brief } = params;
28
+ if (!accountId || !adminUserId)
29
+ return EMPTY;
30
+ const k = brief.format === "long" ? 15 : 5;
31
+ const tokenBudget = params.tokenBudget ??
32
+ (brief.format === "long" ? DEFAULT_TOKEN_BUDGET_LONG : DEFAULT_TOKEN_BUDGET_SHORT);
33
+ const charBudget = tokenBudget * CHARS_PER_TOKEN;
34
+ const session = getSession();
35
+ try {
36
+ // 1. Style card.
37
+ const profile = await session.run(`MATCH (a:AdminUser {accountId: $accountId, adminUserId: $adminUserId})
38
+ OPTIONAL MATCH (a)-[:HAS_VOICE_PROFILE]->(p:VoiceProfile)
39
+ RETURN p.styleCard AS styleCard`, { accountId, adminUserId });
40
+ const styleCard = profile.records[0]?.get("styleCard") ?? null;
41
+ // 2. Exemplars. Keyword-aware filter when brief.topic is set.
42
+ const topic = (brief.topic ?? "").trim();
43
+ const usesTopic = topic.length > 0;
44
+ const cypher = usesTopic
45
+ ? `MATCH (n)
46
+ WHERE ${VOICE_CORPUS_WHERE}
47
+ AND toLower(coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '')) CONTAINS toLower($topic)
48
+ WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
49
+ RETURN elementId(n) AS id,
50
+ labels(n) AS labels,
51
+ coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '') AS body,
52
+ coalesce(n.subject, n.title, n.name, '') AS source,
53
+ ts
54
+ ORDER BY ts IS NULL, ts DESC
55
+ LIMIT $k`
56
+ : `MATCH (n)
57
+ WHERE ${VOICE_CORPUS_WHERE}
58
+ WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
59
+ RETURN elementId(n) AS id,
60
+ labels(n) AS labels,
61
+ coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '') AS body,
62
+ coalesce(n.subject, n.title, n.name, '') AS source,
63
+ ts
64
+ ORDER BY ts IS NULL, ts DESC
65
+ LIMIT $k`;
66
+ const result = await session.run(cypher, {
67
+ accountId,
68
+ topic,
69
+ k,
70
+ });
71
+ let charsUsed = 0;
72
+ const exemplars = [];
73
+ for (const r of result.records) {
74
+ const body = r.get("body") ?? "";
75
+ if (body.length === 0)
76
+ continue;
77
+ const labels = r.get("labels") ?? [];
78
+ const label = labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost", "Conversation"].includes(l)) ?? labels[0] ?? "Unknown";
79
+ const remaining = charBudget - charsUsed;
80
+ if (remaining <= 0)
81
+ break;
82
+ const truncated = body.length > remaining ? body.slice(0, remaining) : body;
83
+ exemplars.push({
84
+ nodeId: r.get("id"),
85
+ label,
86
+ body: truncated,
87
+ source: (r.get("source") ?? "").slice(0, 200),
88
+ });
89
+ charsUsed += truncated.length;
90
+ }
91
+ process.stderr.write(`[voice-retrieve] task=${brief.topic ?? brief.format} styleCardBytes=${styleCard?.length ?? 0} exemplarCount=${exemplars.length} tokenBudget=${tokenBudget}\n`);
92
+ return { styleCard, exemplars };
93
+ }
94
+ catch (err) {
95
+ process.stderr.write(`[voice-retrieve] error: ${err instanceof Error ? err.message : String(err)}\n`);
96
+ return EMPTY;
97
+ }
98
+ finally {
99
+ await session.close();
100
+ }
101
+ }
102
+ //# sourceMappingURL=voice-retrieve-conditioning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-retrieve-conditioning.js","sourceRoot":"","sources":["../../src/tools/voice-retrieve-conditioning.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export type AuthorshipMode = "human-only" | "human-led-agent-assisted" | "agent-led-human-reviewed" | "agent-only" | "unknown";
2
+ export declare const AUTHORSHIP_MODES: ReadonlySet<AuthorshipMode>;
3
+ export interface VoiceTagContentParams {
4
+ nodeIds: string[];
5
+ mode: AuthorshipMode;
6
+ accountId: string;
7
+ }
8
+ export interface VoiceTagContentResult {
9
+ updated: number;
10
+ skipped: number;
11
+ skippedReasons: Record<string, number>;
12
+ }
13
+ export declare function voiceTagContent(params: VoiceTagContentParams): Promise<VoiceTagContentResult>;
14
+ //# sourceMappingURL=voice-tag-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-tag-content.d.ts","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"AAWA,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;CACnB;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,CA4EhC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * voice-tag-content — set `authorshipMode` on one or many content nodes.
3
+ *
4
+ * The tool routes through neo4j directly with an account-scoped MERGE so
5
+ * the operator can only mutate their own nodes. We do not go through
6
+ * `memory-update` because that tool re-embeds and re-validates on every
7
+ * call — overkill for a single-property authorship stamp.
8
+ */
9
+ import { getSession } from "../lib/neo4j.js";
10
+ import { TAGGABLE_PRIMARY_LABELS } from "../lib/voice-corpus.js";
11
+ export const AUTHORSHIP_MODES = new Set([
12
+ "human-only",
13
+ "human-led-agent-assisted",
14
+ "agent-led-human-reviewed",
15
+ "agent-only",
16
+ "unknown",
17
+ ]);
18
+ export async function voiceTagContent(params) {
19
+ const { nodeIds, mode, accountId } = params;
20
+ if (!AUTHORSHIP_MODES.has(mode)) {
21
+ throw new Error(`voice-tag-content: invalid authorshipMode '${mode}'. Valid values: ${[
22
+ ...AUTHORSHIP_MODES,
23
+ ].join(", ")}`);
24
+ }
25
+ if (!accountId) {
26
+ throw new Error("voice-tag-content: accountId is required");
27
+ }
28
+ if (!Array.isArray(nodeIds) || nodeIds.length === 0) {
29
+ return { updated: 0, skipped: 0, skippedReasons: {} };
30
+ }
31
+ const session = getSession();
32
+ const now = new Date().toISOString();
33
+ const skippedReasons = {};
34
+ let updated = 0;
35
+ try {
36
+ // Bulk update in batches of 50 to keep transaction latency bounded.
37
+ const BATCH = 50;
38
+ for (let i = 0; i < nodeIds.length; i += BATCH) {
39
+ const batch = nodeIds.slice(i, i + BATCH);
40
+ const result = await session.run(
41
+ // Tag any of the four primary content labels, or a conversation-
42
+ // document :Section chunk (parent :KnowledgeDocument carries
43
+ // `conversationIdentity`). The EXISTS sub-pattern guards against
44
+ // accidentally tagging document-path :Section rows AND parent
45
+ // `:Conversation` / `:AdminConversation` nodes (which are not
46
+ // body-bearing content).
47
+ `UNWIND $ids AS id
48
+ MATCH (n) WHERE elementId(n) = id AND n.accountId = $accountId
49
+ WITH n
50
+ WHERE any(lbl IN labels(n) WHERE lbl IN $primaryLabels)
51
+ OR ('Section' IN labels(n) AND EXISTS {
52
+ MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
53
+ WHERE parent.conversationIdentity IS NOT NULL
54
+ })
55
+ SET n.authorshipMode = $mode,
56
+ n.authorshipTaggedAt = $now
57
+ RETURN elementId(n) AS id`, {
58
+ ids: batch,
59
+ accountId,
60
+ mode,
61
+ now,
62
+ primaryLabels: [...TAGGABLE_PRIMARY_LABELS],
63
+ });
64
+ const matched = new Set(result.records.map((r) => r.get("id")));
65
+ updated += matched.size;
66
+ for (const id of batch) {
67
+ if (matched.has(id))
68
+ continue;
69
+ const reason = "not-found-or-not-content-label";
70
+ skippedReasons[reason] = (skippedReasons[reason] ?? 0) + 1;
71
+ }
72
+ }
73
+ }
74
+ finally {
75
+ await session.close();
76
+ }
77
+ process.stderr.write(`[voice-tag] mode=${mode} accountId=${accountId} updated=${updated} skipped=${nodeIds.length - updated}\n`);
78
+ return {
79
+ updated,
80
+ skipped: nodeIds.length - updated,
81
+ skippedReasons,
82
+ };
83
+ }
84
+ //# sourceMappingURL=voice-tag-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-tag-content.js","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"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"}