@rubytech/create-realagent 1.0.819 → 1.0.821
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +8 -9
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +70 -20
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/platform/plugins/docs/references/graph.md +20 -0
- package/payload/platform/plugins/email/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +8 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +20 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +40 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/server/chunk-5OG7TUQL.js +315 -0
- package/payload/server/chunk-CZGOB575.js +593 -0
- package/payload/server/chunk-NUXYHO6N.js +10079 -0
- package/payload/server/chunk-SALVIGXH.js +1116 -0
- package/payload/server/chunk-ZT6LKDTP.js +2238 -0
- package/payload/server/client-pool-IQU6H43X.js +32 -0
- package/payload/server/cloudflare-task-tracker-Q4X5BYR7.js +17 -0
- package/payload/server/maxy-edge.js +4 -3
- package/payload/server/neo4j-migrations-CYIKMSEO.js +366 -0
- package/payload/server/public/assets/admin-okt0ygrZ.js +352 -0
- package/payload/server/public/assets/{graph-DeH6ulGh.js → graph-LLMJa4Ch.js} +1 -1
- package/payload/server/public/assets/{page-WIAWD2Oi.js → page-DoaF3DB0.js} +1 -1
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +269 -488
- package/payload/server/public/assets/admin-CdVYoqKD.js +0 -352
package/payload/server/server.js
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
vncLog,
|
|
52
52
|
waitForExit,
|
|
53
53
|
writeChromiumWrapper
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-NUXYHO6N.js";
|
|
55
55
|
import {
|
|
56
56
|
agentLogStream,
|
|
57
57
|
clearSessionHistory,
|
|
@@ -79,12 +79,27 @@ import {
|
|
|
79
79
|
sigtermFlushStreamLogs,
|
|
80
80
|
unregisterSession,
|
|
81
81
|
validateSession
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-SALVIGXH.js";
|
|
83
83
|
import {
|
|
84
84
|
ACCOUNTS_DIR,
|
|
85
|
+
PLATFORM_ROOT,
|
|
86
|
+
getDefaultAccountId,
|
|
87
|
+
resolveAccount,
|
|
88
|
+
resolveAgentConfig,
|
|
89
|
+
resolveDefaultAgentSlug,
|
|
90
|
+
resolveUserAccounts,
|
|
91
|
+
validateAgentSlug
|
|
92
|
+
} from "./chunk-5OG7TUQL.js";
|
|
93
|
+
import {
|
|
94
|
+
CLOUDFLARE_TASK_DIAGNOSTICS,
|
|
95
|
+
appendCloudflareSteps,
|
|
96
|
+
completeCloudflareTask,
|
|
97
|
+
openCloudflareTask,
|
|
98
|
+
readTunnelState
|
|
99
|
+
} from "./chunk-CZGOB575.js";
|
|
100
|
+
import {
|
|
85
101
|
GREETING_DIRECTIVE,
|
|
86
102
|
HAIKU_MODEL,
|
|
87
|
-
PLATFORM_ROOT,
|
|
88
103
|
__commonJS,
|
|
89
104
|
__toESM,
|
|
90
105
|
backfillNullUserIdConversations,
|
|
@@ -99,7 +114,6 @@ import {
|
|
|
99
114
|
findRecentConversation,
|
|
100
115
|
generateSessionLabel,
|
|
101
116
|
getAgentSessionIdForConversation,
|
|
102
|
-
getDefaultAccountId,
|
|
103
117
|
getGroupParticipants,
|
|
104
118
|
getMessagesSince,
|
|
105
119
|
getRecentMessages,
|
|
@@ -110,236 +124,14 @@ import {
|
|
|
110
124
|
loadOnboardingStep,
|
|
111
125
|
projectAgent,
|
|
112
126
|
renameConversation,
|
|
113
|
-
resolveAccount,
|
|
114
|
-
resolveAgentConfig,
|
|
115
|
-
resolveDefaultAgentSlug,
|
|
116
|
-
resolveUserAccounts,
|
|
117
127
|
runBootMigrations,
|
|
118
|
-
validateAgentSlug,
|
|
119
128
|
verifyAndGetConversationUpdatedAt,
|
|
120
129
|
verifyConversationOwnership,
|
|
121
130
|
writeAdminUserAndPerson
|
|
122
|
-
} from "./chunk-
|
|
123
|
-
|
|
124
|
-
// ../lib/graph-write/dist/audit.js
|
|
125
|
-
var require_audit = __commonJS({
|
|
126
|
-
"../lib/graph-write/dist/audit.js"(exports) {
|
|
127
|
-
"use strict";
|
|
128
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
129
|
-
exports.auditCypherWrite = auditCypherWrite;
|
|
130
|
-
exports.formatAuditLine = formatAuditLine;
|
|
131
|
-
var EDGE_PATTERN = /\[[^\]]*?:([A-Z_][A-Za-z0-9_]*(?:\|[A-Z_][A-Za-z0-9_]*)*)[^\]]*?\]/g;
|
|
132
|
-
var CREATE_OR_MERGE_NODE = /\b(?:CREATE|MERGE)\s*\(\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*[A-Z]/g;
|
|
133
|
-
var PROVENANCE_TOKEN = /\bcreatedBy(?:Agent|Tool|Session|Source)\b/g;
|
|
134
|
-
function stripStringLiterals(cypher) {
|
|
135
|
-
return cypher.replace(/'[^']*'|"[^"]*"/g, '""');
|
|
136
|
-
}
|
|
137
|
-
function extractEdgeTypes(cleaned) {
|
|
138
|
-
const out = /* @__PURE__ */ new Set();
|
|
139
|
-
for (const m of cleaned.matchAll(EDGE_PATTERN)) {
|
|
140
|
-
for (const t of m[1].split("|")) {
|
|
141
|
-
const clean = t.trim();
|
|
142
|
-
if (clean)
|
|
143
|
-
out.add(clean);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return out;
|
|
147
|
-
}
|
|
148
|
-
function countCreateOrMergeNodes(cleaned) {
|
|
149
|
-
const matches = cleaned.match(CREATE_OR_MERGE_NODE);
|
|
150
|
-
return matches ? matches.length : 0;
|
|
151
|
-
}
|
|
152
|
-
function countProvenanceStamps(cleaned) {
|
|
153
|
-
const matches = cleaned.match(PROVENANCE_TOKEN);
|
|
154
|
-
return matches ? matches.length : 0;
|
|
155
|
-
}
|
|
156
|
-
function auditCypherWrite(input) {
|
|
157
|
-
const warnings = [];
|
|
158
|
-
const cleaned = stripStringLiterals(input.cypher);
|
|
159
|
-
const referencedTypes = extractEdgeTypes(cleaned);
|
|
160
|
-
for (const t of referencedTypes) {
|
|
161
|
-
if (!input.schema.relationshipTypes.has(t)) {
|
|
162
|
-
warnings.push({ kind: "unknown-type-warning", type: t });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (input.nodesCreated > 0) {
|
|
166
|
-
const createOrMergeNodes = countCreateOrMergeNodes(cleaned);
|
|
167
|
-
if (createOrMergeNodes > 0) {
|
|
168
|
-
const stamps = countProvenanceStamps(cleaned);
|
|
169
|
-
if (stamps < createOrMergeNodes) {
|
|
170
|
-
warnings.push({
|
|
171
|
-
kind: "missing-provenance-warning",
|
|
172
|
-
created: createOrMergeNodes,
|
|
173
|
-
stamped: stamps
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (input.orphanIds.length > 0) {
|
|
179
|
-
warnings.push({ kind: "orphan-warning", orphanIds: input.orphanIds });
|
|
180
|
-
}
|
|
181
|
-
return warnings;
|
|
182
|
-
}
|
|
183
|
-
function formatAuditLine(line) {
|
|
184
|
-
const prefixField = `query="${line.cypherPrefix.replace(/"/g, "'")}"`;
|
|
185
|
-
switch (line.kind) {
|
|
186
|
-
case "accepted":
|
|
187
|
-
return `[graph-cypher-write] accepted ${prefixField} nodesCreated=${line.nodesCreated} relsCreated=${line.relsCreated} agentName=${line.agentName} sessionId=${line.sessionId}`;
|
|
188
|
-
case "orphan-warning":
|
|
189
|
-
return `[graph-cypher-write] orphan-warning ${prefixField} orphanIds=${line.orphanIds.join(",")}`;
|
|
190
|
-
case "unknown-type-warning":
|
|
191
|
-
return `[graph-cypher-write] unknown-type-warning ${prefixField} type=${line.type}`;
|
|
192
|
-
case "missing-provenance-warning":
|
|
193
|
-
return `[graph-cypher-write] missing-provenance-warning ${prefixField} created=${line.created} stamped=${line.stamped}`;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// ../lib/graph-write/dist/index.js
|
|
200
|
-
var require_dist = __commonJS({
|
|
201
|
-
"../lib/graph-write/dist/index.js"(exports) {
|
|
202
|
-
"use strict";
|
|
203
|
-
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
204
|
-
if (k2 === void 0) k2 = k;
|
|
205
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
206
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
207
|
-
desc = { enumerable: true, get: function() {
|
|
208
|
-
return m[k];
|
|
209
|
-
} };
|
|
210
|
-
}
|
|
211
|
-
Object.defineProperty(o, k2, desc);
|
|
212
|
-
}) : (function(o, m, k, k2) {
|
|
213
|
-
if (k2 === void 0) k2 = k;
|
|
214
|
-
o[k2] = m[k];
|
|
215
|
-
}));
|
|
216
|
-
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
217
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
218
|
-
};
|
|
219
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
220
|
-
exports.ACTION_PROVENANCE_LABELS = void 0;
|
|
221
|
-
exports.stampCreatedBy = stampCreatedBy;
|
|
222
|
-
exports.writeNodeWithEdges = writeNodeWithEdges2;
|
|
223
|
-
__exportStar(require_audit(), exports);
|
|
224
|
-
exports.ACTION_PROVENANCE_LABELS = /* @__PURE__ */ new Set([
|
|
225
|
-
"Person",
|
|
226
|
-
"UserProfile",
|
|
227
|
-
"AdminUser",
|
|
228
|
-
"Organization",
|
|
229
|
-
"LocalBusiness",
|
|
230
|
-
"CloudflareTunnel",
|
|
231
|
-
"CloudflareHostname"
|
|
232
|
-
]);
|
|
233
|
-
function requiresActionProvenance(labels) {
|
|
234
|
-
for (const label of labels) {
|
|
235
|
-
if (exports.ACTION_PROVENANCE_LABELS.has(label))
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
function findProducedFromTaskCandidates(relationships) {
|
|
241
|
-
return relationships.filter((r) => r.type === "PRODUCED" && r.direction === "incoming");
|
|
242
|
-
}
|
|
243
|
-
function stampCreatedBy(props, createdBy) {
|
|
244
|
-
return {
|
|
245
|
-
...props,
|
|
246
|
-
createdByAgent: createdBy.agent ?? "unknown",
|
|
247
|
-
createdBySession: createdBy.session ?? "unknown",
|
|
248
|
-
createdByTool: createdBy.tool ?? null,
|
|
249
|
-
createdBySource: createdBy.source ?? null
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
async function writeNodeWithEdges2(params) {
|
|
253
|
-
const { session, labels, props, relationships, createdBy } = params;
|
|
254
|
-
const agentLabel = createdBy.agent ?? createdBy.source ?? "unknown";
|
|
255
|
-
const labelCsv = labels.join(",");
|
|
256
|
-
const reviewDigestActionTool = typeof props.actionTool === "string" && props.actionTool === "review-digest-compose";
|
|
257
|
-
if (labels.includes("ReviewAlert") || reviewDigestActionTool) {
|
|
258
|
-
const actionToolField = reviewDigestActionTool ? "review-digest-compose" : "n/a";
|
|
259
|
-
process.stderr.write(`[graph-write] reject reason=removed-feature labels=${labelCsv} actionTool=${actionToolField} agent=${agentLabel}
|
|
260
|
-
`);
|
|
261
|
-
throw new Error("Write doctrine violated: review-detector feature removed (Task 884) \u2014 `:ReviewAlert` and `:Event {actionTool:'review-digest-compose'}` writes are not allowed.");
|
|
262
|
-
}
|
|
263
|
-
if (!relationships || relationships.length < 1) {
|
|
264
|
-
process.stderr.write(`[graph-write] reject reason=zero-relationships labels=${labelCsv} agent=${agentLabel}
|
|
265
|
-
`);
|
|
266
|
-
throw new Error("Write doctrine violated: a node must be created with at least one relationship. See .docs/neo4j.md (Write doctrine).");
|
|
267
|
-
}
|
|
268
|
-
const labelStr = labels.map((l) => `\`${l.replace(/`/g, "")}\``).join(":");
|
|
269
|
-
const nodeProps = stampCreatedBy(props, createdBy);
|
|
270
|
-
return await session.executeWrite(async (tx) => {
|
|
271
|
-
const targetIds = relationships.map((r) => r.targetNodeId);
|
|
272
|
-
const check = await tx.run(`UNWIND $ids AS id MATCH (t) WHERE elementId(t) = id RETURN elementId(t) AS id, labels(t) AS labels`, { ids: targetIds });
|
|
273
|
-
const labelsByTarget = /* @__PURE__ */ new Map();
|
|
274
|
-
for (const rec of check.records) {
|
|
275
|
-
labelsByTarget.set(rec.get("id"), rec.get("labels"));
|
|
276
|
-
}
|
|
277
|
-
const found = labelsByTarget.size;
|
|
278
|
-
const uniqueRequested = new Set(targetIds).size;
|
|
279
|
-
if (found !== uniqueRequested) {
|
|
280
|
-
process.stderr.write(`[graph-write] reject reason=unresolved-target labels=${labelCsv} agent=${agentLabel} requested=${uniqueRequested} found=${found}
|
|
281
|
-
`);
|
|
282
|
-
throw new Error(`Write doctrine violated: ${uniqueRequested - found} of ${uniqueRequested} relationship target(s) did not resolve (elementId mismatch). No node created.`);
|
|
283
|
-
}
|
|
284
|
-
let producedByTaskId = null;
|
|
285
|
-
if (requiresActionProvenance(labels) && (createdBy.agent ?? "") !== "system") {
|
|
286
|
-
const candidates = findProducedFromTaskCandidates(relationships);
|
|
287
|
-
const taskCandidates = candidates.filter((r) => {
|
|
288
|
-
const lbls = labelsByTarget.get(r.targetNodeId);
|
|
289
|
-
return Array.isArray(lbls) && lbls.includes("Task");
|
|
290
|
-
});
|
|
291
|
-
if (taskCandidates.length === 0) {
|
|
292
|
-
process.stderr.write(`[graph-write] reject reason=missing-action-provenance labels=${labelCsv} agent=${agentLabel}
|
|
293
|
-
`);
|
|
294
|
-
throw new Error(`Process provenance doctrine violated: write to ${labelCsv} requires an inbound :PRODUCED edge from a :Task (createdBy.agent='${agentLabel}'). See .docs/neo4j.md (Process provenance doctrine).`);
|
|
295
|
-
}
|
|
296
|
-
producedByTaskId = taskCandidates[0].targetNodeId;
|
|
297
|
-
}
|
|
298
|
-
let nodeRes;
|
|
299
|
-
try {
|
|
300
|
-
nodeRes = await tx.run(`CREATE (n:${labelStr} $props) RETURN elementId(n) AS nodeId, labels(n) AS nodeLabels`, { props: nodeProps });
|
|
301
|
-
} catch (err) {
|
|
302
|
-
const code = err?.code ?? "";
|
|
303
|
-
if (code === "Neo.ClientError.Schema.ConstraintValidationFailed" && labels.includes("UserProfile")) {
|
|
304
|
-
const accountIdProp = nodeProps.accountId;
|
|
305
|
-
const userIdProp = nodeProps.userId;
|
|
306
|
-
const acctSlice = typeof accountIdProp === "string" ? accountIdProp.slice(0, 8) : "unknown";
|
|
307
|
-
const userSlice = typeof userIdProp === "string" ? userIdProp.slice(0, 8) : "unknown";
|
|
308
|
-
process.stderr.write(`[graph-write] reject reason=user-profile-uniqueness-violation accountId=${acctSlice} userId=${userSlice} writer=${agentLabel}
|
|
309
|
-
`);
|
|
310
|
-
}
|
|
311
|
-
throw err;
|
|
312
|
-
}
|
|
313
|
-
const nodeId = nodeRes.records[0].get("nodeId");
|
|
314
|
-
const nodeLabels = nodeRes.records[0].get("nodeLabels");
|
|
315
|
-
let edgesCreated = 0;
|
|
316
|
-
for (const rel of relationships) {
|
|
317
|
-
const type = rel.type.replace(/`/g, "");
|
|
318
|
-
const q = rel.direction === "outgoing" ? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)` : `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
|
|
319
|
-
const r = await tx.run(q, { from: nodeId, to: rel.targetNodeId });
|
|
320
|
-
const created = r.summary.counters.updates().relationshipsCreated;
|
|
321
|
-
if (created === 0) {
|
|
322
|
-
process.stderr.write(`[graph-write] reject reason=unresolved-target-on-create labels=${labelCsv} agent=${agentLabel} relType=${rel.type} targetId=${rel.targetNodeId}
|
|
323
|
-
`);
|
|
324
|
-
throw new Error(`Write doctrine violated: relationship CREATE to target ${rel.targetNodeId} produced 0 edges (target likely deleted concurrently after pre-check). Transaction rolled back.`);
|
|
325
|
-
}
|
|
326
|
-
edgesCreated += created;
|
|
327
|
-
}
|
|
328
|
-
if (edgesCreated !== relationships.length) {
|
|
329
|
-
process.stderr.write(`[graph-write] reject reason=edge-count-mismatch labels=${labelCsv} agent=${agentLabel} requested=${relationships.length} created=${edgesCreated}
|
|
330
|
-
`);
|
|
331
|
-
throw new Error(`Write doctrine violated: expected ${relationships.length} edges, created ${edgesCreated}. Transaction rolled back.`);
|
|
332
|
-
}
|
|
333
|
-
process.stderr.write(`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"} producedByTask=${producedByTaskId ?? "none"}
|
|
334
|
-
`);
|
|
335
|
-
return { nodeId, labels: nodeLabels, edgesCreated };
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
});
|
|
131
|
+
} from "./chunk-ZT6LKDTP.js";
|
|
340
132
|
|
|
341
133
|
// ../lib/graph-trash/dist/index.js
|
|
342
|
-
var
|
|
134
|
+
var require_dist = __commonJS({
|
|
343
135
|
"../lib/graph-trash/dist/index.js"(exports) {
|
|
344
136
|
"use strict";
|
|
345
137
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -835,7 +627,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
835
627
|
};
|
|
836
628
|
|
|
837
629
|
// server/index.ts
|
|
838
|
-
import { readFileSync as
|
|
630
|
+
import { readFileSync as readFileSync15, existsSync as existsSync21, watchFile } from "fs";
|
|
839
631
|
import { resolve as resolve21, join as join9, basename as basename5 } from "path";
|
|
840
632
|
import { homedir as homedir2 } from "os";
|
|
841
633
|
|
|
@@ -7492,7 +7284,7 @@ var app11 = new Hono();
|
|
|
7492
7284
|
app11.post("/cancel", requireAdminSession, async (c) => {
|
|
7493
7285
|
const session_key = c.var.sessionKey;
|
|
7494
7286
|
try {
|
|
7495
|
-
const { interruptClient: interruptClient2 } = await import("./client-pool-
|
|
7287
|
+
const { interruptClient: interruptClient2 } = await import("./client-pool-IQU6H43X.js");
|
|
7496
7288
|
await interruptClient2(session_key);
|
|
7497
7289
|
return c.json({ ok: true });
|
|
7498
7290
|
} catch (err) {
|
|
@@ -8797,7 +8589,7 @@ var events_default = app20;
|
|
|
8797
8589
|
// server/routes/admin/cloudflare.ts
|
|
8798
8590
|
import { homedir } from "os";
|
|
8799
8591
|
import { resolve as resolve14 } from "path";
|
|
8800
|
-
import { readFileSync as
|
|
8592
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
8801
8593
|
|
|
8802
8594
|
// app/lib/dns-label.ts
|
|
8803
8595
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -8835,214 +8627,6 @@ function addAliasDomain(hostname2) {
|
|
|
8835
8627
|
writeFileSync6(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
8836
8628
|
}
|
|
8837
8629
|
|
|
8838
|
-
// app/lib/cloudflare-task-tracker.ts
|
|
8839
|
-
import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
|
|
8840
|
-
import { randomUUID as randomUUID7 } from "crypto";
|
|
8841
|
-
var import_dist = __toESM(require_dist(), 1);
|
|
8842
|
-
var CREATED_BY_AGENT = "cloudflare-setup-endpoint";
|
|
8843
|
-
var TASK_KIND = "cloudflare-tunnel-login";
|
|
8844
|
-
async function openCloudflareTask(params) {
|
|
8845
|
-
const { accountId, conversationKey, inputsProvided } = params;
|
|
8846
|
-
const taskId = randomUUID7();
|
|
8847
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8848
|
-
const session = getSession();
|
|
8849
|
-
try {
|
|
8850
|
-
const conv = await session.run(
|
|
8851
|
-
`MATCH (c:Conversation {sessionKey: $conversationKey, accountId: $accountId}) RETURN elementId(c) AS id LIMIT 1`,
|
|
8852
|
-
{ conversationKey, accountId }
|
|
8853
|
-
);
|
|
8854
|
-
if (conv.records.length === 0) {
|
|
8855
|
-
throw new Error(
|
|
8856
|
-
`cloudflare-task-tracker: no Conversation with sessionKey=${conversationKey.slice(-8)} for accountId \u2014 refusing to create an orphan Task`
|
|
8857
|
-
);
|
|
8858
|
-
}
|
|
8859
|
-
const conversationElementId = conv.records[0].get("id");
|
|
8860
|
-
const props = {
|
|
8861
|
-
taskId,
|
|
8862
|
-
accountId,
|
|
8863
|
-
name: "Cloudflare tunnel login + setup",
|
|
8864
|
-
description: `Deterministic cloudflare setup invoked by /api/admin/cloudflare/setup. inputsProvided=[${inputsProvided.join(", ")}]`,
|
|
8865
|
-
status: "running",
|
|
8866
|
-
priority: "normal",
|
|
8867
|
-
kind: TASK_KIND,
|
|
8868
|
-
inputsProvided,
|
|
8869
|
-
startedAt: now,
|
|
8870
|
-
createdAt: now,
|
|
8871
|
-
updatedAt: now
|
|
8872
|
-
};
|
|
8873
|
-
const relationships = [
|
|
8874
|
-
{
|
|
8875
|
-
type: "RAISED_DURING",
|
|
8876
|
-
direction: "outgoing",
|
|
8877
|
-
targetNodeId: conversationElementId
|
|
8878
|
-
}
|
|
8879
|
-
];
|
|
8880
|
-
const result = await (0, import_dist.writeNodeWithEdges)({
|
|
8881
|
-
session,
|
|
8882
|
-
labels: ["Task"],
|
|
8883
|
-
props,
|
|
8884
|
-
relationships,
|
|
8885
|
-
createdBy: {
|
|
8886
|
-
agent: CREATED_BY_AGENT,
|
|
8887
|
-
session: conversationKey,
|
|
8888
|
-
tool: "cloudflare-setup-endpoint"
|
|
8889
|
-
}
|
|
8890
|
-
});
|
|
8891
|
-
process.stderr.write(
|
|
8892
|
-
`[task] action-start kind=${TASK_KIND} taskId=${taskId} raisedDuring=${conversationKey.slice(-8)}
|
|
8893
|
-
`
|
|
8894
|
-
);
|
|
8895
|
-
return { taskId, taskElementId: result.nodeId };
|
|
8896
|
-
} finally {
|
|
8897
|
-
await session.close();
|
|
8898
|
-
}
|
|
8899
|
-
}
|
|
8900
|
-
async function appendCloudflareSteps(taskId, accountId, streamLogPath) {
|
|
8901
|
-
if (!existsSync17(streamLogPath)) return [];
|
|
8902
|
-
let content;
|
|
8903
|
-
try {
|
|
8904
|
-
content = readFileSync13(streamLogPath, "utf-8");
|
|
8905
|
-
} catch {
|
|
8906
|
-
return [];
|
|
8907
|
-
}
|
|
8908
|
-
const steps = [];
|
|
8909
|
-
for (const line of content.split(/\r?\n/)) {
|
|
8910
|
-
const m = line.match(/\bphase_line\s+setup-tunnel\s+step=(\S+)/);
|
|
8911
|
-
if (m) steps.push(m[1]);
|
|
8912
|
-
}
|
|
8913
|
-
if (steps.length === 0) return [];
|
|
8914
|
-
const session = getSession();
|
|
8915
|
-
try {
|
|
8916
|
-
await session.run(
|
|
8917
|
-
`MATCH (t:Task {taskId: $taskId, accountId: $accountId})
|
|
8918
|
-
SET t.steps = coalesce(t.steps, []) + $steps,
|
|
8919
|
-
t.updatedAt = $updatedAt`,
|
|
8920
|
-
{ taskId, accountId, steps, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
8921
|
-
);
|
|
8922
|
-
for (const step of steps) {
|
|
8923
|
-
process.stderr.write(
|
|
8924
|
-
`[task] action-step kind=${TASK_KIND} taskId=${taskId} step=${step}
|
|
8925
|
-
`
|
|
8926
|
-
);
|
|
8927
|
-
}
|
|
8928
|
-
return steps;
|
|
8929
|
-
} finally {
|
|
8930
|
-
await session.close();
|
|
8931
|
-
}
|
|
8932
|
-
}
|
|
8933
|
-
async function completeCloudflareTask(params) {
|
|
8934
|
-
const { taskId, taskElementId, accountId, conversationKey, tunnelId, tunnelName, hostnames, status, errorMessage } = params;
|
|
8935
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8936
|
-
if (status === "failed" && (!errorMessage || errorMessage.trim().length === 0)) {
|
|
8937
|
-
throw new Error(
|
|
8938
|
-
"cloudflare-task-tracker: errorMessage is required when status='failed' (Task 885 process-provenance contract)."
|
|
8939
|
-
);
|
|
8940
|
-
}
|
|
8941
|
-
const session = getSession();
|
|
8942
|
-
try {
|
|
8943
|
-
if (status === "completed" && tunnelId && tunnelName) {
|
|
8944
|
-
const conv = await session.run(
|
|
8945
|
-
`MATCH (c:Conversation {sessionKey: $conversationKey, accountId: $accountId}) RETURN elementId(c) AS id LIMIT 1`,
|
|
8946
|
-
{ conversationKey, accountId }
|
|
8947
|
-
);
|
|
8948
|
-
const conversationElementId = conv.records.length > 0 ? conv.records[0].get("id") : null;
|
|
8949
|
-
const tunnelProps = {
|
|
8950
|
-
accountId,
|
|
8951
|
-
tunnelId,
|
|
8952
|
-
tunnelName,
|
|
8953
|
-
createdAt: now,
|
|
8954
|
-
updatedAt: now
|
|
8955
|
-
};
|
|
8956
|
-
const tunnelRels = [
|
|
8957
|
-
{ type: "PRODUCED", direction: "incoming", targetNodeId: taskElementId }
|
|
8958
|
-
];
|
|
8959
|
-
if (conversationElementId) {
|
|
8960
|
-
tunnelRels.push({
|
|
8961
|
-
type: "RAISED_DURING",
|
|
8962
|
-
direction: "outgoing",
|
|
8963
|
-
targetNodeId: conversationElementId
|
|
8964
|
-
});
|
|
8965
|
-
}
|
|
8966
|
-
const tunnelWrite = await (0, import_dist.writeNodeWithEdges)({
|
|
8967
|
-
session,
|
|
8968
|
-
labels: ["CloudflareTunnel"],
|
|
8969
|
-
props: tunnelProps,
|
|
8970
|
-
relationships: tunnelRels,
|
|
8971
|
-
createdBy: {
|
|
8972
|
-
agent: CREATED_BY_AGENT,
|
|
8973
|
-
session: conversationKey,
|
|
8974
|
-
tool: "cloudflare-setup-endpoint"
|
|
8975
|
-
}
|
|
8976
|
-
});
|
|
8977
|
-
for (const h of hostnames ?? []) {
|
|
8978
|
-
const hostRels = [
|
|
8979
|
-
{ type: "PRODUCED", direction: "incoming", targetNodeId: taskElementId },
|
|
8980
|
-
{
|
|
8981
|
-
type: "ROUTES_TO",
|
|
8982
|
-
direction: "outgoing",
|
|
8983
|
-
targetNodeId: tunnelWrite.nodeId
|
|
8984
|
-
}
|
|
8985
|
-
];
|
|
8986
|
-
await (0, import_dist.writeNodeWithEdges)({
|
|
8987
|
-
session,
|
|
8988
|
-
labels: ["CloudflareHostname"],
|
|
8989
|
-
props: {
|
|
8990
|
-
accountId,
|
|
8991
|
-
hostnameValue: h.hostnameValue,
|
|
8992
|
-
tunnelId,
|
|
8993
|
-
isApex: h.isApex,
|
|
8994
|
-
createdAt: now,
|
|
8995
|
-
updatedAt: now
|
|
8996
|
-
},
|
|
8997
|
-
relationships: hostRels,
|
|
8998
|
-
createdBy: {
|
|
8999
|
-
agent: CREATED_BY_AGENT,
|
|
9000
|
-
session: conversationKey,
|
|
9001
|
-
tool: "cloudflare-setup-endpoint"
|
|
9002
|
-
}
|
|
9003
|
-
});
|
|
9004
|
-
}
|
|
9005
|
-
}
|
|
9006
|
-
const setClauses = [
|
|
9007
|
-
"t.status = $status",
|
|
9008
|
-
"t.completedAt = $now",
|
|
9009
|
-
"t.updatedAt = $now"
|
|
9010
|
-
];
|
|
9011
|
-
const queryParams = { taskId, accountId, status, now };
|
|
9012
|
-
if (status === "failed" && errorMessage) {
|
|
9013
|
-
setClauses.push("t.errorMessage = $errorMessage");
|
|
9014
|
-
queryParams.errorMessage = errorMessage;
|
|
9015
|
-
}
|
|
9016
|
-
const updateRes = await session.run(
|
|
9017
|
-
`MATCH (t:Task {taskId: $taskId, accountId: $accountId})
|
|
9018
|
-
SET ${setClauses.join(", ")}
|
|
9019
|
-
RETURN size(coalesce(t.steps, [])) AS stepsCount`,
|
|
9020
|
-
queryParams
|
|
9021
|
-
);
|
|
9022
|
-
const stepsCount = updateRes.records[0]?.get("stepsCount")?.toNumber?.() ?? 0;
|
|
9023
|
-
process.stderr.write(
|
|
9024
|
-
`[task] action-done kind=${TASK_KIND} taskId=${taskId} status=${status} stepsCount=${stepsCount}
|
|
9025
|
-
`
|
|
9026
|
-
);
|
|
9027
|
-
} finally {
|
|
9028
|
-
await session.close();
|
|
9029
|
-
}
|
|
9030
|
-
}
|
|
9031
|
-
function readTunnelState(brandConfigDir) {
|
|
9032
|
-
const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
|
|
9033
|
-
if (!existsSync17(statePath)) return null;
|
|
9034
|
-
try {
|
|
9035
|
-
const parsed = JSON.parse(readFileSync13(statePath, "utf-8"));
|
|
9036
|
-
const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
|
|
9037
|
-
const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
|
|
9038
|
-
const domain = typeof parsed.domain === "string" ? parsed.domain : null;
|
|
9039
|
-
if (!tunnelId || !tunnelName || !domain) return null;
|
|
9040
|
-
return { tunnelId, tunnelName, domain };
|
|
9041
|
-
} catch {
|
|
9042
|
-
return null;
|
|
9043
|
-
}
|
|
9044
|
-
}
|
|
9045
|
-
|
|
9046
8630
|
// server/routes/admin/cloudflare.ts
|
|
9047
8631
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9048
8632
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
@@ -9050,7 +8634,7 @@ function loadBrandInfo() {
|
|
|
9050
8634
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
9051
8635
|
const brandPath = resolve14(platformRoot2, "config", "brand.json");
|
|
9052
8636
|
try {
|
|
9053
|
-
const parsed = JSON.parse(
|
|
8637
|
+
const parsed = JSON.parse(readFileSync13(brandPath, "utf-8"));
|
|
9054
8638
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
9055
8639
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
9056
8640
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -9067,6 +8651,12 @@ function resolvePort() {
|
|
|
9067
8651
|
}
|
|
9068
8652
|
return n;
|
|
9069
8653
|
}
|
|
8654
|
+
function isValidTunnelId(s) {
|
|
8655
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
|
|
8656
|
+
}
|
|
8657
|
+
function isValidTunnelName(s) {
|
|
8658
|
+
return /^[A-Za-z0-9_.-]{1,64}$/.test(s);
|
|
8659
|
+
}
|
|
9070
8660
|
function validateBody(body) {
|
|
9071
8661
|
if (!body || typeof body !== "object") return { field: "request", message: "Invalid request body" };
|
|
9072
8662
|
if (typeof body.adminLabel !== "string" || !isValidLabel(body.adminLabel)) {
|
|
@@ -9091,6 +8681,20 @@ function validateBody(body) {
|
|
|
9091
8681
|
return { field: "request", message: "Apex must be a valid domain or omitted." };
|
|
9092
8682
|
}
|
|
9093
8683
|
}
|
|
8684
|
+
const idSet = typeof body.tunnelId === "string" && body.tunnelId.length > 0;
|
|
8685
|
+
const nameSet = typeof body.tunnelName === "string" && body.tunnelName.length > 0;
|
|
8686
|
+
if (!idSet && !nameSet) {
|
|
8687
|
+
return { field: "tunnel", message: "Pick an existing tunnel from the list, or type a name to create a new one." };
|
|
8688
|
+
}
|
|
8689
|
+
if (idSet && nameSet) {
|
|
8690
|
+
return { field: "tunnel", message: "Pick OR create \u2014 not both. Clear one of the tunnel inputs." };
|
|
8691
|
+
}
|
|
8692
|
+
if (idSet && !isValidTunnelId(body.tunnelId)) {
|
|
8693
|
+
return { field: "tunnel", message: "Selected tunnel UUID is malformed." };
|
|
8694
|
+
}
|
|
8695
|
+
if (nameSet && !isValidTunnelName(body.tunnelName)) {
|
|
8696
|
+
return { field: "tunnel", message: "New-tunnel name must be 1\u201364 characters of letters, digits, dot, dash, underscore." };
|
|
8697
|
+
}
|
|
9094
8698
|
return null;
|
|
9095
8699
|
}
|
|
9096
8700
|
var app21 = new Hono();
|
|
@@ -9203,6 +8807,91 @@ ${result.stderr}` : ""}`;
|
|
|
9203
8807
|
);
|
|
9204
8808
|
return c.json(success, 200);
|
|
9205
8809
|
});
|
|
8810
|
+
var TUNNELS_TIMEOUT_MS = 30 * 1e3;
|
|
8811
|
+
app21.get("/tunnels", requireAdminSession, async (c) => {
|
|
8812
|
+
const started = Date.now();
|
|
8813
|
+
const sessionKey = c.var.sessionKey;
|
|
8814
|
+
let correlationId;
|
|
8815
|
+
let sessionKeyTail;
|
|
8816
|
+
let streamLogPath;
|
|
8817
|
+
function tag() {
|
|
8818
|
+
if (!correlationId) return "";
|
|
8819
|
+
return ` conversationId=${correlationId} session_key=${sessionKeyTail ?? "unknown"}`;
|
|
8820
|
+
}
|
|
8821
|
+
function log(line) {
|
|
8822
|
+
console.log(`[cloudflare-tunnels] ${line}${tag()}`);
|
|
8823
|
+
}
|
|
8824
|
+
function logErr(line) {
|
|
8825
|
+
console.error(`[cloudflare-tunnels] ${line}${tag()}`);
|
|
8826
|
+
}
|
|
8827
|
+
function err(field, message, output) {
|
|
8828
|
+
logErr(`phase=error field=${field} reason="${message.slice(0, 160).replace(/"/g, "'")}"`);
|
|
8829
|
+
if (streamLogPath) {
|
|
8830
|
+
writeRouteMilestone(
|
|
8831
|
+
streamLogPath,
|
|
8832
|
+
"cloudflare-tunnels",
|
|
8833
|
+
`phase=error field=${field} reason="${message.slice(0, 160).replace(/"/g, "'")}"`
|
|
8834
|
+
);
|
|
8835
|
+
}
|
|
8836
|
+
const body = { ok: false, field, message, output, correlationId, streamLogPath };
|
|
8837
|
+
return c.json(body, 200);
|
|
8838
|
+
}
|
|
8839
|
+
const accountId = getAccountIdForSession(sessionKey);
|
|
8840
|
+
correlationId = getConversationIdForSession(sessionKey);
|
|
8841
|
+
sessionKeyTail = sessionKey.slice(-8);
|
|
8842
|
+
if (!accountId) return err("session", "No account bound to session \u2014 refresh chat.");
|
|
8843
|
+
if (!correlationId) return err("session", "No active conversation for session \u2014 refresh chat.");
|
|
8844
|
+
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
8845
|
+
const brand = loadBrandInfo();
|
|
8846
|
+
const certPath = resolve14(homedir(), brand.configDir, "cloudflared", "cert.pem");
|
|
8847
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
8848
|
+
if (!existsSync22(certPath)) {
|
|
8849
|
+
return err("cert", `Cloudflare origin certificate is not on disk yet (${certPath}). Complete the Cloudflare login first by submitting the form once \u2014 the OAuth flow writes cert.pem.`);
|
|
8850
|
+
}
|
|
8851
|
+
const result = await runFormSpawn({
|
|
8852
|
+
scriptPath: "cloudflared",
|
|
8853
|
+
args: ["--origincert", certPath, "tunnel", "list", "--output", "json"],
|
|
8854
|
+
streamLogPath,
|
|
8855
|
+
correlationId,
|
|
8856
|
+
timeoutMs: TUNNELS_TIMEOUT_MS,
|
|
8857
|
+
log,
|
|
8858
|
+
logErr,
|
|
8859
|
+
broadcast: broadcastToConversation
|
|
8860
|
+
});
|
|
8861
|
+
const combined = `${result.stdout}${result.stderr ? `
|
|
8862
|
+
---
|
|
8863
|
+
${result.stderr}` : ""}`;
|
|
8864
|
+
if (result.code !== 0) {
|
|
8865
|
+
const message = result.timedOut ? `cloudflared tunnel list timed out after ${TUNNELS_TIMEOUT_MS / 1e3}s` : `cloudflared tunnel list exited with code ${result.code ?? "null"}${result.signal ? ` (signal ${result.signal})` : ""}`;
|
|
8866
|
+
return err("script", message, combined);
|
|
8867
|
+
}
|
|
8868
|
+
let tunnels;
|
|
8869
|
+
try {
|
|
8870
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
8871
|
+
if (!Array.isArray(parsed)) throw new Error("cloudflared response is not an array");
|
|
8872
|
+
tunnels = parsed.filter((t) => t.deleted_at == null).map((t) => {
|
|
8873
|
+
if (typeof t.id !== "string" || typeof t.name !== "string") {
|
|
8874
|
+
throw new Error("tunnel entry missing id or name");
|
|
8875
|
+
}
|
|
8876
|
+
return { id: t.id, name: t.name };
|
|
8877
|
+
});
|
|
8878
|
+
} catch (e) {
|
|
8879
|
+
return err(
|
|
8880
|
+
"script",
|
|
8881
|
+
`cloudflared returned malformed JSON: ${e instanceof Error ? e.message : String(e)}`,
|
|
8882
|
+
combined
|
|
8883
|
+
);
|
|
8884
|
+
}
|
|
8885
|
+
const total = Date.now() - started;
|
|
8886
|
+
log(`phase=response-sent total_ms=${total} count=${tunnels.length}`);
|
|
8887
|
+
writeRouteMilestone(
|
|
8888
|
+
streamLogPath,
|
|
8889
|
+
"cloudflare-tunnels",
|
|
8890
|
+
`phase=response-sent total_ms=${total} count=${tunnels.length} source=cloudflared`
|
|
8891
|
+
);
|
|
8892
|
+
const success = { ok: true, tunnels, correlationId, streamLogPath };
|
|
8893
|
+
return c.json(success, 200);
|
|
8894
|
+
});
|
|
9206
8895
|
app21.post("/setup", requireAdminSession, async (c) => {
|
|
9207
8896
|
const started = Date.now();
|
|
9208
8897
|
const sessionKey = c.var.sessionKey;
|
|
@@ -9297,7 +8986,15 @@ app21.post("/setup", requireAdminSession, async (c) => {
|
|
|
9297
8986
|
cloudflareTask = await openCloudflareTask({
|
|
9298
8987
|
accountId,
|
|
9299
8988
|
conversationKey: sessionKey,
|
|
9300
|
-
inputsProvided: ["adminLabel", "adminDomain", publicFqdn ? "publicLabel" : null, apex ? "apex" : null, "password"].filter((s) => typeof s === "string")
|
|
8989
|
+
inputsProvided: ["adminLabel", "adminDomain", publicFqdn ? "publicLabel" : null, apex ? "apex" : null, "password"].filter((s) => typeof s === "string"),
|
|
8990
|
+
inputs: {
|
|
8991
|
+
adminLabel: body.adminLabel,
|
|
8992
|
+
adminDomain: body.adminDomain,
|
|
8993
|
+
publicLabel: body.publicLabel,
|
|
8994
|
+
publicDomain: body.publicDomain,
|
|
8995
|
+
apex: body.apex
|
|
8996
|
+
},
|
|
8997
|
+
messageId: typeof body.messageId === "string" && body.messageId.length > 0 ? body.messageId : void 0
|
|
9301
8998
|
});
|
|
9302
8999
|
log(`phase=task-opened taskId=${cloudflareTask.taskId}`);
|
|
9303
9000
|
} catch (e) {
|
|
@@ -9306,7 +9003,13 @@ app21.post("/setup", requireAdminSession, async (c) => {
|
|
|
9306
9003
|
let actionId;
|
|
9307
9004
|
let unit;
|
|
9308
9005
|
try {
|
|
9309
|
-
const launched = await launchAction("cloudflare-setup", {
|
|
9006
|
+
const launched = await launchAction("cloudflare-setup", {
|
|
9007
|
+
positional: args,
|
|
9008
|
+
streamLogPath,
|
|
9009
|
+
accountDir,
|
|
9010
|
+
tunnelId: typeof body.tunnelId === "string" && body.tunnelId.length > 0 ? body.tunnelId : void 0,
|
|
9011
|
+
tunnelName: typeof body.tunnelName === "string" && body.tunnelName.length > 0 ? body.tunnelName : void 0
|
|
9012
|
+
});
|
|
9310
9013
|
actionId = launched.actionId;
|
|
9311
9014
|
unit = launched.unit;
|
|
9312
9015
|
} catch (e) {
|
|
@@ -9324,14 +9027,14 @@ app21.post("/setup", requireAdminSession, async (c) => {
|
|
|
9324
9027
|
if (!status || status.execMainStatus !== 0) {
|
|
9325
9028
|
logErr(`phase=post-exit-skipped reason=${status ? `exit=${status.execMainStatus}` : "unit-gone"} id=${actionId}`);
|
|
9326
9029
|
try {
|
|
9327
|
-
const
|
|
9030
|
+
const diagnostic = status ? `${CLOUDFLARE_TASK_DIAGNOSTICS.scriptExitedNonzero} exit=${status.execMainStatus}` : CLOUDFLARE_TASK_DIAGNOSTICS.endpointDiedPreReconcile;
|
|
9328
9031
|
await completeCloudflareTask({
|
|
9329
9032
|
taskId: cloudflareTask.taskId,
|
|
9330
9033
|
taskElementId: cloudflareTask.taskElementId,
|
|
9331
9034
|
accountId,
|
|
9332
9035
|
conversationKey: sessionKey,
|
|
9333
9036
|
status: "failed",
|
|
9334
|
-
errorMessage:
|
|
9037
|
+
errorMessage: diagnostic
|
|
9335
9038
|
});
|
|
9336
9039
|
} catch (e) {
|
|
9337
9040
|
logErr(`phase=task-close-failed status=failed reason="${e instanceof Error ? e.message : String(e)}"`);
|
|
@@ -10071,7 +9774,7 @@ app22.delete("/", requireAdminSession, async (c) => {
|
|
|
10071
9774
|
var files_default = app22;
|
|
10072
9775
|
|
|
10073
9776
|
// ../lib/graph-search/src/index.ts
|
|
10074
|
-
var
|
|
9777
|
+
var import_dist = __toESM(require_dist());
|
|
10075
9778
|
import { int } from "neo4j-driver";
|
|
10076
9779
|
var VECTOR_WEIGHT = 0.7;
|
|
10077
9780
|
var BM25_WEIGHT = 0.3;
|
|
@@ -10126,7 +9829,7 @@ async function bm25Only(session, params) {
|
|
|
10126
9829
|
${scopeClause}
|
|
10127
9830
|
${agentClause}
|
|
10128
9831
|
${labelClause}
|
|
10129
|
-
AND ${(0,
|
|
9832
|
+
AND ${(0, import_dist.notTrashed)("node")}
|
|
10130
9833
|
${kwClause}
|
|
10131
9834
|
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
10132
9835
|
ORDER BY score DESC
|
|
@@ -10216,7 +9919,7 @@ async function hybrid(session, embed2, params) {
|
|
|
10216
9919
|
WHERE node.accountId = $accountId
|
|
10217
9920
|
${scopeClause}
|
|
10218
9921
|
${agentClause}
|
|
10219
|
-
AND ${(0,
|
|
9922
|
+
AND ${(0, import_dist.notTrashed)("node")}
|
|
10220
9923
|
${keywordClause}
|
|
10221
9924
|
RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
|
|
10222
9925
|
ORDER BY score DESC
|
|
@@ -10277,7 +9980,7 @@ async function hybrid(session, embed2, params) {
|
|
|
10277
9980
|
const propResult = await session.run(
|
|
10278
9981
|
`MATCH (node)
|
|
10279
9982
|
WHERE node.accountId = $accountId
|
|
10280
|
-
AND ${(0,
|
|
9983
|
+
AND ${(0, import_dist.notTrashed)("node")}
|
|
10281
9984
|
AND node.keywords IS NOT NULL
|
|
10282
9985
|
AND ANY(kw IN $kwSubs WHERE ANY(nk IN node.keywords WHERE toLower(nk) = kw))
|
|
10283
9986
|
${propScopeClause}
|
|
@@ -10334,7 +10037,7 @@ async function hybrid(session, embed2, params) {
|
|
|
10334
10037
|
`UNWIND $nodeIds AS nid
|
|
10335
10038
|
MATCH (n)-[r]-(related)
|
|
10336
10039
|
WHERE elementId(n) = nid
|
|
10337
|
-
AND ${(0,
|
|
10040
|
+
AND ${(0, import_dist.notTrashed)("related")}
|
|
10338
10041
|
${expandScopeClause}
|
|
10339
10042
|
${expandAgentClause}
|
|
10340
10043
|
WITH nid, n, r, related
|
|
@@ -10779,6 +10482,12 @@ async function handleDefault(c, accountId) {
|
|
|
10779
10482
|
var NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT = 100;
|
|
10780
10483
|
var NEIGHBOURHOOD_SEARCH_MAX_LIMIT = 2e3;
|
|
10781
10484
|
var NEIGHBOURHOOD_LIMIT = 2e3;
|
|
10485
|
+
var CLUSTER_CONVERSATION_LABELS = /* @__PURE__ */ new Set([
|
|
10486
|
+
"Conversation",
|
|
10487
|
+
"AdminConversation",
|
|
10488
|
+
"PublicConversation"
|
|
10489
|
+
]);
|
|
10490
|
+
var MAX_CLUSTER_MESSAGES = 200;
|
|
10782
10491
|
async function handleNeighbourhood(c, accountId) {
|
|
10783
10492
|
const elementId = c.req.query("elementId");
|
|
10784
10493
|
if (!elementId) {
|
|
@@ -10816,7 +10525,12 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
10816
10525
|
// `neo4j.int` keeps the LIMIT value as a 64-bit Integer on the wire —
|
|
10817
10526
|
// bare JS `number` works on Pi 4 today but errors on driver versions
|
|
10818
10527
|
// that strictly type-check LIMIT clauses against Long.
|
|
10819
|
-
neighbourhoodLimit: neo4j2.int(NEIGHBOURHOOD_LIMIT)
|
|
10528
|
+
neighbourhoodLimit: neo4j2.int(NEIGHBOURHOOD_LIMIT),
|
|
10529
|
+
// Task 886 §C — same Long-int discipline for the cluster-expand cap.
|
|
10530
|
+
// Used by NEIGHBOURHOOD_CYPHER's `siblingMessages[0..$clusterMessagesCap]`
|
|
10531
|
+
// slice. The search-intersect variant ignores it (cluster-expand
|
|
10532
|
+
// does not apply when search is narrowing the canvas).
|
|
10533
|
+
clusterMessagesCap: neo4j2.int(MAX_CLUSTER_MESSAGES)
|
|
10820
10534
|
};
|
|
10821
10535
|
if (allowedIds !== null) cypherParams.allowedIds = allowedIds;
|
|
10822
10536
|
const result = await session.executeRead(async (tx) => {
|
|
@@ -10839,6 +10553,15 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
10839
10553
|
console.error(
|
|
10840
10554
|
`[graph-page] load mode=neighbourhood account=${accountId} agentActions=${includeAgentActions} elementId=${elementId} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
10841
10555
|
);
|
|
10556
|
+
if (allowedIds === null) {
|
|
10557
|
+
const conversationNode = nodes.find((n) => n.labels.some((lbl) => CLUSTER_CONVERSATION_LABELS.has(lbl)));
|
|
10558
|
+
const messageCount = nodes.filter((n) => n.labels.includes("Message")).length;
|
|
10559
|
+
if (conversationNode && messageCount >= 2) {
|
|
10560
|
+
console.error(
|
|
10561
|
+
`[graph] cluster-expand kind=conversation conversationId=${conversationNode.id} messageCount=${messageCount} reason=click-target-is-${nodes[0]?.labels.includes("Message") ? "message" : "conversation"}`
|
|
10562
|
+
);
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10842
10565
|
if (allowedIds !== null) {
|
|
10843
10566
|
console.error(
|
|
10844
10567
|
`[graph-page] neighbourhood-search-intersect q="${q}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
|
|
@@ -10939,15 +10662,50 @@ var NEIGHBOURHOOD_CYPHER = `
|
|
|
10939
10662
|
AND n.accountId = $accountId
|
|
10940
10663
|
AND NOT n:Trashed
|
|
10941
10664
|
AND n.deletedAt IS NULL
|
|
10665
|
+
|
|
10666
|
+
// Resolve the owning Conversation: either n itself if it carries a
|
|
10667
|
+
// Conversation sublabel, or the Conversation reached by one PART_OF
|
|
10668
|
+
// hop (Messages \u2192 Conversation). For non-cluster roots both branches
|
|
10669
|
+
// null out and \`conv\` stays null.
|
|
10670
|
+
OPTIONAL MATCH (n)-[:PART_OF]->(convFromMsg:Conversation)
|
|
10671
|
+
WHERE convFromMsg.accountId = $accountId
|
|
10672
|
+
AND NOT convFromMsg:Trashed
|
|
10673
|
+
AND convFromMsg.deletedAt IS NULL
|
|
10674
|
+
WITH n, CASE
|
|
10675
|
+
WHEN any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation']) THEN n
|
|
10676
|
+
ELSE convFromMsg
|
|
10677
|
+
END AS conv
|
|
10678
|
+
|
|
10679
|
+
// Pull all sibling messages of that conversation (or all messages of
|
|
10680
|
+
// n itself when n is a Conversation). EXCLUDE n from the collection
|
|
10681
|
+
// so the later \`[n] + \u2026\` doesn't double-count.
|
|
10682
|
+
OPTIONAL MATCH (conv)<-[:PART_OF]-(siblingMsg:Message)
|
|
10683
|
+
WHERE siblingMsg.accountId = $accountId
|
|
10684
|
+
AND NOT siblingMsg:Trashed
|
|
10685
|
+
AND siblingMsg.deletedAt IS NULL
|
|
10686
|
+
AND elementId(siblingMsg) <> elementId(n)
|
|
10687
|
+
WITH n, conv, collect(DISTINCT siblingMsg) AS siblingMessages
|
|
10688
|
+
WITH n, conv, siblingMessages[0..$clusterMessagesCap] AS clusterMessages
|
|
10689
|
+
|
|
10942
10690
|
OPTIONAL MATCH (n)-[]-(m)
|
|
10943
10691
|
WHERE m.accountId = $accountId
|
|
10944
10692
|
AND NOT m:Trashed
|
|
10945
10693
|
AND m.deletedAt IS NULL
|
|
10946
10694
|
AND NOT any(lbl IN labels(m) WHERE lbl IN $agentActionLabels)
|
|
10947
|
-
WITH n, m
|
|
10695
|
+
WITH n, conv, clusterMessages, m
|
|
10948
10696
|
LIMIT $neighbourhoodLimit
|
|
10949
|
-
WITH n, collect(DISTINCT m) AS neighbours
|
|
10950
|
-
|
|
10697
|
+
WITH n, conv, clusterMessages, collect(DISTINCT m) AS neighbours
|
|
10698
|
+
|
|
10699
|
+
// Compose the window: root + (conv if non-null and not already n) +
|
|
10700
|
+
// cluster messages + 1-hop neighbours not already in the cluster.
|
|
10701
|
+
WITH [n]
|
|
10702
|
+
+ (CASE WHEN conv IS NOT NULL AND elementId(conv) <> elementId(n) THEN [conv] ELSE [] END)
|
|
10703
|
+
+ clusterMessages
|
|
10704
|
+
+ [x IN neighbours WHERE x IS NOT NULL
|
|
10705
|
+
AND elementId(x) <> elementId(n)
|
|
10706
|
+
AND (conv IS NULL OR elementId(x) <> elementId(conv))
|
|
10707
|
+
AND none(c IN clusterMessages WHERE elementId(c) = elementId(x))]
|
|
10708
|
+
AS windowNodes
|
|
10951
10709
|
WITH windowNodes, [x IN windowNodes | elementId(x)] AS windowIds
|
|
10952
10710
|
UNWIND windowNodes AS w
|
|
10953
10711
|
OPTIONAL MATCH (w)-[r]-(o)
|
|
@@ -11510,7 +11268,7 @@ var adherence_default = app30;
|
|
|
11510
11268
|
import neo4j3 from "neo4j-driver";
|
|
11511
11269
|
import { readFile as readFile5, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
11512
11270
|
import { resolve as resolve17, relative as relative2, isAbsolute } from "path";
|
|
11513
|
-
import { existsSync as
|
|
11271
|
+
import { existsSync as existsSync17 } from "fs";
|
|
11514
11272
|
var LIMIT = 50;
|
|
11515
11273
|
var TEXT_MIME_PREFIXES = ["text/", "application/json", "application/markdown"];
|
|
11516
11274
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -11658,7 +11416,7 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
11658
11416
|
async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
11659
11417
|
const names = /* @__PURE__ */ new Set();
|
|
11660
11418
|
for (const dir of [overrideDir, bundledDir]) {
|
|
11661
|
-
if (!
|
|
11419
|
+
if (!existsSync17(dir)) continue;
|
|
11662
11420
|
try {
|
|
11663
11421
|
const entries = await readdir3(dir);
|
|
11664
11422
|
for (const entry of entries) {
|
|
@@ -11673,7 +11431,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
11673
11431
|
}
|
|
11674
11432
|
async function readAgentTemplateRow(inp) {
|
|
11675
11433
|
let chosenPath = null;
|
|
11676
|
-
if (
|
|
11434
|
+
if (existsSync17(inp.overridePath)) {
|
|
11677
11435
|
try {
|
|
11678
11436
|
validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
|
|
11679
11437
|
chosenPath = inp.overridePath;
|
|
@@ -11684,7 +11442,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
11684
11442
|
);
|
|
11685
11443
|
return null;
|
|
11686
11444
|
}
|
|
11687
|
-
} else if (
|
|
11445
|
+
} else if (existsSync17(inp.bundledPath)) {
|
|
11688
11446
|
if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
|
|
11689
11447
|
console.error(
|
|
11690
11448
|
`[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
|
|
@@ -11726,7 +11484,7 @@ var sidebar_artefacts_default = app31;
|
|
|
11726
11484
|
// server/routes/admin/sidebar-artefact-save.ts
|
|
11727
11485
|
import { mkdir as mkdir4, readdir as readdir4, stat as stat6, writeFile as writeFile5 } from "fs/promises";
|
|
11728
11486
|
import { resolve as resolve18 } from "path";
|
|
11729
|
-
import { existsSync as
|
|
11487
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11730
11488
|
var ADMIN_AGENT_FILES2 = /* @__PURE__ */ new Set(["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"]);
|
|
11731
11489
|
var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
11732
11490
|
var app32 = new Hono();
|
|
@@ -11790,7 +11548,7 @@ async function resolveSavePath(id, accountId, accountDir) {
|
|
|
11790
11548
|
}
|
|
11791
11549
|
if (UUID_RE5.test(id)) {
|
|
11792
11550
|
const dir = resolve18(ATTACHMENTS_ROOT, accountId, id);
|
|
11793
|
-
if (!
|
|
11551
|
+
if (!existsSync18(dir)) {
|
|
11794
11552
|
return { kind: "reject", status: 400, reason: "not-found" };
|
|
11795
11553
|
}
|
|
11796
11554
|
try {
|
|
@@ -11814,7 +11572,7 @@ var sidebar_artefact_save_default = app32;
|
|
|
11814
11572
|
|
|
11815
11573
|
// server/routes/admin/sidebar-artefact-content.ts
|
|
11816
11574
|
import { readFile as readFile6, readdir as readdir5 } from "fs/promises";
|
|
11817
|
-
import { existsSync as
|
|
11575
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11818
11576
|
import { resolve as resolve19 } from "path";
|
|
11819
11577
|
var UUID_RE6 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
11820
11578
|
var app33 = new Hono();
|
|
@@ -11828,7 +11586,7 @@ app33.get("/", requireAdminSession, async (c) => {
|
|
|
11828
11586
|
return new Response("Not found", { status: 404 });
|
|
11829
11587
|
}
|
|
11830
11588
|
const dir = resolve19(ATTACHMENTS_ROOT, accountId, id);
|
|
11831
|
-
if (!
|
|
11589
|
+
if (!existsSync19(dir)) {
|
|
11832
11590
|
console.error(`[admin/sidebar-artefact-content] not-found id=${id.slice(0, 8)}`);
|
|
11833
11591
|
return new Response("Not found", { status: 404 });
|
|
11834
11592
|
}
|
|
@@ -11890,7 +11648,7 @@ app34.route("/sidebar-artefact-content", sidebar_artefact_content_default);
|
|
|
11890
11648
|
var admin_default = app34;
|
|
11891
11649
|
|
|
11892
11650
|
// server/routes/sites.ts
|
|
11893
|
-
import { existsSync as
|
|
11651
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync5 } from "fs";
|
|
11894
11652
|
import { resolve as resolve20 } from "path";
|
|
11895
11653
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
11896
11654
|
var MIME = {
|
|
@@ -11957,7 +11715,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
11957
11715
|
}
|
|
11958
11716
|
let stat7;
|
|
11959
11717
|
try {
|
|
11960
|
-
stat7 =
|
|
11718
|
+
stat7 = existsSync20(filePath) ? statSync5(filePath) : null;
|
|
11961
11719
|
} catch {
|
|
11962
11720
|
stat7 = null;
|
|
11963
11721
|
}
|
|
@@ -11970,7 +11728,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
11970
11728
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
11971
11729
|
return c.text("Forbidden", 403);
|
|
11972
11730
|
}
|
|
11973
|
-
if (!
|
|
11731
|
+
if (!existsSync20(filePath)) {
|
|
11974
11732
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
11975
11733
|
return c.text("Not found", 404);
|
|
11976
11734
|
}
|
|
@@ -11989,7 +11747,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
11989
11747
|
}
|
|
11990
11748
|
let body;
|
|
11991
11749
|
try {
|
|
11992
|
-
body =
|
|
11750
|
+
body = readFileSync14(realPath);
|
|
11993
11751
|
} catch (err) {
|
|
11994
11752
|
const code = err?.code;
|
|
11995
11753
|
if (code === "EISDIR") {
|
|
@@ -12123,12 +11881,12 @@ function clientFrom(c) {
|
|
|
12123
11881
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
12124
11882
|
var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join9(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
12125
11883
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
12126
|
-
if (BRAND_JSON_PATH && !
|
|
11884
|
+
if (BRAND_JSON_PATH && !existsSync21(BRAND_JSON_PATH)) {
|
|
12127
11885
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
12128
11886
|
}
|
|
12129
|
-
if (BRAND_JSON_PATH &&
|
|
11887
|
+
if (BRAND_JSON_PATH && existsSync21(BRAND_JSON_PATH)) {
|
|
12130
11888
|
try {
|
|
12131
|
-
const parsed = JSON.parse(
|
|
11889
|
+
const parsed = JSON.parse(readFileSync15(BRAND_JSON_PATH, "utf-8"));
|
|
12132
11890
|
BRAND = { ...BRAND, ...parsed };
|
|
12133
11891
|
} catch (err) {
|
|
12134
11892
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -12150,8 +11908,8 @@ var brandLoginOpts = {
|
|
|
12150
11908
|
var ALIAS_DOMAINS_PATH2 = join9(homedir2(), BRAND.configDir, "alias-domains.json");
|
|
12151
11909
|
function loadAliasDomains() {
|
|
12152
11910
|
try {
|
|
12153
|
-
if (!
|
|
12154
|
-
const parsed = JSON.parse(
|
|
11911
|
+
if (!existsSync21(ALIAS_DOMAINS_PATH2)) return null;
|
|
11912
|
+
const parsed = JSON.parse(readFileSync15(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
12155
11913
|
if (!Array.isArray(parsed)) {
|
|
12156
11914
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
12157
11915
|
return null;
|
|
@@ -12497,14 +12255,14 @@ app36.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
12497
12255
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
12498
12256
|
return c.text("Forbidden", 403);
|
|
12499
12257
|
}
|
|
12500
|
-
if (!
|
|
12258
|
+
if (!existsSync21(filePath)) {
|
|
12501
12259
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
12502
12260
|
return c.text("Not found", 404);
|
|
12503
12261
|
}
|
|
12504
12262
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
12505
12263
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
12506
12264
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
12507
|
-
const body =
|
|
12265
|
+
const body = readFileSync15(filePath);
|
|
12508
12266
|
return c.body(body, 200, {
|
|
12509
12267
|
"Content-Type": contentType,
|
|
12510
12268
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -12527,14 +12285,14 @@ app36.get("/generated/:filename", (c) => {
|
|
|
12527
12285
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
12528
12286
|
return c.text("Forbidden", 403);
|
|
12529
12287
|
}
|
|
12530
|
-
if (!
|
|
12288
|
+
if (!existsSync21(filePath)) {
|
|
12531
12289
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
12532
12290
|
return c.text("Not found", 404);
|
|
12533
12291
|
}
|
|
12534
12292
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
12535
12293
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
12536
12294
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
12537
|
-
const body =
|
|
12295
|
+
const body = readFileSync15(filePath);
|
|
12538
12296
|
return c.body(body, 200, {
|
|
12539
12297
|
"Content-Type": contentType,
|
|
12540
12298
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -12544,9 +12302,9 @@ app36.route("/sites", sites_default);
|
|
|
12544
12302
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
12545
12303
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
12546
12304
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
12547
|
-
if (BRAND_JSON_PATH &&
|
|
12305
|
+
if (BRAND_JSON_PATH && existsSync21(BRAND_JSON_PATH)) {
|
|
12548
12306
|
try {
|
|
12549
|
-
const fullBrand = JSON.parse(
|
|
12307
|
+
const fullBrand = JSON.parse(readFileSync15(BRAND_JSON_PATH, "utf-8"));
|
|
12550
12308
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
12551
12309
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
12552
12310
|
} catch {
|
|
@@ -12564,8 +12322,8 @@ function readInstalledVersion() {
|
|
|
12564
12322
|
try {
|
|
12565
12323
|
if (!PLATFORM_ROOT7) return "unknown";
|
|
12566
12324
|
const versionFile = join9(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
|
|
12567
|
-
if (!
|
|
12568
|
-
const content =
|
|
12325
|
+
if (!existsSync21(versionFile)) return "unknown";
|
|
12326
|
+
const content = readFileSync15(versionFile, "utf-8").trim();
|
|
12569
12327
|
return content || "unknown";
|
|
12570
12328
|
} catch {
|
|
12571
12329
|
return "unknown";
|
|
@@ -12606,7 +12364,7 @@ var clientErrorReporterScript = `<script>
|
|
|
12606
12364
|
function cachedHtml(file) {
|
|
12607
12365
|
let html = htmlCache.get(file);
|
|
12608
12366
|
if (!html) {
|
|
12609
|
-
html =
|
|
12367
|
+
html = readFileSync15(resolve21(process.cwd(), "public", file), "utf-8");
|
|
12610
12368
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
12611
12369
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
12612
12370
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -12625,13 +12383,13 @@ function loadBrandingCache(agentSlug) {
|
|
|
12625
12383
|
const configDir2 = join9(homedir2(), BRAND.configDir);
|
|
12626
12384
|
try {
|
|
12627
12385
|
const accountJsonPath = join9(configDir2, "account.json");
|
|
12628
|
-
if (!
|
|
12629
|
-
const account = JSON.parse(
|
|
12386
|
+
if (!existsSync21(accountJsonPath)) return null;
|
|
12387
|
+
const account = JSON.parse(readFileSync15(accountJsonPath, "utf-8"));
|
|
12630
12388
|
const accountId = account.accountId;
|
|
12631
12389
|
if (!accountId) return null;
|
|
12632
12390
|
const cachePath = join9(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
12633
|
-
if (!
|
|
12634
|
-
return JSON.parse(
|
|
12391
|
+
if (!existsSync21(cachePath)) return null;
|
|
12392
|
+
return JSON.parse(readFileSync15(cachePath, "utf-8"));
|
|
12635
12393
|
} catch {
|
|
12636
12394
|
return null;
|
|
12637
12395
|
}
|
|
@@ -12640,8 +12398,8 @@ function resolveDefaultSlug() {
|
|
|
12640
12398
|
try {
|
|
12641
12399
|
const configDir2 = join9(homedir2(), BRAND.configDir);
|
|
12642
12400
|
const accountJsonPath = join9(configDir2, "account.json");
|
|
12643
|
-
if (!
|
|
12644
|
-
const account = JSON.parse(
|
|
12401
|
+
if (!existsSync21(accountJsonPath)) return null;
|
|
12402
|
+
const account = JSON.parse(readFileSync15(accountJsonPath, "utf-8"));
|
|
12645
12403
|
return account.defaultAgent || null;
|
|
12646
12404
|
} catch {
|
|
12647
12405
|
return null;
|
|
@@ -12714,7 +12472,7 @@ app36.use("/vnc-popout.html", logViewerFetch);
|
|
|
12714
12472
|
app36.get("/vnc-popout.html", (c) => {
|
|
12715
12473
|
let html = htmlCache.get("vnc-popout.html");
|
|
12716
12474
|
if (!html) {
|
|
12717
|
-
html =
|
|
12475
|
+
html = readFileSync15(resolve21(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
12718
12476
|
const name = escapeHtml(BRAND.productName);
|
|
12719
12477
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
12720
12478
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -12804,8 +12562,8 @@ try {
|
|
|
12804
12562
|
(async () => {
|
|
12805
12563
|
try {
|
|
12806
12564
|
let userId = "";
|
|
12807
|
-
if (
|
|
12808
|
-
const users = JSON.parse(
|
|
12565
|
+
if (existsSync21(USERS_FILE)) {
|
|
12566
|
+
const users = JSON.parse(readFileSync15(USERS_FILE, "utf-8").trim() || "[]");
|
|
12809
12567
|
userId = users[0]?.userId ?? "";
|
|
12810
12568
|
}
|
|
12811
12569
|
await backfillNullUserIdConversations(userId);
|
|
@@ -12835,6 +12593,29 @@ var bootEntitlement = bootAccountConfig ? resolveEntitlement(
|
|
|
12835
12593
|
}
|
|
12836
12594
|
) : null;
|
|
12837
12595
|
autoDeliverPremiumPlugins(bootEntitlement?.purchasedPlugins ?? void 0);
|
|
12596
|
+
(async () => {
|
|
12597
|
+
if (!bootAccount) return;
|
|
12598
|
+
try {
|
|
12599
|
+
const { recoverRunningCloudflareTasks } = await import("./cloudflare-task-tracker-Q4X5BYR7.js");
|
|
12600
|
+
const result = await recoverRunningCloudflareTasks(
|
|
12601
|
+
bootAccount.accountId,
|
|
12602
|
+
configDirForWhatsApp,
|
|
12603
|
+
// No conversationKey at boot — the tracker's writeNodeWithEdges path
|
|
12604
|
+
// requires it for the Conversation join, but the recovery path is
|
|
12605
|
+
// operating on Tasks whose Conversation is already linked. Pass null
|
|
12606
|
+
// and let the tracker pick up the linked Conversation via the Task's
|
|
12607
|
+
// existing edge.
|
|
12608
|
+
null
|
|
12609
|
+
);
|
|
12610
|
+
if (result.scanned > 0) {
|
|
12611
|
+
console.error(
|
|
12612
|
+
`[task] reconciler-startup kind=cloudflare-tunnel-login scanned=${result.scanned} resolved=${result.resolved.length}`
|
|
12613
|
+
);
|
|
12614
|
+
}
|
|
12615
|
+
} catch (err) {
|
|
12616
|
+
console.error(`[task] reconciler-startup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12617
|
+
}
|
|
12618
|
+
})();
|
|
12838
12619
|
var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
|
|
12839
12620
|
var bootDelivered = [];
|
|
12840
12621
|
var bootDistMissing = [];
|