@rubytech/create-realagent 1.0.853 → 1.0.855

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 (42) hide show
  1. package/dist/index.js +9 -3
  2. package/package.json +1 -1
  3. package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
  4. package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/persistent-components/dist/index.js +32 -0
  6. package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
  7. package/payload/platform/lib/persistent-components/src/index.ts +28 -0
  8. package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
  9. package/payload/platform/package.json +2 -2
  10. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  11. package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
  12. package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +204 -20
  13. package/payload/platform/plugins/admin/mcp/dist/index.js +40 -1
  14. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +2 -0
  16. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +39 -10
  17. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +112 -20
  18. package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
  19. package/payload/platform/plugins/docs/references/deployment.md +2 -0
  20. package/payload/platform/plugins/docs/references/getting-started.md +2 -0
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  23. package/payload/platform/scripts/admin-persist-audit.ts +191 -0
  24. package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
  25. package/payload/platform/scripts/installer-device-verify.sh +17 -4
  26. package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
  27. package/payload/server/chunk-CFNSKDGA.js +667 -0
  28. package/payload/server/chunk-DC6DWYZJ.js +1603 -0
  29. package/payload/server/chunk-LTB5SSQW.js +10889 -0
  30. package/payload/server/chunk-MN2LGNUB.js +2143 -0
  31. package/payload/server/client-pool-AMT2W3II.js +34 -0
  32. package/payload/server/cloudflare-task-tracker-LJ4SMK2D.js +20 -0
  33. package/payload/server/maxy-edge.js +3 -3
  34. package/payload/server/public/assets/admin-Cpk5cT4I.js +352 -0
  35. package/payload/server/public/assets/public-DApUXgoq.js +5 -0
  36. package/payload/server/public/assets/useVoiceRecorder-CI8GpxfU.js +36 -0
  37. package/payload/server/public/index.html +2 -2
  38. package/payload/server/public/public.html +2 -2
  39. package/payload/server/server.js +543 -354
  40. package/payload/server/public/assets/admin-Dyl8uNxX.js +0 -352
  41. package/payload/server/public/assets/public-B_PNZUph.js +0 -5
  42. package/payload/server/public/assets/useVoiceRecorder-fD0IWzJj.js +0 -36
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env -S node --loader tsx
2
+ /**
3
+ * Task 942 — backfill :KnowledgeDocument projections for legacy
4
+ * :Component rows whose render-component was emitted before this
5
+ * task landed.
6
+ *
7
+ * Walks every `:Component {name ∈ PERSISTENT_COMPONENTS}` row that
8
+ * lacks a sibling `:KnowledgeDocument` (matched by accountId +
9
+ * deterministic attachmentId derived from the component's id) and
10
+ * for each one materialises the file on disk + MERGEs the projection
11
+ * row in a single Cypher tx. Idempotent — re-running against the
12
+ * same rows is a no-op (MERGE collapses, file write rewrites the
13
+ * same bytes).
14
+ *
15
+ * Usage:
16
+ * tsx platform/scripts/component-knowledgedoc-backfill.ts \
17
+ * [--account-id=<uuid>] # optional filter, default = all accounts
18
+ * [--dry-run] # print what would happen, do not write
19
+ *
20
+ * Exit codes:
21
+ * 0 = no rows needed backfill, OR all rows succeeded (including --dry-run)
22
+ * 1 = at least one row failed (disk write threw, Cypher tx threw)
23
+ * 2 = invocation / Neo4j connection error
24
+ *
25
+ * Per-row log line:
26
+ * [component-kd-backfill] convId=<…> componentId=<…> outcome=created|skipped|failed reason=<…>
27
+ *
28
+ * The skip cases:
29
+ * - component data does not contain `data.content` or `data.html`,
30
+ * - both fields empty,
31
+ * - the projection row already exists (idempotent re-run).
32
+ */
33
+
34
+ import process from "node:process";
35
+
36
+ import { isPersistentComponent, PERSISTENT_COMPONENTS } from "../lib/persistent-components/src/index";
37
+ import { deriveComponentAttachmentId, deriveComponentTitle, pickComponentBytes } from "../ui/app/lib/claude-agent/component-attachment";
38
+ import { getSession } from "../ui/app/lib/neo4j-store";
39
+ import { storeComponentArtefact } from "../ui/app/lib/attachments";
40
+
41
+ interface Args {
42
+ accountIdFilter?: string;
43
+ dryRun: boolean;
44
+ }
45
+
46
+ function parseArgs(argv: string[]): Args {
47
+ const out: Args = { dryRun: false };
48
+ for (const a of argv) {
49
+ const m = a.match(/^--([a-z-]+)(?:=(.+))?$/);
50
+ if (!m) continue;
51
+ const [, key, val] = m;
52
+ if (key === "account-id") out.accountIdFilter = val;
53
+ else if (key === "dry-run") out.dryRun = true;
54
+ }
55
+ return out;
56
+ }
57
+
58
+ interface ComponentRow {
59
+ componentId: string;
60
+ conversationId: string;
61
+ accountId: string;
62
+ name: string;
63
+ data: string;
64
+ existingAttachmentId: string | null;
65
+ messageId: string;
66
+ }
67
+
68
+ async function main(): Promise<number> {
69
+ const args = parseArgs(process.argv.slice(2));
70
+ const session = getSession();
71
+ let backfilled = 0;
72
+ let skipped = 0;
73
+ let failed = 0;
74
+
75
+ try {
76
+ // Pull every PERSISTENT_COMPONENTS :Component row, optionally
77
+ // filtered by accountId. The query also returns the existing
78
+ // c.attachmentId (null on legacy / pre-942 rows). For legacy rows
79
+ // we derive attachmentId from componentId — it's the only stable
80
+ // identifier on the historical data — write the file, MERGE the
81
+ // projection, AND back-fill c.attachmentId so the audit harness
82
+ // collapses on the same row on its next run.
83
+ const componentNames = Array.from(PERSISTENT_COMPONENTS);
84
+ const filterClause = args.accountIdFilter ? "AND c.accountId = $accountId" : "";
85
+ const result = await session.run(
86
+ `MATCH (m:Message)-[:HAS_COMPONENT]->(c:Component)
87
+ WHERE c.name IN $names ${filterClause}
88
+ RETURN c.componentId AS componentId,
89
+ c.conversationId AS conversationId,
90
+ c.accountId AS accountId,
91
+ c.name AS name,
92
+ c.data AS data,
93
+ c.attachmentId AS existingAttachmentId,
94
+ m.messageId AS messageId
95
+ ORDER BY c.createdAt`,
96
+ args.accountIdFilter
97
+ ? { names: componentNames, accountId: args.accountIdFilter }
98
+ : { names: componentNames },
99
+ );
100
+
101
+ for (const record of result.records) {
102
+ const row: ComponentRow = {
103
+ componentId: record.get("componentId") as string,
104
+ conversationId: record.get("conversationId") as string,
105
+ accountId: record.get("accountId") as string,
106
+ name: record.get("name") as string,
107
+ data: record.get("data") as string,
108
+ existingAttachmentId: (record.get("existingAttachmentId") as string | null) ?? null,
109
+ messageId: record.get("messageId") as string,
110
+ };
111
+
112
+ if (!isPersistentComponent(row.name)) {
113
+ // Defensive — the WHERE clause already filters, but a future
114
+ // schema change might mismatch; log and move on.
115
+ skipped += 1;
116
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=skipped reason=name-not-persistent`);
117
+ continue;
118
+ }
119
+
120
+ let dataObj: Record<string, unknown>;
121
+ try {
122
+ dataObj = JSON.parse(row.data) as Record<string, unknown>;
123
+ } catch {
124
+ skipped += 1;
125
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=skipped reason=data-not-json`);
126
+ continue;
127
+ }
128
+
129
+ const bytesPick = pickComponentBytes(dataObj);
130
+ if (!bytesPick) {
131
+ skipped += 1;
132
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=skipped reason=no-content-or-html`);
133
+ continue;
134
+ }
135
+
136
+ // Prefer the live-writer-stamped attachmentId when present;
137
+ // otherwise derive from componentId (legacy / pre-942 rows).
138
+ // The derived value is then written back onto :Component below
139
+ // so the audit harness sees a single source of truth on the
140
+ // next run.
141
+ const attachmentId = row.existingAttachmentId ?? deriveComponentAttachmentId(row.componentId);
142
+ const derivedFromComponentId = !row.existingAttachmentId;
143
+ const title = deriveComponentTitle(row.name, dataObj);
144
+ const filename = bytesPick.mimeType === "text/html" ? `${title}.html` : `${title}.md`;
145
+
146
+ if (args.dryRun) {
147
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=dry-run attachmentId=${attachmentId.slice(0, 8)} mimeType=${bytesPick.mimeType} bytes=${bytesPick.content.length} source=${derivedFromComponentId ? "derived" : "stamped"}`);
148
+ continue;
149
+ }
150
+
151
+ try {
152
+ await storeComponentArtefact(row.accountId, attachmentId, bytesPick.mimeType, bytesPick.content, filename);
153
+ } catch (err) {
154
+ failed += 1;
155
+ const reason = err instanceof Error ? err.message : String(err);
156
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=failed reason=disk-write:${JSON.stringify(reason.slice(0, 200))}`);
157
+ continue;
158
+ }
159
+
160
+ try {
161
+ // MERGE the projection + the discovery edge from :Message + back-fill
162
+ // c.attachmentId so the audit harness sees a single attachmentId
163
+ // source on its next run. The Cypher returns whether the projection
164
+ // was newly created or already existed, so the per-row log line
165
+ // distinguishes the two outcomes for forensic purposes.
166
+ const mergeResult = await session.run(
167
+ `MATCH (m:Message {messageId: $messageId, accountId: $accountId})
168
+ MATCH (m)-[:HAS_COMPONENT]->(c:Component {componentId: $componentId})
169
+ MERGE (k:KnowledgeDocument {accountId: $accountId, attachmentId: $attachmentId})
170
+ ON CREATE SET k.name = $title,
171
+ k.encodingFormat = $mimeType,
172
+ k.createdAt = datetime(),
173
+ k.updatedAt = datetime()
174
+ ON MATCH SET k.updatedAt = datetime()
175
+ MERGE (m)-[:HAS_KNOWLEDGE_DOCUMENT]->(k)
176
+ SET c.attachmentId = $attachmentId
177
+ RETURN CASE WHEN k.createdAt = k.updatedAt THEN 'created' ELSE 'projection-existed' END AS state`,
178
+ {
179
+ accountId: row.accountId,
180
+ attachmentId,
181
+ title,
182
+ mimeType: bytesPick.mimeType,
183
+ messageId: row.messageId,
184
+ componentId: row.componentId,
185
+ },
186
+ );
187
+ const state = mergeResult.records[0]?.get("state") as string | undefined;
188
+ if (state === "created") {
189
+ backfilled += 1;
190
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=created attachmentId=${attachmentId.slice(0, 8)} mimeType=${bytesPick.mimeType} bytes=${bytesPick.content.length} source=${derivedFromComponentId ? "derived" : "stamped"}`);
191
+ } else {
192
+ skipped += 1;
193
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=skipped reason=already-projected`);
194
+ }
195
+ } catch (err) {
196
+ failed += 1;
197
+ const reason = err instanceof Error ? err.message : String(err);
198
+ console.log(`[component-kd-backfill] convId=${row.conversationId.slice(0, 8)} componentId=${row.componentId.slice(0, 8)} outcome=failed reason=cypher:${JSON.stringify(reason.slice(0, 200))}`);
199
+ }
200
+ }
201
+ } finally {
202
+ await session.close();
203
+ }
204
+
205
+ console.log(`[component-kd-backfill] summary backfilled=${backfilled} skipped=${skipped} failed=${failed}`);
206
+ return failed === 0 ? 0 : 1;
207
+ }
208
+
209
+ main()
210
+ .then((code) => process.exit(code))
211
+ .catch((err) => {
212
+ console.error(`[component-kd-backfill] crashed: ${err instanceof Error ? err.stack : String(err)}`);
213
+ process.exit(2);
214
+ });
@@ -188,10 +188,18 @@ for i in $(seq 0 $((DEVICE_COUNT - 1))); do
188
188
  continue
189
189
  fi
190
190
 
191
- # Step 2 — confirm CDP banner in the latest install log on the device.
192
- # logs are at $HOME/<configDir>/logs/install-*.log; pick the newest by
193
- # mtime (`ls -t`) and grep for the canonical success banner emitted by
194
- # the installer at packages/create-maxy/src/index.ts.
191
+ # Step 2 — confirm a terminal-success banner in the latest install log.
192
+ # Pick the newest install-*.log by mtime (`ls -t`) and grep for either of
193
+ # the installer's two terminal-success markers emitted by
194
+ # packages/create-maxy/src/index.ts:
195
+ # • DISPLAY_MODE=virtual (Pi, headless VNC) →
196
+ # "Browser automation ready (CDP connected)" (index.ts:3012)
197
+ # • DISPLAY_MODE=native (laptop, on-demand Chromium) →
198
+ # "[cdp-check] skipped reason=native-display" (index.ts:3004)
199
+ # Both terminate the install successfully; either is a pass. Hard-coding
200
+ # only the virtual-mode banner would mark every laptop install FAIL even
201
+ # though the laptop install correctly skips CDP probing because there is
202
+ # no persistent Chromium running for CDP to talk to.
195
203
  REMOTE_CHECK=$(cat <<'REMOTE'
196
204
  set -euo pipefail
197
205
  LOG_DIR="$HOME/__CONFIGDIR__/logs"
@@ -202,6 +210,11 @@ if [[ -z "${LATEST:-}" ]]; then
202
210
  fi
203
211
  echo "log=$LATEST"
204
212
  if grep -F -q "Browser automation ready (CDP connected)" "$LATEST"; then
213
+ echo "marker=cdp-connected"
214
+ exit 0
215
+ fi
216
+ if grep -F -q "[cdp-check] skipped reason=native-display" "$LATEST"; then
217
+ echo "marker=native-display-skip"
205
218
  exit 0
206
219
  fi
207
220
  echo "banner-not-found"
@@ -64,9 +64,9 @@ Generate PDFs from rendered HTML pages using the browser tools.
64
64
  - **Document structure:** Cover (full-bleed, print image fallback, `page-break-after: always`) > Optional TOC > Content (flowing) > Back page (mandatory for multi-page, `page-break-before: always`).
65
65
  - **Single-pager:** Fixed `width: 210mm`, `min-height: 297mm`, `margin: 0 auto` on screen. In print add `height: 297mm`, `page-break-after: always`.
66
66
 
67
- **Print image capture:** For cover and back page: start a local HTTP server, set viewport to element size, hide UI chrome, take screenshot of element, save PNG. These images replace glassmorphism effects in print mode.
67
+ **Print image capture:** For cover and back page: navigate to the rendered HTML, set viewport to element size, hide UI chrome, take screenshot of element, save PNG. These images replace glassmorphism effects in print mode.
68
68
 
69
- **Local files:** `file://` URLs are blocked by Playwright. Start a local HTTP server and navigate to `http://localhost:8080/filename.html`.
69
+ **Local files:** `file://` URLs are rewritten transparently by the `playwright-file-guard` PreToolUse hook pass them directly to `browser_navigate` and the hook will spawn a loopback `python3 -m http.server` on a free port and rewrite the URL before Playwright sees it. No agent-side server management needed.
70
70
 
71
71
  ## File delivery
72
72