@rubytech/create-maxy-code 0.1.460 → 0.1.462
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/dist/__tests__/cron-heartbeat-registration.test.js +57 -28
- package/dist/__tests__/launchd-plist.test.js +26 -0
- package/dist/cron-registration.js +30 -8
- package/dist/index.js +50 -9
- package/dist/launchd-plist.js +1 -1
- package/dist/uninstall.js +9 -3
- package/package.json +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.d.ts +2 -1
- package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.js +14 -22
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/index.ts +15 -22
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +8 -7
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +19 -19
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/src/__tests__/spawn-tee.test.ts +34 -5
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +22 -19
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +35 -17
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js +41 -48
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/src/index.ts +42 -51
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +27 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +49 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +11 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.js +16 -0
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +18 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +77 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-scoped-token.js +3 -0
- package/payload/platform/lib/storage-broker/dist/house-scoped-token.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.d.ts +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +32 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +61 -0
- package/payload/platform/lib/storage-broker/src/audit.ts +45 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +91 -0
- package/payload/platform/lib/storage-broker/src/house-scoped-token.ts +3 -0
- package/payload/platform/lib/storage-broker/src/registry.ts +5 -1
- package/payload/platform/plugins/admin/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +22 -11
- package/payload/platform/plugins/aeo/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/browser/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +54 -40
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +169 -39
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +207 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +5 -5
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/package.json +1 -1
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +2 -0
- package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +21 -19
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.mjs +193 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +18 -12
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +14 -5
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +2 -0
- package/payload/platform/plugins/connector/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/contacts/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/docs/references/admin-session.md +3 -3
- package/payload/platform/plugins/docs/references/cloudflare.md +2 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +11 -4
- package/payload/platform/plugins/docs/references/plugins-guide.md +8 -6
- package/payload/platform/plugins/email/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/filesystem/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/graph-viewer/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +2 -2
- package/payload/platform/plugins/outlook/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.js +238 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +9 -5
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +58 -9
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +113 -19
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +5 -2
- package/payload/platform/plugins/quickbooks/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/replicate/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/scheduling/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/storage-broker/PLUGIN.md +9 -1
- package/payload/platform/plugins/storage-broker/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js +16 -5
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +49 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/url-get/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +98 -9
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js +54 -21
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +110 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +6 -3
- package/payload/platform/plugins/web-designer/PLUGIN.md +1 -1
- package/payload/platform/plugins/web-designer/commands/website.md +6 -0
- package/payload/platform/plugins/web-designer/skills/web-design/references/deploy.md +12 -0
- package/payload/platform/plugins/whatsapp/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/work/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/workflows/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/scripts/__tests__/logs-rotate.test.sh +260 -0
- package/payload/platform/scripts/__tests__/resume-tunnel.test.sh +79 -0
- package/payload/platform/scripts/logs-read.sh +31 -0
- package/payload/platform/scripts/logs-rotate.sh +207 -0
- package/payload/platform/scripts/resume-tunnel.sh +27 -0
- 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 +2 -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.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +41 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +22 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js +12 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +122 -9
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +214 -29
- package/payload/platform/services/claude-session-manager/dist/pty-census.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 +39 -1
- 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 +5 -3
- 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 +13 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js +4 -2
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +150 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +333 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js +55 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js.map +1 -0
- package/payload/server/{chunk-JXWFVE5X.js → chunk-BBSPFN45.js} +65 -0
- package/payload/server/public/assets/{AdminLoginScreens-BFbpkEUS.js → AdminLoginScreens-Bed8SDxY.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CdOGObaZ.js → AdminShell-BLh9O6dn.js} +1 -1
- package/payload/server/public/assets/{Checkbox-RFD_F0c0.js → Checkbox-BC_KSbpS.js} +1 -1
- package/payload/server/public/assets/{admin-CYGdL-1O.js → admin-DfWgJVI3.js} +1 -1
- package/payload/server/public/assets/{browser-DVfa_plj.js → browser-C0vkv-hH.js} +1 -1
- package/payload/server/public/assets/{calendar-JC_Dxa4n.js → calendar-_pfHw0Hp.js} +1 -1
- package/payload/server/public/assets/chat-CufidiBQ.js +1 -0
- package/payload/server/public/assets/chevron-left-BnpVBu51.js +1 -0
- package/payload/server/public/assets/data-CR-fbPWr.js +1 -0
- package/payload/server/public/assets/{graph-hBUrRSkr.js → graph-BZiudUGr.js} +1 -1
- package/payload/server/public/assets/{graph-labels-CHh6zs8t.js → graph-labels-BzmmJwCR.js} +1 -1
- package/payload/server/public/assets/{maximize-2-DjxE4rm9.js → maximize-2-Bhdsv0U-.js} +1 -1
- package/payload/server/public/assets/{operator-BqU9SQuu.js → operator-Cj0B_usT.js} +1 -1
- package/payload/server/public/assets/page-84Fdwiu_.js +32 -0
- package/payload/server/public/assets/{page-DiQhU6lD.js → page-8AB3jUa_.js} +1 -1
- package/payload/server/public/assets/{public-Cu_eALgY.js → public-BkNW9e9T.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-F9DsSTSJ.js → rotate-ccw-D_5cETGt.js} +1 -1
- package/payload/server/public/assets/{tasks-CE-D0f50.js → tasks-EyKLXINi.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-D2rsWhov.js → time-entry-format-DAwmmR3i.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-QxOmoobV.js → useCopyFeedback-rOWd_lzl.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-RIzNqFjV.js → useSelectionMode-DDmLmPph.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-BW_fK1Nt.css → useSubAccountSwitcher-pu0jjLCz.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-WrWurSYI.js → useVoiceRecorder-azG8zSUI.js} +1 -1
- 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 +581 -209
- package/payload/server/{src-4F37OHLK.js → src-YLGAZY36.js} +5 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.ts +0 -73
- package/payload/server/public/assets/chat-Yf8Nkop8.js +0 -1
- package/payload/server/public/assets/chevron-left-CAiAUBxO.js +0 -1
- package/payload/server/public/assets/data-hqdYXaEn.js +0 -1
- package/payload/server/public/assets/page-B4c4_4HP.js +0 -32
- /package/payload/server/public/assets/{useSubAccountSwitcher-wo_qGW-I.js → useSubAccountSwitcher-CU-lOxTd.js} +0 -0
|
@@ -10,9 +10,18 @@
|
|
|
10
10
|
* - All callers updated in the same commit.
|
|
11
11
|
*
|
|
12
12
|
* Retrieval order (best to worst):
|
|
13
|
-
* 1.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* 1. Corpus nodes whose body carries the most `brief.topic` terms.
|
|
14
|
+
* 2. Within an equal term count, the most recent.
|
|
15
|
+
*
|
|
16
|
+
* `brief.topic` ranks; it never excludes. Until Task 1719 it was applied as
|
|
17
|
+
* `WHERE toLower(body) CONTAINS toLower($topic)` — a single literal
|
|
18
|
+
* whole-phrase match — so a multi-word topic ('programme outline training')
|
|
19
|
+
* matched no body and the walk returned zero exemplars over a non-empty
|
|
20
|
+
* corpus, while `status` still read "ok" because a style card alone satisfies
|
|
21
|
+
* `hasData`. Ranking honours this header's original intent (topic hits first,
|
|
22
|
+
* K backfilled by recency) in one pass: a voice profile conditions on how the
|
|
23
|
+
* operator writes, not what about, so an off-topic exemplar still carries the
|
|
24
|
+
* signal and is always worth more than nothing.
|
|
16
25
|
*
|
|
17
26
|
* Embeddings are not used in v1: the corpus is small (single operator's
|
|
18
27
|
* personal writing — typically <500 docs), and recency-with-keyword-filter
|
|
@@ -36,6 +45,16 @@ const CHARS_PER_TOKEN = 4;
|
|
|
36
45
|
function errorResult(error) {
|
|
37
46
|
return { styleCard: null, exemplars: [], status: "error", error };
|
|
38
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Cypher `size()` arrives as a driver Integer, not a JS number, and an Integer
|
|
50
|
+
* is truthy at zero — so comparing the raw value would count every row as a
|
|
51
|
+
* topic hit. Narrow it before any arithmetic.
|
|
52
|
+
*/
|
|
53
|
+
function toNumber(value) {
|
|
54
|
+
if (neo4j.isInt(value))
|
|
55
|
+
return value.toNumber();
|
|
56
|
+
return typeof value === "number" ? value : 0;
|
|
57
|
+
}
|
|
39
58
|
export async function voiceRetrieveConditioning(params) {
|
|
40
59
|
const { accountId, userId, brief } = params;
|
|
41
60
|
const scope = brief.scope ?? "personal";
|
|
@@ -55,12 +74,18 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
55
74
|
(length === "long" ? DEFAULT_TOKEN_BUDGET_LONG : DEFAULT_TOKEN_BUDGET_SHORT);
|
|
56
75
|
const charBudget = tokenBudget * CHARS_PER_TOKEN;
|
|
57
76
|
const format = brief.format;
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
// Distinct lowercased topic terms. Lowercasing happens here, once, so the
|
|
78
|
+
// Cypher compares term-to-body without a per-row toLower on the term. An
|
|
79
|
+
// absent/blank topic yields [], which scores every row 0 and leaves the
|
|
80
|
+
// ordering to recency alone — the same walk, no branch.
|
|
81
|
+
const topicTerms = Array.from(new Set((brief.topic ?? "")
|
|
82
|
+
.toLowerCase()
|
|
83
|
+
.split(/\s+/)
|
|
84
|
+
.filter((t) => t.length > 0)));
|
|
60
85
|
// Body resolution (Task 471): for :KnowledgeDocument with HAS_SECTION
|
|
61
86
|
// children, concatenate child :Section bodies in `position` order (KD.body is
|
|
62
87
|
// null after Task 465 server-slicing); other labels read n.body via the
|
|
63
|
-
// COALESCE chain.
|
|
88
|
+
// COALESCE chain. Topic ranking scores against the computed body.
|
|
64
89
|
const bodyComputeBlock = `WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
|
|
65
90
|
OPTIONAL MATCH (n)-[:HAS_SECTION]->(s:Section)
|
|
66
91
|
WHERE ${notTrashed("s")}
|
|
@@ -86,30 +111,29 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
86
111
|
const profile = await session.run(`OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
|
|
87
112
|
RETURN p.styleCard AS styleCard`, { accountId, profileUserId, format });
|
|
88
113
|
const styleCard = profile.records[0]?.get("styleCard") ?? null;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
96
|
-
ORDER BY ts IS NULL, ts DESC
|
|
97
|
-
LIMIT $k`
|
|
98
|
-
: `MATCH (n)
|
|
114
|
+
// `topicScore` counts how many distinct topic terms the body carries and
|
|
115
|
+
// orders on it. It is a rank, not a predicate: a row scoring 0 stays in
|
|
116
|
+
// the result set and is simply ordered behind the hits, so the walk
|
|
117
|
+
// returns up to K exemplars whenever the corpus holds any, whatever the
|
|
118
|
+
// topic. `ts IS NULL` keeps undated nodes last within an equal score.
|
|
119
|
+
const cypher = `MATCH (n)
|
|
99
120
|
WHERE ${exemplarWhere}
|
|
100
121
|
${bodyComputeBlock}
|
|
122
|
+
WITH n, ts, body,
|
|
123
|
+
size([t IN $topicTerms WHERE toLower(body) CONTAINS t]) AS topicScore
|
|
101
124
|
RETURN elementId(n) AS id, labels(n) AS labels, body,
|
|
102
|
-
coalesce(n.subject, n.title, n.name, '') AS source, ts
|
|
103
|
-
ORDER BY ts IS NULL, ts DESC
|
|
125
|
+
coalesce(n.subject, n.title, n.name, '') AS source, ts, topicScore
|
|
126
|
+
ORDER BY topicScore DESC, ts IS NULL, ts DESC
|
|
104
127
|
LIMIT $k`;
|
|
105
128
|
const result = await session.run(cypher, {
|
|
106
129
|
...exemplarParams,
|
|
107
|
-
|
|
130
|
+
topicTerms,
|
|
108
131
|
// neo4j-driver serializes a plain JS number as a Cypher float (15 →
|
|
109
132
|
// 15.0), which LIMIT rejects. Send a Neo4j integer.
|
|
110
133
|
k: neo4j.int(k),
|
|
111
134
|
});
|
|
112
135
|
let charsUsed = 0;
|
|
136
|
+
let topicHits = 0;
|
|
113
137
|
const exemplars = [];
|
|
114
138
|
for (const r of result.records) {
|
|
115
139
|
const body = r.get("body") ?? "";
|
|
@@ -128,8 +152,10 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
128
152
|
source: (r.get("source") ?? "").slice(0, 200),
|
|
129
153
|
});
|
|
130
154
|
charsUsed += truncated.length;
|
|
155
|
+
if (toNumber(r.get("topicScore")) > 0)
|
|
156
|
+
topicHits += 1;
|
|
131
157
|
}
|
|
132
|
-
return { styleCard, exemplars };
|
|
158
|
+
return { styleCard, exemplars, topicHits };
|
|
133
159
|
};
|
|
134
160
|
const hasData = (r) => (r.styleCard !== null && r.styleCard.length > 0) || r.exemplars.length > 0;
|
|
135
161
|
const orgLookup = () => lookup(ORG_USER_ID, voiceCorpusWhereWithFormat(format), { accountId, format });
|
|
@@ -154,8 +180,15 @@ export async function voiceRetrieveConditioning(params) {
|
|
|
154
180
|
result = org;
|
|
155
181
|
status = hasData(org) ? "ok" : "no-data";
|
|
156
182
|
}
|
|
183
|
+
// `topicTerms`/`topicHits` are the pair that makes a thin result legible
|
|
184
|
+
// without reproducing it: exemplarCount=0 says the scope's corpus is empty,
|
|
185
|
+
// and topicHits=0 with topicTerms>0 says the exemplars are recency-picked
|
|
186
|
+
// rather than topic-matched. Before Task 1719 the two were indistinguishable
|
|
187
|
+
// — a topic that matched nothing and an absent corpus both logged
|
|
188
|
+
// exemplarCount=0, and only the first was a defect.
|
|
157
189
|
process.stderr.write(`[voice-retrieve] scope=${scope} format=${format} status=${status} ` +
|
|
158
|
-
`styleCardBytes=${result.styleCard?.length ?? 0} exemplarCount=${result.exemplars.length}
|
|
190
|
+
`styleCardBytes=${result.styleCard?.length ?? 0} exemplarCount=${result.exemplars.length} ` +
|
|
191
|
+
`topicTerms=${topicTerms.length} topicHits=${result.topicHits} tokenBudget=${tokenBudget}\n`);
|
|
159
192
|
return { styleCard: result.styleCard, exemplars: result.exemplars, status };
|
|
160
193
|
}
|
|
161
194
|
catch (err) {
|
package/payload/platform/plugins/voice-mirror/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
|
|
1
|
+
{"version":3,"file":"voice-retrieve-conditioning.js","sourceRoot":"","sources":["../../src/tools/voice-retrieve-conditioning.ts"],"names":[],"mappings":"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"}
|
|
@@ -644,6 +644,63 @@ check("voice-distil-profile keeps both structural write anchors", () => {
|
|
|
644
644
|
assert.equal(anchors?.length, 2, "both write-path anchors retained");
|
|
645
645
|
});
|
|
646
646
|
|
|
647
|
+
check("both write paths aggregate the sample link in a CALL subquery", () => {
|
|
648
|
+
// The load-bearing invariant of the zero-record fix. A bare
|
|
649
|
+
// `WITH p UNWIND $ids ... RETURN elementId(p)` tail collapses the row when the
|
|
650
|
+
// id list is empty or every id is stale, so records.length === 0 stops meaning
|
|
651
|
+
// "the anchor missed" and the write reports an identity error for what is a
|
|
652
|
+
// content condition. The CALL subquery's bare aggregation returns a row over
|
|
653
|
+
// empty input, which is what keeps the outer row alive. Reverting either tail
|
|
654
|
+
// reintroduces the bug silently, so pin the shape here.
|
|
655
|
+
const subqueries = distilSrc.match(/RETURN count\(\*\) AS learned/g);
|
|
656
|
+
assert.equal(subqueries?.length, 2, "both write paths aggregate with count(*)");
|
|
657
|
+
// Positive form: each sample UNWIND must sit inside a CALL block. A negative
|
|
658
|
+
// "no bare UNWIND" regex cannot tell the subquery's own UNWIND from an outer
|
|
659
|
+
// one, so it fires on correct code.
|
|
660
|
+
const wrapped = distilSrc.match(
|
|
661
|
+
/CALL \{\s*\n\s*WITH p\s*\n\s*UNWIND \$(sampleIds|amendIds) AS sid/g,
|
|
662
|
+
);
|
|
663
|
+
assert.equal(wrapped?.length, 2, "both sample links are wrapped in a CALL subquery");
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
check("the zero-resolved write reports content, never identity", () => {
|
|
667
|
+
// The whole point of the fix: a merged profile with no sources must not reuse
|
|
668
|
+
// the unknown-:AdminUser wording, and must not tell the caller their id is
|
|
669
|
+
// wrong. Both messages disclaim identity explicitly.
|
|
670
|
+
// The messages are built by concatenating template literals, so a sentence can
|
|
671
|
+
// straddle a `+ ` join in source while being whole at runtime. Flatten the
|
|
672
|
+
// joins before matching prose, or these assertions test the formatting rather
|
|
673
|
+
// than the message.
|
|
674
|
+
const flat = distilSrc.replace(/`\s*\+\s*\n\s*`/g, "");
|
|
675
|
+
const disclaimers = flat.match(
|
|
676
|
+
/This is a content condition, not an identity one\./g,
|
|
677
|
+
);
|
|
678
|
+
assert.equal(disclaimers?.length, 2, "both zero-resolved messages disclaim identity");
|
|
679
|
+
assert.ok(
|
|
680
|
+
/none carried readable text to sample/.test(flat),
|
|
681
|
+
"the empty-sample cause has its own wording",
|
|
682
|
+
);
|
|
683
|
+
assert.ok(
|
|
684
|
+
/stopped resolving/.test(flat),
|
|
685
|
+
"the vanished-samples cause has its own wording",
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
check("the vanished-samples message does not blame trashing", () => {
|
|
690
|
+
// A trashed node keeps its elementId, and the write resolves with a bare
|
|
691
|
+
// `MATCH (s) WHERE elementId(s) = sid` carrying no trash predicate, so it
|
|
692
|
+
// still resolves and still takes its edge. Only a hard delete can produce the
|
|
693
|
+
// condition. Naming trashing sends the operator to a trash that cannot hold
|
|
694
|
+
// the cause, which is the same misattribution the fix removes.
|
|
695
|
+
const at = distilSrc.indexOf("function noSourcesResolvedMessage");
|
|
696
|
+
assert.ok(at > -1, "noSourcesResolvedMessage present");
|
|
697
|
+
const body = distilSrc.slice(at, at + 1200);
|
|
698
|
+
assert.ok(
|
|
699
|
+
!/deleted or trashed/.test(body),
|
|
700
|
+
"must not name trashing as a cause of an unresolved id",
|
|
701
|
+
);
|
|
702
|
+
});
|
|
703
|
+
|
|
647
704
|
const retrieveSrc = readFileSync(
|
|
648
705
|
join(TOOLS_SRC, "voice-retrieve-conditioning.ts"),
|
|
649
706
|
"utf-8",
|
|
@@ -685,6 +742,59 @@ check("voice-retrieve-conditioning never sets isError", () => {
|
|
|
685
742
|
);
|
|
686
743
|
});
|
|
687
744
|
|
|
745
|
+
// Comments stripped, so a check that forbids a Cypher clause cannot be tripped
|
|
746
|
+
// by documentation *describing* that clause. The header of
|
|
747
|
+
// voice-retrieve-conditioning.ts quotes the topic-rank predicate verbatim to
|
|
748
|
+
// explain why it went; that prose must not read as the predicate itself.
|
|
749
|
+
const retrieveCode = retrieveSrc
|
|
750
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
751
|
+
.replace(/^\s*\/\/.*$/gm, "");
|
|
752
|
+
|
|
753
|
+
check("voice-retrieve-conditioning topic ranks and never excludes", () => {
|
|
754
|
+
// The regression guard for the reported defect: brief.topic was applied as a
|
|
755
|
+
// literal whole-phrase predicate, so a multi-word topic matched no body and
|
|
756
|
+
// the walk returned zero exemplars over a non-empty corpus. Any predicate
|
|
757
|
+
// that tests the topic inside a WHERE reintroduces it, whether phrased on
|
|
758
|
+
// $topic or on the terms — the topic belongs in the ORDER BY alone.
|
|
759
|
+
assert.ok(
|
|
760
|
+
!/CONTAINS\s+toLower\(\$topic\)/.test(retrieveCode),
|
|
761
|
+
"topic must not filter the exemplar walk: a non-matching topic empties a non-empty corpus",
|
|
762
|
+
);
|
|
763
|
+
assert.ok(
|
|
764
|
+
!/WHERE[^\n]*\$topicTerms/.test(retrieveCode),
|
|
765
|
+
"topic terms must not appear in a WHERE that gates the walk, only in the ranked projection",
|
|
766
|
+
);
|
|
767
|
+
assert.ok(
|
|
768
|
+
/ORDER BY topicScore DESC/.test(retrieveCode),
|
|
769
|
+
"topic must rank the walk: topicScore orders, recency breaks the tie",
|
|
770
|
+
);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
check("voice-retrieve-conditioning narrows Cypher integers before comparing", () => {
|
|
774
|
+
// A driver Integer is truthy at zero, so `topicScore > 0` on the raw value
|
|
775
|
+
// would count every row as a topic hit and make the observability line lie.
|
|
776
|
+
assert.ok(
|
|
777
|
+
/toNumber\(r\.get\("topicScore"\)\)\s*>\s*0/.test(retrieveSrc),
|
|
778
|
+
"topicScore must be narrowed via toNumber() before the hit comparison",
|
|
779
|
+
);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
check("voice-retrieve-conditioning builds distinct lowercased topic terms", () => {
|
|
783
|
+
// Asserted on the source, never on dist/: dist is a build artifact that
|
|
784
|
+
// survives a source revert, so a check reading it passes against code that no
|
|
785
|
+
// longer exists. The parse is what turns a phrase into rankable terms —
|
|
786
|
+
// lowercased once here so the Cypher compares term-to-body directly, and
|
|
787
|
+
// de-duplicated so a repeated word cannot inflate topicScore.
|
|
788
|
+
assert.ok(
|
|
789
|
+
/\.toLowerCase\(\)[\s\S]{0,60}\.split\(\/\\s\+\/\)/.test(retrieveSrc),
|
|
790
|
+
"topic must be lowercased and split on whitespace into terms",
|
|
791
|
+
);
|
|
792
|
+
assert.ok(
|
|
793
|
+
/const topicTerms = Array\.from\(\s*new Set\(/.test(retrieveSrc),
|
|
794
|
+
"topic terms must be de-duplicated so a repeated word cannot inflate topicScore",
|
|
795
|
+
);
|
|
796
|
+
});
|
|
797
|
+
|
|
688
798
|
// ---------------------------------------------------------------------------
|
|
689
799
|
// 14. One refusal wording across every gate (Task 1701).
|
|
690
800
|
// record-feedback and ingest already enforced the rule via an inline
|
|
@@ -115,7 +115,7 @@ This runs only on the admin seat (the tool is admin-allowlisted). Accounts witho
|
|
|
115
115
|
|
|
116
116
|
## Flow — drafting-time conditioning
|
|
117
117
|
|
|
118
|
-
Drafting skills (email composition, Postiz, property-brochure, investor-data-room Stage 3 prose, prospectus) call `voice-retrieve-conditioning` transparently before assembling their prompt. The skill takes a brief — `{topic, format, length, register, scope}` — where `format` is the target corpus format and `scope` picks whose voice to draft in. Default `scope` is `personal` (the calling operator's own voice); a drafting skill whose output goes out under the business name passes `scope: "org"` for the house voice. Email composition stays personal; property-brochure, the investor-data-room Stage 3 business plan, and the prospectus skills request `org`. It returns:
|
|
118
|
+
Drafting skills (email composition, Postiz, property-brochure, investor-data-room Stage 3 prose, prospectus) call `voice-retrieve-conditioning` transparently before assembling their prompt. The skill takes a brief — `{topic, format, length, register, scope}` — where `format` is the target corpus format and `scope` picks whose voice to draft in. Default `scope` is `personal` (the calling operator's own voice); a drafting skill whose output goes out under the business name passes `scope: "org"` for the house voice. Email composition stays personal; property-brochure, the investor-data-room Stage 3 business plan, and the prospectus skills request `org`. `topic` is optional and only ranks the exemplars: its terms sort the corpus by how many they match, and a topic matching nothing still returns the most recent writing. Describe the subject as naturally as you like, because no phrasing can cost you exemplars. It returns:
|
|
119
119
|
|
|
120
120
|
```yaml
|
|
121
121
|
styleCard: |
|
|
@@ -209,7 +209,9 @@ A value naming no `:AdminUser` on the account is refused, and nothing is written
|
|
|
209
209
|
|
|
210
210
|
The tools resolve identity from the spawn environment on their own. Pass `author:` only to attribute content to a different operator on a multi-operator account, and only with that operator's real `:AdminUser` id.
|
|
211
211
|
|
|
212
|
-
If a tool refuses
|
|
212
|
+
If a tool refuses *on identity*, the id is wrong. Do not retry with a different guess. The account's real admin identity is the operator's to supply.
|
|
213
|
+
|
|
214
|
+
Not every refusal is an identity refusal. `voice-distil-profile` also refuses when the profile merged but no source content could be linked to it, either because no sampled item carried readable text or because the sampled items stopped resolving mid-run. Those messages say so, and close with "This is a content condition, not an identity one." Changing the id will not fix them, and the `[voice-identity] refuse` line is absent when they fire.
|
|
213
215
|
|
|
214
216
|
`__org__` is exempt, because the house voice anchors on the business rather than on a person.
|
|
215
217
|
|
|
@@ -222,6 +224,7 @@ If a tool refuses, the id is wrong. Do not retry with a different guess. The acc
|
|
|
222
224
|
| `[voice-distil] scope=<scope> userId=<id\|__org__> format=<format> skip reason=<below-threshold\|recent\|empty-corpus>` | When the cadence/empty guard fires. |
|
|
223
225
|
| `[voice-distil] scope=<scope> mode=amend userId=<id\|__org__> format=<format> nodeIds=<count> existingProfile=<elementId\|none> eligible=<n> ineligible=<n> exemplars=<n>` | Each amend-mode call. `eligible=0` means surface `ineligible[]` to the operator, never silently no-op. |
|
|
224
226
|
| `[voice-distil] scope=<scope> mode=write userId=<id\|__org__> anchor=<…> format=<format> amended=true amendedFromNodeIds=<count> ...` | Amend-write fired (operator-initiated update persisted). |
|
|
227
|
+
| `[voice-distil] scope=<scope> userId=<id\|__org__> anchor=<…> format=<format> mode=write sources=none attempted=<n> resolved=0 ...` | The profile merged and the style card saved, but no `LEARNED_FROM` edge was written. `attempted=0` means no sampled item carried readable text; `attempted>0` means every sampled item stopped resolving between the walk and the write. Either way the call throws, and the cause is content, not identity. |
|
|
225
228
|
| `[voice-retrieve] scope=<scope> format=<format> status=<ok\|fallback-org\|no-data\|error> styleCardBytes=<n> exemplarCount=<k> tokenBudget=<n>` | Each retrieval. `status=fallback-org` proves the personal→org fallback fired. |
|
|
226
229
|
| `[voice-record-feedback] scope=<scope> userId=<id\|__org__> format=<format> intent="<haiku-summary>" diffBytes=<n>` | Each feedback write. |
|
|
227
230
|
| `[voice-ingest-session-text] sessionId=<id> adminUser=<id> turns=<n> bytes=<n> skipped=<m>` | On-demand session-text capture. |
|
|
@@ -229,7 +232,7 @@ If a tool refuses, the id is wrong. Do not retry with a different guess. The acc
|
|
|
229
232
|
|
|
230
233
|
**Confirms working:** `[voice-retrieve] exemplarCount≥1` precedes every drafting-skill prompt assembly when the calling skill has not opted out. `[voice-distil]` appears at least once per 30-day window per active format per operator. `[voice-ingest-session-text] turns≥1` appears when the operator asks to capture a session's voice.
|
|
231
234
|
|
|
232
|
-
**Indicates failure:** `[voice-retrieve] exemplarCount=0` with non-empty corpus → similarity index broken; `[voice-distil] error …` → corpus walk or LLM call failed; absence of `[voice-record-feedback]` after an operator-edit-then-send flow → feedback loop is dead.
|
|
235
|
+
**Indicates failure:** `[voice-retrieve] exemplarCount=0` with non-empty corpus → similarity index broken; `[voice-distil] error …` → corpus walk or LLM call failed; `[voice-distil] … sources=none attempted=0` → the corpus for that format is tagged but holds no readable text, so the profile has a style card and no sources; absence of `[voice-record-feedback]` after an operator-edit-then-send flow → feedback loop is dead.
|
|
233
236
|
|
|
234
237
|
**Diagnostic path:**
|
|
235
238
|
|
|
@@ -46,7 +46,7 @@ The plugin's only host coupling is `bin/resolve-cf.sh`. It resolves two things a
|
|
|
46
46
|
|
|
47
47
|
Going live and the richer Cloudflare features reuse existing, proven surfaces rather than reimplementing them. The deploy references in `skills/web-design/references/` name each dependency:
|
|
48
48
|
|
|
49
|
-
- **deploy + D1 forms** — inside maxy-code the canonical site tree is handed to `cloudflare:site-deploy` unchanged; standalone it follows the generic `wrangler pages deploy` sequence from the Cloudflare references. Extra form "buckets" are extra D1 tables and Pages Functions on the same pattern.
|
|
49
|
+
- **deploy + D1 forms** — inside maxy-code the canonical site tree is handed to `cloudflare:site-deploy` unchanged; standalone it follows the generic `wrangler pages deploy` sequence from the Cloudflare references. On a multi-tenant install a **client account** takes neither path: it is denied the `pages` scope (`reason=not-house`) and publishes with `storage-broker:storage-pages-deploy`, which deploys house-side against a project the house has recorded to it. Extra form "buckets" are extra D1 tables and Pages Functions on the same pattern.
|
|
50
50
|
- **gated access** — Cloudflare Access is composed from the Cloudflare Access references and the proven gating pattern; no new gating mechanics are authored here.
|
|
51
51
|
- **e-signing** — routed to the `business-assistant:e-sign` skill, which owns signature capture, D1 recording, PDF stamping, and dispatch. This is a host dependency, not shipped self-contained in the marketplace edition.
|
|
52
52
|
|
|
@@ -52,6 +52,12 @@ site-tree root come from is resolved by `bin/resolve-cf.sh`:
|
|
|
52
52
|
account directory automatically and hands the built tree to `cloudflare:site-deploy`
|
|
53
53
|
unchanged. The skill triggers ("design me a website") remain the primary surface
|
|
54
54
|
there; this command is an equivalent entry point.
|
|
55
|
+
- **Inside maxy-code, as a client account on a multi-tenant install:** there is no
|
|
56
|
+
account Cloudflare token to resolve — the house holds the only master, and
|
|
57
|
+
`cf-token.sh` denies the `pages` scope (`reason=not-house`). The build is unchanged;
|
|
58
|
+
only going live differs. Publish with `storage-broker:storage-pages-deploy`, naming
|
|
59
|
+
the site folder and the Pages project. The project must already be recorded to the
|
|
60
|
+
account by the house.
|
|
55
61
|
|
|
56
62
|
## What you get back
|
|
57
63
|
|
|
@@ -8,6 +8,18 @@ tree per site; never deploy from a `.bak` clone or a stale sibling.
|
|
|
8
8
|
|
|
9
9
|
This reference composes existing deploy mechanics. It does not fork them.
|
|
10
10
|
|
|
11
|
+
## First: on a multi-tenant install, a client account publishes through the broker
|
|
12
|
+
|
|
13
|
+
If this session is a **client account** on an install where the house
|
|
14
|
+
holds the sole Cloudflare master (the post-1631 posture), neither path below will
|
|
15
|
+
work: `cf-token.sh` denies the `pages` scope with `reason=not-house`, because the
|
|
16
|
+
master is account-wide. Publish with `storage-pages-deploy` instead — it names the
|
|
17
|
+
site folder and the project, and the deploy runs house-side. The project must
|
|
18
|
+
already be recorded to this account by the house (`storage-pages-adopt`); if the
|
|
19
|
+
tool answers `unregistered`, ask the house to record it rather than trying to mint
|
|
20
|
+
a token. The two paths below are the **house account** and the **standalone**
|
|
21
|
+
editions.
|
|
22
|
+
|
|
11
23
|
## Inside maxy-code: hand off to `cloudflare:site-deploy`
|
|
12
24
|
|
|
13
25
|
When the host carries the `cloudflare` plugin, hand the canonical tree to the
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Claude Code CLI → shim (this file) = node running <real-entry> in-process
|
|
15
15
|
* stdin/stdout : untouched (JSON-RPC channel)
|
|
16
|
-
* stderr : process.stderr.write teed →
|
|
16
|
+
* stderr : process.stderr.write teed → raw sink + passthrough
|
|
17
17
|
*
|
|
18
|
-
* Destinations
|
|
19
|
-
* - `${LOG_DIR}/mcp-<name
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Destinations:
|
|
19
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — raw stderr + lifecycle lines.
|
|
20
|
+
* The sole raw sink. Task 1721 retired the per-date twin: its date was
|
|
21
|
+
* pinned at spawn, no reader ever parsed it, and mcp-stderr-tee wrote the
|
|
22
|
+
* same file, so every line landed twice.
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-nosession.log` — the same, for a spawn with
|
|
24
|
+
* no SESSION_ID (enumeration). One fixed file per server.
|
|
24
25
|
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
25
26
|
* the [mcp-helper] lifecycle lines.
|
|
26
27
|
*
|
|
@@ -49,13 +50,13 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
49
50
|
const ENTRY = process.argv[2];
|
|
50
51
|
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
52
|
const spawnStamp = Date.now();
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}
|
|
53
|
+
// The one raw sink (Task 1721). A sessionless spawn (rc-daemon stamps
|
|
54
|
+
// SESSION_ID='' — rc-daemon.ts:513 — while still stamping LOG_DIR) shares one
|
|
55
|
+
// fixed file per server rather than a date-named one: the date was never read
|
|
56
|
+
// by any reader and named a process lifetime, not a day. Interleaved spawns in
|
|
57
|
+
// the nosession file are separated by the [mcp-helper] op=spawn pid= lines.
|
|
58
|
+
const rawSinkPath = LOG_DIR
|
|
59
|
+
? (0, node_path_1.resolve)(LOG_DIR, SESSION_ID ? `mcp-${SERVER_NAME}-${SESSION_ID}.log` : `mcp-${SERVER_NAME}-nosession.log`)
|
|
59
60
|
: undefined;
|
|
60
61
|
// Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
|
|
61
62
|
// grow the buffer without bound.
|
|
@@ -66,7 +67,7 @@ let exitEmitted = false;
|
|
|
66
67
|
let exitSignal;
|
|
67
68
|
// The real stderr writer, captured before the tee replaces it. Lifecycle lines
|
|
68
69
|
// and stderr passthrough both go through this so they are never re-teed into
|
|
69
|
-
// the
|
|
70
|
+
// the raw sink nor recursed back into the patched writer.
|
|
70
71
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
71
72
|
// LOG_DIR is created once, lazily, on the first successful append — not per
|
|
72
73
|
// chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
|
|
@@ -115,7 +116,7 @@ function postToServerLog(suffix, level) {
|
|
|
115
116
|
// log-ingest route rejects newlines).
|
|
116
117
|
function emitLifecycle(suffix, level) {
|
|
117
118
|
const line = `[mcp-helper] ${suffix}\n`;
|
|
118
|
-
appendSafe(
|
|
119
|
+
appendSafe(rawSinkPath, line);
|
|
119
120
|
try {
|
|
120
121
|
rawStderrWrite(line);
|
|
121
122
|
}
|
|
@@ -127,12 +128,11 @@ if (!ENTRY) {
|
|
|
127
128
|
process.exit(2);
|
|
128
129
|
}
|
|
129
130
|
// Replace process.stderr.write with a tee: mirror every server stderr byte to
|
|
130
|
-
// the
|
|
131
|
+
// the raw sink, keep a rolling tail for op=exit, emit
|
|
131
132
|
// op=boot on the first bytes, then pass the write through to the real stderr.
|
|
132
133
|
const teeWrite = ((...args) => {
|
|
133
134
|
const chunk = args[0];
|
|
134
|
-
appendSafe(
|
|
135
|
-
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
135
|
+
appendSafe(rawSinkPath, chunk); // raw sink (Task 1721)
|
|
136
136
|
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
137
137
|
stderrTail = (stderrTail + text).slice(-TAIL_CAP);
|
|
138
138
|
if (!bootEmitted) {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Claude Code CLI → shim (this file) = node running <real-entry> in-process
|
|
15
15
|
* stdin/stdout : untouched (JSON-RPC channel)
|
|
16
|
-
* stderr : process.stderr.write teed →
|
|
16
|
+
* stderr : process.stderr.write teed → raw sink + passthrough
|
|
17
17
|
*
|
|
18
|
-
* Destinations
|
|
19
|
-
* - `${LOG_DIR}/mcp-<name
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Destinations:
|
|
19
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — raw stderr + lifecycle lines.
|
|
20
|
+
* The sole raw sink. Task 1721 retired the per-date twin: its date was
|
|
21
|
+
* pinned at spawn, no reader ever parsed it, and mcp-stderr-tee wrote the
|
|
22
|
+
* same file, so every line landed twice.
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-nosession.log` — the same, for a spawn with
|
|
24
|
+
* no SESSION_ID (enumeration). One fixed file per server.
|
|
24
25
|
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
25
26
|
* the [mcp-helper] lifecycle lines.
|
|
26
27
|
*
|
|
@@ -49,13 +50,13 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
49
50
|
const ENTRY = process.argv[2];
|
|
50
51
|
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
52
|
const spawnStamp = Date.now();
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}
|
|
53
|
+
// The one raw sink (Task 1721). A sessionless spawn (rc-daemon stamps
|
|
54
|
+
// SESSION_ID='' — rc-daemon.ts:513 — while still stamping LOG_DIR) shares one
|
|
55
|
+
// fixed file per server rather than a date-named one: the date was never read
|
|
56
|
+
// by any reader and named a process lifetime, not a day. Interleaved spawns in
|
|
57
|
+
// the nosession file are separated by the [mcp-helper] op=spawn pid= lines.
|
|
58
|
+
const rawSinkPath = LOG_DIR
|
|
59
|
+
? (0, node_path_1.resolve)(LOG_DIR, SESSION_ID ? `mcp-${SERVER_NAME}-${SESSION_ID}.log` : `mcp-${SERVER_NAME}-nosession.log`)
|
|
59
60
|
: undefined;
|
|
60
61
|
// Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
|
|
61
62
|
// grow the buffer without bound.
|
|
@@ -66,7 +67,7 @@ let exitEmitted = false;
|
|
|
66
67
|
let exitSignal;
|
|
67
68
|
// The real stderr writer, captured before the tee replaces it. Lifecycle lines
|
|
68
69
|
// and stderr passthrough both go through this so they are never re-teed into
|
|
69
|
-
// the
|
|
70
|
+
// the raw sink nor recursed back into the patched writer.
|
|
70
71
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
71
72
|
// LOG_DIR is created once, lazily, on the first successful append — not per
|
|
72
73
|
// chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
|
|
@@ -115,7 +116,7 @@ function postToServerLog(suffix, level) {
|
|
|
115
116
|
// log-ingest route rejects newlines).
|
|
116
117
|
function emitLifecycle(suffix, level) {
|
|
117
118
|
const line = `[mcp-helper] ${suffix}\n`;
|
|
118
|
-
appendSafe(
|
|
119
|
+
appendSafe(rawSinkPath, line);
|
|
119
120
|
try {
|
|
120
121
|
rawStderrWrite(line);
|
|
121
122
|
}
|
|
@@ -127,12 +128,11 @@ if (!ENTRY) {
|
|
|
127
128
|
process.exit(2);
|
|
128
129
|
}
|
|
129
130
|
// Replace process.stderr.write with a tee: mirror every server stderr byte to
|
|
130
|
-
// the
|
|
131
|
+
// the raw sink, keep a rolling tail for op=exit, emit
|
|
131
132
|
// op=boot on the first bytes, then pass the write through to the real stderr.
|
|
132
133
|
const teeWrite = ((...args) => {
|
|
133
134
|
const chunk = args[0];
|
|
134
|
-
appendSafe(
|
|
135
|
-
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
135
|
+
appendSafe(rawSinkPath, chunk); // raw sink (Task 1721)
|
|
136
136
|
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
137
137
|
stderrTail = (stderrTail + text).slice(-TAIL_CAP);
|
|
138
138
|
if (!bootEmitted) {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Claude Code CLI → shim (this file) = node running <real-entry> in-process
|
|
15
15
|
* stdin/stdout : untouched (JSON-RPC channel)
|
|
16
|
-
* stderr : process.stderr.write teed →
|
|
16
|
+
* stderr : process.stderr.write teed → raw sink + passthrough
|
|
17
17
|
*
|
|
18
|
-
* Destinations
|
|
19
|
-
* - `${LOG_DIR}/mcp-<name
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Destinations:
|
|
19
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — raw stderr + lifecycle lines.
|
|
20
|
+
* The sole raw sink. Task 1721 retired the per-date twin: its date was
|
|
21
|
+
* pinned at spawn, no reader ever parsed it, and mcp-stderr-tee wrote the
|
|
22
|
+
* same file, so every line landed twice.
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-nosession.log` — the same, for a spawn with
|
|
24
|
+
* no SESSION_ID (enumeration). One fixed file per server.
|
|
24
25
|
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
25
26
|
* the [mcp-helper] lifecycle lines.
|
|
26
27
|
*
|
|
@@ -49,13 +50,13 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
49
50
|
const ENTRY = process.argv[2];
|
|
50
51
|
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
52
|
const spawnStamp = Date.now();
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}
|
|
53
|
+
// The one raw sink (Task 1721). A sessionless spawn (rc-daemon stamps
|
|
54
|
+
// SESSION_ID='' — rc-daemon.ts:513 — while still stamping LOG_DIR) shares one
|
|
55
|
+
// fixed file per server rather than a date-named one: the date was never read
|
|
56
|
+
// by any reader and named a process lifetime, not a day. Interleaved spawns in
|
|
57
|
+
// the nosession file are separated by the [mcp-helper] op=spawn pid= lines.
|
|
58
|
+
const rawSinkPath = LOG_DIR
|
|
59
|
+
? (0, node_path_1.resolve)(LOG_DIR, SESSION_ID ? `mcp-${SERVER_NAME}-${SESSION_ID}.log` : `mcp-${SERVER_NAME}-nosession.log`)
|
|
59
60
|
: undefined;
|
|
60
61
|
// Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
|
|
61
62
|
// grow the buffer without bound.
|
|
@@ -66,7 +67,7 @@ let exitEmitted = false;
|
|
|
66
67
|
let exitSignal;
|
|
67
68
|
// The real stderr writer, captured before the tee replaces it. Lifecycle lines
|
|
68
69
|
// and stderr passthrough both go through this so they are never re-teed into
|
|
69
|
-
// the
|
|
70
|
+
// the raw sink nor recursed back into the patched writer.
|
|
70
71
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
71
72
|
// LOG_DIR is created once, lazily, on the first successful append — not per
|
|
72
73
|
// chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
|
|
@@ -115,7 +116,7 @@ function postToServerLog(suffix, level) {
|
|
|
115
116
|
// log-ingest route rejects newlines).
|
|
116
117
|
function emitLifecycle(suffix, level) {
|
|
117
118
|
const line = `[mcp-helper] ${suffix}\n`;
|
|
118
|
-
appendSafe(
|
|
119
|
+
appendSafe(rawSinkPath, line);
|
|
119
120
|
try {
|
|
120
121
|
rawStderrWrite(line);
|
|
121
122
|
}
|
|
@@ -127,12 +128,11 @@ if (!ENTRY) {
|
|
|
127
128
|
process.exit(2);
|
|
128
129
|
}
|
|
129
130
|
// Replace process.stderr.write with a tee: mirror every server stderr byte to
|
|
130
|
-
// the
|
|
131
|
+
// the raw sink, keep a rolling tail for op=exit, emit
|
|
131
132
|
// op=boot on the first bytes, then pass the write through to the real stderr.
|
|
132
133
|
const teeWrite = ((...args) => {
|
|
133
134
|
const chunk = args[0];
|
|
134
|
-
appendSafe(
|
|
135
|
-
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
135
|
+
appendSafe(rawSinkPath, chunk); // raw sink (Task 1721)
|
|
136
136
|
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
137
137
|
stderrTail = (stderrTail + text).slice(-TAIL_CAP);
|
|
138
138
|
if (!bootEmitted) {
|