@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.
- package/dist/index.js +9 -3
- package/package.json +1 -1
- package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
- package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/persistent-components/dist/index.js +32 -0
- package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
- package/payload/platform/lib/persistent-components/src/index.ts +28 -0
- package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
- package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +204 -20
- package/payload/platform/plugins/admin/mcp/dist/index.js +40 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +2 -0
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +39 -10
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +112 -20
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
- package/payload/platform/plugins/docs/references/deployment.md +2 -0
- package/payload/platform/plugins/docs/references/getting-started.md +2 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
- package/payload/platform/scripts/admin-persist-audit.ts +191 -0
- package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
- package/payload/platform/scripts/installer-device-verify.sh +17 -4
- package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
- package/payload/server/chunk-CFNSKDGA.js +667 -0
- package/payload/server/chunk-DC6DWYZJ.js +1603 -0
- package/payload/server/chunk-LTB5SSQW.js +10889 -0
- package/payload/server/chunk-MN2LGNUB.js +2143 -0
- package/payload/server/client-pool-AMT2W3II.js +34 -0
- package/payload/server/cloudflare-task-tracker-LJ4SMK2D.js +20 -0
- package/payload/server/maxy-edge.js +3 -3
- package/payload/server/public/assets/admin-Cpk5cT4I.js +352 -0
- package/payload/server/public/assets/public-DApUXgoq.js +5 -0
- package/payload/server/public/assets/useVoiceRecorder-CI8GpxfU.js +36 -0
- package/payload/server/public/index.html +2 -2
- package/payload/server/public/public.html +2 -2
- package/payload/server/server.js +543 -354
- package/payload/server/public/assets/admin-Dyl8uNxX.js +0 -352
- package/payload/server/public/assets/public-B_PNZUph.js +0 -5
- 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
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
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:
|
|
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
|
|
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
|
|