meshy-node 0.3.4 → 0.3.6
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/README.md +1 -1
- package/dashboard/assets/DashboardPage-CBz6UrRs.js +119 -0
- package/dashboard/assets/{DashboardShared-BdW5P7SR.js → DashboardShared-CsdH5gbQ.js} +3 -3
- package/dashboard/assets/{DiffTab-BQZgk5X1.js → DiffTab-D32e4GZ0.js} +3 -3
- package/dashboard/assets/{FilesTab-BO5dV6XO.js → FilesTab-JtS0K69s.js} +7 -7
- package/dashboard/assets/{PreviewTab-B0Kmn8YC.js → PreviewTab-DjvNLJqk.js} +2 -2
- package/dashboard/assets/{SharedConversationPage-BocsZBPi.js → SharedConversationPage-DkfDIi1Q.js} +3 -3
- package/dashboard/assets/{file-D9zXUdI6.js → file-DEAybfOx.js} +1 -1
- package/dashboard/assets/{folder-gwCJ2sbc.js → folder-BgNe6kvb.js} +1 -1
- package/dashboard/assets/index-BZEpNAD0.css +1 -0
- package/dashboard/assets/index-CTmXIGF9.js +271 -0
- package/dashboard/assets/{input-Cdu3B_SQ.js → input-zoVCGkkt.js} +1 -1
- package/dashboard/index.html +2 -2
- package/main.cjs +1913 -1830
- package/package.json +1 -1
- package/runtime-metadata.json +5 -5
- package/dashboard/assets/DashboardPage-CJ55N6Jh.js +0 -124
- package/dashboard/assets/index-D8EsMsvI.js +0 -266
- package/dashboard/assets/index-DvQBsfbb.css +0 -1
package/main.cjs
CHANGED
|
@@ -33847,6 +33847,7 @@ var TaskEngine = class {
|
|
|
33847
33847
|
}
|
|
33848
33848
|
/** Tracks which node a task was previously assigned to, for retry preference. */
|
|
33849
33849
|
previousAssignments = /* @__PURE__ */ new Map();
|
|
33850
|
+
deletedTaskIds = /* @__PURE__ */ new Set();
|
|
33850
33851
|
// ── Task CRUD ────────────────────────────────────────────────────────
|
|
33851
33852
|
createTask(input) {
|
|
33852
33853
|
if (!input.title) {
|
|
@@ -33901,6 +33902,9 @@ var TaskEngine = class {
|
|
|
33901
33902
|
let created = 0;
|
|
33902
33903
|
let updated = 0;
|
|
33903
33904
|
for (const task of tasks) {
|
|
33905
|
+
if (this.deletedTaskIds.has(task.id)) {
|
|
33906
|
+
continue;
|
|
33907
|
+
}
|
|
33904
33908
|
const existing = this.store.getTask(task.id);
|
|
33905
33909
|
if (!existing) {
|
|
33906
33910
|
this.store.createTask(task);
|
|
@@ -33962,6 +33966,7 @@ var TaskEngine = class {
|
|
|
33962
33966
|
const deleted = this.store.deleteTask(id);
|
|
33963
33967
|
if (!deleted) return false;
|
|
33964
33968
|
this.previousAssignments.delete(id);
|
|
33969
|
+
this.deletedTaskIds.add(id);
|
|
33965
33970
|
if (this.logDir) {
|
|
33966
33971
|
const logFile = path4.join(this.logDir, `${id}.jsonl`);
|
|
33967
33972
|
try {
|
|
@@ -36197,14 +36202,51 @@ var import_node_child_process3 = require("child_process");
|
|
|
36197
36202
|
// ../../packages/core/src/agents/cli-invocations.ts
|
|
36198
36203
|
var fs8 = __toESM(require("fs"), 1);
|
|
36199
36204
|
var path8 = __toESM(require("path"), 1);
|
|
36205
|
+
var WINDOWS_CLAUDE_NATIVE_PACKAGES = {
|
|
36206
|
+
x64: "@anthropic-ai/claude-code-win32-x64",
|
|
36207
|
+
arm64: "@anthropic-ai/claude-code-win32-arm64"
|
|
36208
|
+
};
|
|
36209
|
+
function getPathEntries() {
|
|
36210
|
+
return (process.env.PATH ?? process.env.Path ?? process.env.path ?? "").split(path8.delimiter).filter(Boolean);
|
|
36211
|
+
}
|
|
36212
|
+
function isWindowsExecutable(filePath) {
|
|
36213
|
+
let fd = null;
|
|
36214
|
+
try {
|
|
36215
|
+
fd = fs8.openSync(filePath, "r");
|
|
36216
|
+
const signature = Buffer.alloc(2);
|
|
36217
|
+
return fs8.readSync(fd, signature, 0, signature.length, 0) === signature.length && signature.toString("ascii") === "MZ";
|
|
36218
|
+
} catch {
|
|
36219
|
+
return false;
|
|
36220
|
+
} finally {
|
|
36221
|
+
if (fd !== null) fs8.closeSync(fd);
|
|
36222
|
+
}
|
|
36223
|
+
}
|
|
36224
|
+
function getWindowsClaudePackageBinaryPath(npmBinDir) {
|
|
36225
|
+
const binaryPath = path8.join(npmBinDir, "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe");
|
|
36226
|
+
return isWindowsExecutable(binaryPath) ? binaryPath : null;
|
|
36227
|
+
}
|
|
36228
|
+
function getWindowsClaudeNativePath(npmBinDir) {
|
|
36229
|
+
const packageName = WINDOWS_CLAUDE_NATIVE_PACKAGES[process.arch];
|
|
36230
|
+
if (!packageName) return null;
|
|
36231
|
+
const nativePath = path8.join(npmBinDir, "node_modules", ...packageName.split("/"), "claude.exe");
|
|
36232
|
+
return isWindowsExecutable(nativePath) ? nativePath : null;
|
|
36233
|
+
}
|
|
36200
36234
|
function resolveClaudeInvocation() {
|
|
36201
36235
|
if (process.platform !== "win32") {
|
|
36202
36236
|
return { command: "claude", argsPrefix: [] };
|
|
36203
36237
|
}
|
|
36204
|
-
const pathEntries = (
|
|
36238
|
+
const pathEntries = getPathEntries();
|
|
36205
36239
|
for (const entry of pathEntries) {
|
|
36206
36240
|
const shimPath = path8.join(entry, "claude.cmd");
|
|
36207
36241
|
if (!fs8.existsSync(shimPath)) continue;
|
|
36242
|
+
const packageBinaryPath = getWindowsClaudePackageBinaryPath(entry);
|
|
36243
|
+
if (packageBinaryPath) {
|
|
36244
|
+
return { command: packageBinaryPath, argsPrefix: [] };
|
|
36245
|
+
}
|
|
36246
|
+
const nativePath = getWindowsClaudeNativePath(entry);
|
|
36247
|
+
if (nativePath) {
|
|
36248
|
+
return { command: nativePath, argsPrefix: [] };
|
|
36249
|
+
}
|
|
36208
36250
|
const cliPath = path8.join(entry, "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
36209
36251
|
if (fs8.existsSync(cliPath)) {
|
|
36210
36252
|
return {
|
|
@@ -36212,6 +36254,13 @@ function resolveClaudeInvocation() {
|
|
|
36212
36254
|
argsPrefix: [cliPath]
|
|
36213
36255
|
};
|
|
36214
36256
|
}
|
|
36257
|
+
const wrapperPath = path8.join(entry, "node_modules", "@anthropic-ai", "claude-code", "cli-wrapper.cjs");
|
|
36258
|
+
if (fs8.existsSync(wrapperPath)) {
|
|
36259
|
+
return {
|
|
36260
|
+
command: process.execPath,
|
|
36261
|
+
argsPrefix: [wrapperPath]
|
|
36262
|
+
};
|
|
36263
|
+
}
|
|
36215
36264
|
}
|
|
36216
36265
|
return { command: "claude", argsPrefix: [] };
|
|
36217
36266
|
}
|
|
@@ -36219,7 +36268,7 @@ function resolveCodexInvocation() {
|
|
|
36219
36268
|
if (process.platform !== "win32") {
|
|
36220
36269
|
return { command: "codex", argsPrefix: [] };
|
|
36221
36270
|
}
|
|
36222
|
-
const pathEntries = (
|
|
36271
|
+
const pathEntries = getPathEntries();
|
|
36223
36272
|
for (const entry of pathEntries) {
|
|
36224
36273
|
const shimPath = path8.join(entry, "codex.cmd");
|
|
36225
36274
|
if (!fs8.existsSync(shimPath)) continue;
|
|
@@ -37472,6 +37521,12 @@ async function handleLocalNodeMessage(deps, message) {
|
|
|
37472
37521
|
error: error instanceof Error ? error.message : String(error)
|
|
37473
37522
|
});
|
|
37474
37523
|
}
|
|
37524
|
+
return;
|
|
37525
|
+
}
|
|
37526
|
+
if (message.kind === "task.delete") {
|
|
37527
|
+
const payload = message.payload;
|
|
37528
|
+
if (!payload.taskId) return;
|
|
37529
|
+
deps.taskEngine.deleteTask(payload.taskId);
|
|
37475
37530
|
}
|
|
37476
37531
|
}
|
|
37477
37532
|
|
|
@@ -44405,1962 +44460,2010 @@ function computeParentPath(rootPath, currentPath, useAbsolute) {
|
|
|
44405
44460
|
return segments.length === 0 ? "." : segments.join("/");
|
|
44406
44461
|
}
|
|
44407
44462
|
|
|
44408
|
-
// ../../packages/api/src/
|
|
44409
|
-
|
|
44410
|
-
|
|
44411
|
-
|
|
44412
|
-
|
|
44413
|
-
|
|
44414
|
-
|
|
44463
|
+
// ../../packages/api/src/node/node-operation-service.ts
|
|
44464
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
44465
|
+
var path17 = __toESM(require("path"), 1);
|
|
44466
|
+
var import_node_crypto7 = require("crypto");
|
|
44467
|
+
|
|
44468
|
+
// ../../packages/api/src/node/agent-upgrade-service.ts
|
|
44469
|
+
var import_node_child_process9 = require("child_process");
|
|
44470
|
+
|
|
44471
|
+
// ../../packages/api/src/app/system-info.ts
|
|
44472
|
+
var os6 = __toESM(require("os"), 1);
|
|
44473
|
+
var import_node_child_process8 = require("child_process");
|
|
44474
|
+
var RUNTIME_TOOLS = [
|
|
44475
|
+
{ id: "claude", label: "Claude Code", command: "claude", packageName: "@anthropic-ai/claude-code" },
|
|
44476
|
+
{ id: "codex", label: "Codex", command: "codex", packageName: "@openai/codex" }
|
|
44477
|
+
];
|
|
44478
|
+
var runtimeToolUpdateCache = /* @__PURE__ */ new Map();
|
|
44479
|
+
function clearRuntimeToolUpdateCache(packageName) {
|
|
44480
|
+
if (packageName) {
|
|
44481
|
+
runtimeToolUpdateCache.delete(packageName);
|
|
44482
|
+
return;
|
|
44415
44483
|
}
|
|
44416
|
-
|
|
44417
|
-
const log2 = logger27?.child("nodes/workdir-local");
|
|
44418
|
-
let logged = false;
|
|
44419
|
-
let result;
|
|
44420
|
-
let serviceDurationMs;
|
|
44421
|
-
const logCompletion = (closed) => {
|
|
44422
|
-
if (logged) return;
|
|
44423
|
-
logged = true;
|
|
44424
|
-
log2?.info?.("completed local node workdir tree request", {
|
|
44425
|
-
nodeId: self2.id,
|
|
44426
|
-
requestedNodeId: nodeId,
|
|
44427
|
-
path: query.path,
|
|
44428
|
-
directoriesOnly: query.directoriesOnly,
|
|
44429
|
-
allowAbsolute: query.allowAbsolute,
|
|
44430
|
-
limit: query.limit,
|
|
44431
|
-
offset: query.offset,
|
|
44432
|
-
entries: result?.entries.length,
|
|
44433
|
-
total: result?.total,
|
|
44434
|
-
serviceDurationMs,
|
|
44435
|
-
durationMs: Date.now() - startedAt,
|
|
44436
|
-
statusCode: res.statusCode,
|
|
44437
|
-
closed
|
|
44438
|
-
});
|
|
44439
|
-
};
|
|
44440
|
-
res.once("finish", () => logCompletion(false));
|
|
44441
|
-
res.once("close", () => {
|
|
44442
|
-
if (!res.writableEnded) {
|
|
44443
|
-
logCompletion(true);
|
|
44444
|
-
}
|
|
44445
|
-
});
|
|
44446
|
-
const serviceStartedAt = Date.now();
|
|
44447
|
-
result = getLocalNodeWorkDirTree(
|
|
44448
|
-
self2.id,
|
|
44449
|
-
self2.workDir ?? workDir,
|
|
44450
|
-
query.path,
|
|
44451
|
-
{
|
|
44452
|
-
limit: query.limit,
|
|
44453
|
-
offset: query.offset,
|
|
44454
|
-
directoriesOnly: query.directoriesOnly,
|
|
44455
|
-
allowAbsolute: query.allowAbsolute
|
|
44456
|
-
}
|
|
44457
|
-
);
|
|
44458
|
-
serviceDurationMs = Date.now() - serviceStartedAt;
|
|
44459
|
-
res.json(result);
|
|
44484
|
+
runtimeToolUpdateCache.clear();
|
|
44460
44485
|
}
|
|
44461
|
-
function
|
|
44462
|
-
const
|
|
44463
|
-
|
|
44464
|
-
|
|
44465
|
-
|
|
44466
|
-
|
|
44467
|
-
}
|
|
44468
|
-
|
|
44469
|
-
|
|
44470
|
-
|
|
44471
|
-
|
|
44472
|
-
|
|
44473
|
-
|
|
44474
|
-
offset: query.offset,
|
|
44475
|
-
allowAbsolute: query.allowAbsolute
|
|
44476
|
-
}
|
|
44477
|
-
));
|
|
44486
|
+
function runRuntimeToolCommand(command, args, platform2) {
|
|
44487
|
+
const result = (0, import_node_child_process8.spawnSync)(command, args, {
|
|
44488
|
+
encoding: "utf-8",
|
|
44489
|
+
shell: platform2 === "win32" && command !== "where",
|
|
44490
|
+
windowsHide: true,
|
|
44491
|
+
timeout: 2500
|
|
44492
|
+
});
|
|
44493
|
+
return {
|
|
44494
|
+
status: result.status,
|
|
44495
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
44496
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
44497
|
+
error: result.error?.message ?? null
|
|
44498
|
+
};
|
|
44478
44499
|
}
|
|
44479
|
-
function
|
|
44480
|
-
|
|
44481
|
-
|
|
44482
|
-
const self2 = nodeRegistry.getSelf();
|
|
44483
|
-
if (options.requireSelfNode && nodeId !== self2.id) {
|
|
44484
|
-
throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
|
|
44500
|
+
function normalizeOutput(output) {
|
|
44501
|
+
if (!output) {
|
|
44502
|
+
return null;
|
|
44485
44503
|
}
|
|
44486
|
-
|
|
44487
|
-
|
|
44488
|
-
self2.workDir ?? workDir,
|
|
44489
|
-
body.path,
|
|
44490
|
-
{
|
|
44491
|
-
branchName: body.branchName,
|
|
44492
|
-
startPoint: body.startPoint,
|
|
44493
|
-
limit: body.limit,
|
|
44494
|
-
offset: body.offset,
|
|
44495
|
-
allowAbsolute: body.allowAbsolute
|
|
44496
|
-
}
|
|
44497
|
-
));
|
|
44498
|
-
}
|
|
44499
|
-
|
|
44500
|
-
// ../../packages/api/src/tasks/task-route-utils.ts
|
|
44501
|
-
var fs15 = __toESM(require("fs"), 1);
|
|
44502
|
-
var import_node_stream2 = require("stream");
|
|
44503
|
-
var import_promises5 = require("stream/promises");
|
|
44504
|
-
|
|
44505
|
-
// ../../packages/api/src/node/node-message-compat.ts
|
|
44506
|
-
var LEGACY_KIND_BY_NODE_MESSAGE = {
|
|
44507
|
-
"node.workdir.tree": "node-workdir-tree",
|
|
44508
|
-
"node.workdir.branch-info": "node-workdir-branch-info",
|
|
44509
|
-
"node.workdir.branch-create": "node-workdir-branch-create",
|
|
44510
|
-
"node.sessions.list": "node-sessions-list",
|
|
44511
|
-
"node.transport.set": "devtunnel",
|
|
44512
|
-
"node.agent.upgrade": "node-agent-upgrade",
|
|
44513
|
-
"task.cancel": "task-cancel",
|
|
44514
|
-
"task.logs": "task-logs",
|
|
44515
|
-
"task.output.summary": "task-output-summary",
|
|
44516
|
-
"task.output.tree": "task-output-tree",
|
|
44517
|
-
"task.output.content": "task-output-content",
|
|
44518
|
-
"task.output.download": "task-output-download",
|
|
44519
|
-
"task.output.diff": "task-output-diff",
|
|
44520
|
-
"task.preview.create": "task-preview-session"
|
|
44521
|
-
};
|
|
44522
|
-
function canRequestNodeMessage(heartbeat) {
|
|
44523
|
-
return !!(heartbeat?.requestNodeMessage || heartbeat?.requestWorkerControl);
|
|
44504
|
+
const firstLine = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
44505
|
+
return firstLine ?? null;
|
|
44524
44506
|
}
|
|
44525
|
-
function
|
|
44526
|
-
|
|
44527
|
-
|
|
44528
|
-
|
|
44507
|
+
function formatLocalDate(date) {
|
|
44508
|
+
const year = date.getFullYear();
|
|
44509
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
44510
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
44511
|
+
return `${year}-${month}-${day}`;
|
|
44529
44512
|
}
|
|
44530
|
-
function
|
|
44513
|
+
function resolveCommandPath(command, commandRunner, platform2) {
|
|
44514
|
+
const lookupCommand = platform2 === "win32" ? "where" : "which";
|
|
44515
|
+
const result = commandRunner(lookupCommand, [command]);
|
|
44516
|
+
if (result.status === 0) {
|
|
44517
|
+
return { path: normalizeOutput(result.stdout) };
|
|
44518
|
+
}
|
|
44531
44519
|
return {
|
|
44532
|
-
|
|
44533
|
-
|
|
44520
|
+
path: null,
|
|
44521
|
+
detail: normalizeOutput(result.stderr) ?? result.error ?? null
|
|
44534
44522
|
};
|
|
44535
44523
|
}
|
|
44536
|
-
|
|
44537
|
-
|
|
44538
|
-
function isRecord4(value) {
|
|
44539
|
-
return typeof value === "object" && value !== null;
|
|
44540
|
-
}
|
|
44541
|
-
function restoreTaskState(taskEngine, task) {
|
|
44542
|
-
taskEngine.updateTask(task.id, {
|
|
44543
|
-
status: task.status,
|
|
44544
|
-
result: task.result,
|
|
44545
|
-
error: task.error,
|
|
44546
|
-
assignedTo: task.assignedTo
|
|
44547
|
-
});
|
|
44524
|
+
function extractSemver(value) {
|
|
44525
|
+
return value?.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? null;
|
|
44548
44526
|
}
|
|
44549
|
-
function
|
|
44550
|
-
|
|
44551
|
-
|
|
44552
|
-
|
|
44553
|
-
|
|
44554
|
-
|
|
44555
|
-
|
|
44556
|
-
return { logs: [], total: 0 };
|
|
44557
|
-
}
|
|
44558
|
-
const content = fs15.readFileSync(logPath, "utf-8");
|
|
44559
|
-
const allLines = content.trim().split("\n").filter(Boolean);
|
|
44560
|
-
const logs = [];
|
|
44561
|
-
for (let i = after; i < allLines.length; i++) {
|
|
44562
|
-
try {
|
|
44563
|
-
logs.push(JSON.parse(allLines[i]));
|
|
44564
|
-
} catch {
|
|
44527
|
+
function normalizePackageVersion(output) {
|
|
44528
|
+
if (!output) return null;
|
|
44529
|
+
const trimmed = output.trim();
|
|
44530
|
+
try {
|
|
44531
|
+
const parsed = JSON.parse(trimmed);
|
|
44532
|
+
if (typeof parsed === "string") {
|
|
44533
|
+
return parsed.trim() || null;
|
|
44565
44534
|
}
|
|
44535
|
+
} catch {
|
|
44566
44536
|
}
|
|
44567
|
-
return
|
|
44537
|
+
return normalizeOutput(trimmed);
|
|
44568
44538
|
}
|
|
44569
|
-
function
|
|
44570
|
-
if (
|
|
44571
|
-
|
|
44572
|
-
|
|
44573
|
-
|
|
44574
|
-
|
|
44575
|
-
|
|
44576
|
-
|
|
44577
|
-
|
|
44578
|
-
|
|
44539
|
+
function normalizeInstalledPackageVersion(output, packageName) {
|
|
44540
|
+
if (!output) return null;
|
|
44541
|
+
const trimmed = output.trim();
|
|
44542
|
+
try {
|
|
44543
|
+
const parsed = JSON.parse(trimmed);
|
|
44544
|
+
const version2 = parsed.dependencies?.[packageName]?.version;
|
|
44545
|
+
if (typeof version2 === "string") {
|
|
44546
|
+
return version2.trim() || null;
|
|
44547
|
+
}
|
|
44548
|
+
} catch {
|
|
44579
44549
|
}
|
|
44550
|
+
return extractSemver(normalizeOutput(trimmed));
|
|
44580
44551
|
}
|
|
44581
|
-
function
|
|
44582
|
-
|
|
44583
|
-
|
|
44584
|
-
}
|
|
44585
|
-
const parsed = Date.parse(event.timestamp);
|
|
44586
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
44552
|
+
function resolveInstalledPackageVersion(definition, commandRunner) {
|
|
44553
|
+
const result = commandRunner("npm", ["list", "-g", definition.packageName, "--depth=0", "--json"]);
|
|
44554
|
+
return normalizeInstalledPackageVersion(result.stdout ?? null, definition.packageName);
|
|
44587
44555
|
}
|
|
44588
|
-
function
|
|
44589
|
-
const
|
|
44590
|
-
|
|
44591
|
-
|
|
44556
|
+
function compareSemver(left, right) {
|
|
44557
|
+
const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10));
|
|
44558
|
+
const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10));
|
|
44559
|
+
for (let index = 0; index < 3; index += 1) {
|
|
44560
|
+
const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
|
|
44561
|
+
if (delta !== 0) return delta;
|
|
44592
44562
|
}
|
|
44593
|
-
return
|
|
44594
|
-
if (part.type === "text") return `t:${part.text}`;
|
|
44595
|
-
if (part.type === "tool_result") return `r:${part.toolUseId}:${part.isError === true ? "1" : "0"}:${part.content}`;
|
|
44596
|
-
const dataStart = part.data.slice(0, 32);
|
|
44597
|
-
const dataEnd = part.data.slice(-32);
|
|
44598
|
-
return `i:${part.mediaType}:${part.data.length}:${dataStart}:${dataEnd}`;
|
|
44599
|
-
}).join("|");
|
|
44563
|
+
return 0;
|
|
44600
44564
|
}
|
|
44601
|
-
function
|
|
44602
|
-
|
|
44603
|
-
|
|
44604
|
-
|
|
44605
|
-
|
|
44606
|
-
|
|
44565
|
+
function checkToolUpdate(definition, installedVersion, checkedAt, commandRunner, checkedOn, updateCache) {
|
|
44566
|
+
const currentVersion = extractSemver(installedVersion);
|
|
44567
|
+
const cached = updateCache.get(definition.packageName);
|
|
44568
|
+
let latestVersion = cached?.latestVersion ?? null;
|
|
44569
|
+
let detail = cached?.detail ?? null;
|
|
44570
|
+
let updateCheckedAt = cached?.checkedAt ?? checkedAt;
|
|
44571
|
+
if (cached?.checkedOn !== checkedOn) {
|
|
44572
|
+
const latestResult = commandRunner("npm", ["view", definition.packageName, "version", "--json"]);
|
|
44573
|
+
latestVersion = latestResult.status === 0 ? normalizePackageVersion(latestResult.stdout ?? null) : null;
|
|
44574
|
+
detail = latestResult.status === 0 ? null : normalizeOutput(latestResult.stderr) ?? latestResult.error ?? "Update check failed";
|
|
44575
|
+
updateCheckedAt = checkedAt;
|
|
44576
|
+
updateCache.set(definition.packageName, {
|
|
44577
|
+
checkedOn,
|
|
44578
|
+
checkedAt,
|
|
44579
|
+
latestVersion,
|
|
44580
|
+
detail
|
|
44581
|
+
});
|
|
44607
44582
|
}
|
|
44608
|
-
return
|
|
44609
|
-
|
|
44610
|
-
|
|
44583
|
+
return {
|
|
44584
|
+
packageName: definition.packageName,
|
|
44585
|
+
currentVersion,
|
|
44586
|
+
latestVersion,
|
|
44587
|
+
updateAvailable: Boolean(currentVersion && latestVersion && compareSemver(latestVersion, currentVersion) > 0),
|
|
44588
|
+
checkedAt: updateCheckedAt,
|
|
44589
|
+
detail
|
|
44590
|
+
};
|
|
44611
44591
|
}
|
|
44612
|
-
function
|
|
44613
|
-
|
|
44614
|
-
|
|
44615
|
-
return
|
|
44616
|
-
|
|
44617
|
-
|
|
44618
|
-
|
|
44619
|
-
|
|
44620
|
-
|
|
44621
|
-
|
|
44622
|
-
|
|
44623
|
-
|
|
44624
|
-
|
|
44625
|
-
|
|
44626
|
-
}
|
|
44627
|
-
if (isRecord4(event.message)) {
|
|
44628
|
-
return `assistant:${JSON.stringify(normalizeStructuredValue(event.message))}`;
|
|
44629
|
-
}
|
|
44592
|
+
function inspectTool(definition, commandRunner, checkedAt, checkedOn, updateCache, platform2) {
|
|
44593
|
+
const resolved = resolveCommandPath(definition.command, commandRunner, platform2);
|
|
44594
|
+
if (!resolved.path) {
|
|
44595
|
+
return {
|
|
44596
|
+
id: definition.id,
|
|
44597
|
+
label: definition.label,
|
|
44598
|
+
command: definition.command,
|
|
44599
|
+
available: false,
|
|
44600
|
+
path: null,
|
|
44601
|
+
version: null,
|
|
44602
|
+
checkedAt,
|
|
44603
|
+
detail: resolved.detail ?? "Command not found on PATH",
|
|
44604
|
+
update: checkToolUpdate(definition, null, checkedAt, commandRunner, checkedOn, updateCache)
|
|
44605
|
+
};
|
|
44630
44606
|
}
|
|
44631
|
-
|
|
44607
|
+
const versionResult = commandRunner(definition.command, ["--version"]);
|
|
44608
|
+
const commandVersion = normalizeOutput(versionResult.stdout) ?? normalizeOutput(versionResult.stderr);
|
|
44609
|
+
const version2 = extractSemver(commandVersion) ? commandVersion : resolveInstalledPackageVersion(definition, commandRunner);
|
|
44610
|
+
const detail = versionResult.status === 0 ? null : normalizeOutput(versionResult.stderr) ?? versionResult.error ?? "Version check failed";
|
|
44611
|
+
return {
|
|
44612
|
+
id: definition.id,
|
|
44613
|
+
label: definition.label,
|
|
44614
|
+
command: definition.command,
|
|
44615
|
+
available: true,
|
|
44616
|
+
path: resolved.path,
|
|
44617
|
+
version: version2,
|
|
44618
|
+
checkedAt,
|
|
44619
|
+
detail,
|
|
44620
|
+
update: checkToolUpdate(definition, version2, checkedAt, commandRunner, checkedOn, updateCache)
|
|
44621
|
+
};
|
|
44632
44622
|
}
|
|
44633
|
-
function
|
|
44634
|
-
|
|
44635
|
-
|
|
44636
|
-
|
|
44637
|
-
|
|
44638
|
-
|
|
44639
|
-
|
|
44640
|
-
|
|
44641
|
-
}).map((entry) => entry.event);
|
|
44623
|
+
function inspectRuntimeTools(options = {}) {
|
|
44624
|
+
const platform2 = options.platform ?? process.platform;
|
|
44625
|
+
const commandRunner = options.commandRunner ?? ((command, args) => runRuntimeToolCommand(command, args, platform2));
|
|
44626
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
44627
|
+
const checkedAt = now.toISOString();
|
|
44628
|
+
const checkedOn = formatLocalDate(now);
|
|
44629
|
+
const updateCache = options.updateCache ?? runtimeToolUpdateCache;
|
|
44630
|
+
return RUNTIME_TOOLS.map((definition) => inspectTool(definition, commandRunner, checkedAt, checkedOn, updateCache, platform2));
|
|
44642
44631
|
}
|
|
44643
|
-
function
|
|
44644
|
-
const
|
|
44645
|
-
|
|
44646
|
-
|
|
44647
|
-
|
|
44648
|
-
|
|
44649
|
-
|
|
44632
|
+
function getOsSnapshot() {
|
|
44633
|
+
const cpus2 = os6.cpus();
|
|
44634
|
+
return {
|
|
44635
|
+
platform: os6.platform(),
|
|
44636
|
+
release: os6.release(),
|
|
44637
|
+
arch: os6.arch(),
|
|
44638
|
+
hostname: os6.hostname(),
|
|
44639
|
+
cpuModel: cpus2[0]?.model ?? null,
|
|
44640
|
+
cpuCount: cpus2.length,
|
|
44641
|
+
totalMemoryBytes: os6.totalmem(),
|
|
44642
|
+
freeMemoryBytes: os6.freemem()
|
|
44643
|
+
};
|
|
44650
44644
|
}
|
|
44651
|
-
function
|
|
44652
|
-
|
|
44653
|
-
|
|
44654
|
-
|
|
44645
|
+
function buildNodeSettingsSnapshot(options) {
|
|
44646
|
+
const uptime = process.uptime();
|
|
44647
|
+
const runtimeMetadata = options.runtimeMetadata;
|
|
44648
|
+
const agents = (options.inspectRuntimeTools?.() ?? inspectRuntimeTools()).map((agent) => ({
|
|
44649
|
+
...agent,
|
|
44650
|
+
metadataStatus: runtimeMetadata?.components?.[agent.id]?.status ?? null
|
|
44651
|
+
}));
|
|
44652
|
+
return {
|
|
44653
|
+
collectedAt: Date.now(),
|
|
44654
|
+
version: runtimeMetadata?.packageVersion ?? "0.1.0",
|
|
44655
|
+
packageName: runtimeMetadata?.packageName ?? "meshy",
|
|
44656
|
+
uptime,
|
|
44657
|
+
auth: options.auth,
|
|
44658
|
+
os: getOsSnapshot(),
|
|
44659
|
+
runtime: {
|
|
44660
|
+
nodeVersion: process.version,
|
|
44661
|
+
pid: process.pid,
|
|
44662
|
+
startedAt: Date.now() - Math.floor(uptime * 1e3),
|
|
44663
|
+
cwd: process.cwd(),
|
|
44664
|
+
workDir: options.workDir ?? null,
|
|
44665
|
+
storagePath: options.storagePath ?? null,
|
|
44666
|
+
localDashboardOrigin: options.localDashboardOrigin ?? null
|
|
44667
|
+
},
|
|
44668
|
+
agents,
|
|
44669
|
+
startupRequirements: {
|
|
44670
|
+
lastCheckedAt: runtimeMetadata?.startupRequirementsLastCheckedAt,
|
|
44671
|
+
lastCheckedOn: runtimeMetadata?.startupRequirementsLastCheckedOn,
|
|
44672
|
+
components: runtimeMetadata?.components ?? {}
|
|
44673
|
+
},
|
|
44674
|
+
components: runtimeMetadata?.components ?? {},
|
|
44675
|
+
repository: runtimeMetadata?.repository ?? {},
|
|
44676
|
+
packages: runtimeMetadata?.packages ?? {}
|
|
44677
|
+
};
|
|
44678
|
+
}
|
|
44679
|
+
|
|
44680
|
+
// ../../packages/api/src/node/agent-upgrade-service.ts
|
|
44681
|
+
var AGENT_PACKAGES = {
|
|
44682
|
+
claude: "@anthropic-ai/claude-code",
|
|
44683
|
+
codex: "@openai/codex"
|
|
44684
|
+
};
|
|
44685
|
+
var OUTPUT_LIMIT = 4e3;
|
|
44686
|
+
function summarizeOutput(value) {
|
|
44687
|
+
const text = value ?? "";
|
|
44688
|
+
return text.length > OUTPUT_LIMIT ? `${text.slice(0, OUTPUT_LIMIT)}\u2026` : text;
|
|
44689
|
+
}
|
|
44690
|
+
function defaultCommandRunner(command, args) {
|
|
44691
|
+
const result = (0, import_node_child_process9.spawnSync)(command, args, {
|
|
44692
|
+
encoding: "utf-8",
|
|
44693
|
+
shell: process.platform === "win32",
|
|
44694
|
+
windowsHide: true,
|
|
44695
|
+
timeout: 18e4
|
|
44655
44696
|
});
|
|
44697
|
+
return {
|
|
44698
|
+
status: result.status,
|
|
44699
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
44700
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
44701
|
+
error: result.error?.message ?? null
|
|
44702
|
+
};
|
|
44656
44703
|
}
|
|
44657
|
-
function
|
|
44658
|
-
|
|
44659
|
-
|
|
44660
|
-
|
|
44661
|
-
|
|
44662
|
-
|
|
44663
|
-
|
|
44664
|
-
for (const localEvent of localTranscript) {
|
|
44665
|
-
if (nativeTranscript.some((nativeEvent) => nativeEvent.signature === localEvent.signature && timestampsMatch(nativeEvent.event, localEvent.event))) {
|
|
44666
|
-
duplicates.add(localEvent.index);
|
|
44667
|
-
}
|
|
44704
|
+
function isRuntimeAgentId(value) {
|
|
44705
|
+
return value === "claude" || value === "codex";
|
|
44706
|
+
}
|
|
44707
|
+
function buildUpgradeArgs(agent, packageName) {
|
|
44708
|
+
const args = ["install", "-g", `${packageName}@latest`];
|
|
44709
|
+
if (agent === "claude") {
|
|
44710
|
+
args.push("--include=optional", "--ignore-scripts=false");
|
|
44668
44711
|
}
|
|
44669
|
-
return
|
|
44712
|
+
return args;
|
|
44670
44713
|
}
|
|
44671
|
-
function
|
|
44672
|
-
|
|
44673
|
-
|
|
44714
|
+
function upgradeRuntimeAgent(agent, options = {}) {
|
|
44715
|
+
const packageName = AGENT_PACKAGES[agent];
|
|
44716
|
+
if (!packageName) {
|
|
44717
|
+
throw new MeshyError("VALIDATION_ERROR", `Unsupported upgrade agent: ${agent}`, 400);
|
|
44674
44718
|
}
|
|
44675
|
-
const
|
|
44676
|
-
const
|
|
44677
|
-
|
|
44678
|
-
|
|
44719
|
+
const command = "npm";
|
|
44720
|
+
const args = buildUpgradeArgs(agent, packageName);
|
|
44721
|
+
const result = (options.commandRunner ?? defaultCommandRunner)(command, args);
|
|
44722
|
+
const ok = result.status === 0 && !result.error;
|
|
44723
|
+
if (ok) {
|
|
44724
|
+
clearRuntimeToolUpdateCache(packageName);
|
|
44679
44725
|
}
|
|
44680
|
-
|
|
44726
|
+
const detail = result.error ?? (summarizeOutput(result.stderr) || "Agent upgrade failed");
|
|
44727
|
+
return {
|
|
44728
|
+
ok,
|
|
44729
|
+
agent,
|
|
44730
|
+
packageName,
|
|
44731
|
+
command,
|
|
44732
|
+
args,
|
|
44733
|
+
status: result.status,
|
|
44734
|
+
stdout: summarizeOutput(result.stdout),
|
|
44735
|
+
stderr: summarizeOutput(result.stderr),
|
|
44736
|
+
detail: ok ? null : detail
|
|
44737
|
+
};
|
|
44681
44738
|
}
|
|
44682
|
-
function
|
|
44683
|
-
const
|
|
44684
|
-
|
|
44685
|
-
|
|
44686
|
-
|
|
44739
|
+
function upgradeRuntimeAgentForDeps(deps, agent) {
|
|
44740
|
+
const result = deps.upgradeRuntimeAgent?.(agent) ?? upgradeRuntimeAgent(agent);
|
|
44741
|
+
if (result.ok) {
|
|
44742
|
+
const settingsSnapshot = deps.refreshSettingsSnapshot?.();
|
|
44743
|
+
if (settingsSnapshot) {
|
|
44744
|
+
deps.nodeRegistry.updateSettingsSnapshot(deps.nodeRegistry.getSelf().id, settingsSnapshot);
|
|
44745
|
+
return { ...result, settingsSnapshot };
|
|
44746
|
+
}
|
|
44687
44747
|
}
|
|
44688
|
-
|
|
44689
|
-
if (history.logs.length === 0) {
|
|
44690
|
-
return local;
|
|
44691
|
-
}
|
|
44692
|
-
if (local.total > 0) {
|
|
44693
|
-
const logs = mergeNativeSessionLogs(history.logs, local.logs);
|
|
44694
|
-
return { logs, total: logs.length };
|
|
44695
|
-
}
|
|
44696
|
-
recordLocalTaskLogs(engineRegistry, task, history.logs);
|
|
44697
|
-
return readLocalTaskLogs(engineRegistry, taskId, after, agent);
|
|
44698
|
-
}
|
|
44699
|
-
async function seedTaskSnapshotOnWorker(task, logs, node, log2, timeoutMs = 1e4) {
|
|
44700
|
-
const proxyPath = "/api/worker/import-task";
|
|
44701
|
-
const { endpoint, response } = await fetchNodeWithFallback(
|
|
44702
|
-
node,
|
|
44703
|
-
proxyPath,
|
|
44704
|
-
{
|
|
44705
|
-
method: "POST",
|
|
44706
|
-
headers: { "Content-Type": "application/json" },
|
|
44707
|
-
body: JSON.stringify({ task, logs })
|
|
44708
|
-
},
|
|
44709
|
-
timeoutMs,
|
|
44710
|
-
void 0,
|
|
44711
|
-
{ preferPublicEndpoint: true }
|
|
44712
|
-
);
|
|
44713
|
-
if (!response.ok) {
|
|
44714
|
-
const failure = await response.text().catch(() => "");
|
|
44715
|
-
throw new MeshyError("NODE_OFFLINE", failure || `Failed to seed task on ${node.name} (${response.status})`, 502);
|
|
44716
|
-
}
|
|
44717
|
-
const body = await response.json().catch(() => null);
|
|
44718
|
-
const seededLogs = Array.isArray(body?.logs) ? body.logs : logs;
|
|
44719
|
-
log2.info("seeded task snapshot on worker", {
|
|
44720
|
-
taskId: task.id,
|
|
44721
|
-
assignedTo: node.id,
|
|
44722
|
-
endpoint,
|
|
44723
|
-
importedLogCount: seededLogs.length
|
|
44724
|
-
});
|
|
44725
|
-
return seededLogs;
|
|
44726
|
-
}
|
|
44727
|
-
async function requestTaskLogsOverKeepalive(heartbeat, nodeId, task, after) {
|
|
44728
|
-
if (!canRequestNodeMessage(heartbeat)) return null;
|
|
44729
|
-
const message = createNodeMessage("task.logs", { taskId: task.id, after, task }, { expectsResponse: true });
|
|
44730
|
-
return requestFallbackNodeMessage(heartbeat, nodeId, message);
|
|
44731
|
-
}
|
|
44732
|
-
async function sendProxyResponse(res, proxyRes) {
|
|
44733
|
-
const contentType = proxyRes.headers.get("content-type");
|
|
44734
|
-
const contentDisposition = proxyRes.headers.get("content-disposition");
|
|
44735
|
-
const cacheControl = proxyRes.headers.get("cache-control");
|
|
44736
|
-
if (contentType) res.setHeader("Content-Type", contentType);
|
|
44737
|
-
if (contentDisposition) res.setHeader("Content-Disposition", contentDisposition);
|
|
44738
|
-
if (cacheControl) res.setHeader("Cache-Control", cacheControl);
|
|
44739
|
-
if (contentType?.split(";")[0]?.trim().toLowerCase() === "text/event-stream") {
|
|
44740
|
-
if (!cacheControl) res.setHeader("Cache-Control", "no-cache");
|
|
44741
|
-
res.status(proxyRes.status);
|
|
44742
|
-
res.flushHeaders();
|
|
44743
|
-
if (!proxyRes.body) {
|
|
44744
|
-
res.end();
|
|
44745
|
-
return;
|
|
44746
|
-
}
|
|
44747
|
-
try {
|
|
44748
|
-
await (0, import_promises5.pipeline)(import_node_stream2.Readable.fromWeb(proxyRes.body), res);
|
|
44749
|
-
} catch (error) {
|
|
44750
|
-
if (!res.destroyed && !res.writableEnded) {
|
|
44751
|
-
throw error;
|
|
44752
|
-
}
|
|
44753
|
-
}
|
|
44754
|
-
return;
|
|
44755
|
-
}
|
|
44756
|
-
const body = Buffer.from(await proxyRes.arrayBuffer());
|
|
44757
|
-
res.status(proxyRes.status).send(body);
|
|
44758
|
-
}
|
|
44759
|
-
async function maybeProxyReadToLeader(req, res, options = {}) {
|
|
44760
|
-
const { dataRouter, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
|
|
44761
|
-
const methods = new Set((options.methods ?? ["GET"]).map((method) => method.toUpperCase()));
|
|
44762
|
-
if (!methods.has(req.method.toUpperCase()) || nodeRegistry?.isLeader?.() !== false || !dataRouter?.proxyToLeader) {
|
|
44763
|
-
return false;
|
|
44764
|
-
}
|
|
44765
|
-
const leaderEndpoint = dataRouter.getLeaderEndpoint?.();
|
|
44766
|
-
if (!leaderEndpoint) {
|
|
44767
|
-
return false;
|
|
44768
|
-
}
|
|
44769
|
-
try {
|
|
44770
|
-
const result = await dataRouter.proxyToLeader(req);
|
|
44771
|
-
for (const [key, value] of Object.entries(result.headers)) {
|
|
44772
|
-
if (key.toLowerCase() === "content-length" || key.toLowerCase() === "transfer-encoding") continue;
|
|
44773
|
-
res.setHeader(key, value);
|
|
44774
|
-
}
|
|
44775
|
-
res.status(result.status).json(result.body);
|
|
44776
|
-
return true;
|
|
44777
|
-
} catch (error) {
|
|
44778
|
-
const log2 = rootLogger?.child?.("tasks/leader-read-proxy") ?? rootLogger;
|
|
44779
|
-
log2?.warn?.("leader read proxy failed", {
|
|
44780
|
-
path: req.originalUrl ?? req.url,
|
|
44781
|
-
leaderEndpoint,
|
|
44782
|
-
error: error instanceof Error ? error.message : String(error)
|
|
44783
|
-
});
|
|
44784
|
-
throw error instanceof MeshyError ? error : new MeshyError("NODE_OFFLINE", "Leader unreachable", 502);
|
|
44785
|
-
}
|
|
44786
|
-
}
|
|
44787
|
-
|
|
44788
|
-
// ../../packages/api/src/node/node-native-session-service.ts
|
|
44789
|
-
function getLocalNodeNativeSessions(nodeId, agent, limit) {
|
|
44790
|
-
return {
|
|
44791
|
-
nodeId,
|
|
44792
|
-
agent,
|
|
44793
|
-
sessions: listNativeSessions({ agent, limit })
|
|
44794
|
-
};
|
|
44795
|
-
}
|
|
44796
|
-
|
|
44797
|
-
// ../../packages/api/src/tasks/task-cancellation.ts
|
|
44798
|
-
var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "archived"]);
|
|
44799
|
-
function cancelTaskOnCurrentNode(deps, taskId, options = {}) {
|
|
44800
|
-
const task = deps.taskEngine.getTask(taskId);
|
|
44801
|
-
if (!task) {
|
|
44802
|
-
if (options.missingOk) {
|
|
44803
|
-
return {
|
|
44804
|
-
task: null,
|
|
44805
|
-
cancelled: false,
|
|
44806
|
-
engineCancelRequested: false,
|
|
44807
|
-
missing: true,
|
|
44808
|
-
terminal: false
|
|
44809
|
-
};
|
|
44810
|
-
}
|
|
44811
|
-
throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
|
|
44812
|
-
}
|
|
44813
|
-
if (TERMINAL_STATUSES2.has(task.status)) {
|
|
44814
|
-
if (options.terminalOk) {
|
|
44815
|
-
return {
|
|
44816
|
-
task,
|
|
44817
|
-
cancelled: false,
|
|
44818
|
-
engineCancelRequested: false,
|
|
44819
|
-
missing: false,
|
|
44820
|
-
terminal: true
|
|
44821
|
-
};
|
|
44822
|
-
}
|
|
44823
|
-
throw new MeshyError("VALIDATION_ERROR", `Task ${taskId} cannot be cancelled (status: ${task.status})`, 400);
|
|
44824
|
-
}
|
|
44825
|
-
let engineCancelRequested = false;
|
|
44826
|
-
if (task.status === "running") {
|
|
44827
|
-
const engine = deps.engineRegistry.get(task.agent);
|
|
44828
|
-
if (!engine || typeof engine.cancelTask !== "function") {
|
|
44829
|
-
deps.logger.warn("engine not registered while cancelling running task", {
|
|
44830
|
-
taskId,
|
|
44831
|
-
agent: task.agent
|
|
44832
|
-
});
|
|
44833
|
-
} else {
|
|
44834
|
-
engineCancelRequested = engine.cancelTask(taskId);
|
|
44835
|
-
if (!engineCancelRequested) {
|
|
44836
|
-
deps.logger.warn("running task had no active engine process to cancel", {
|
|
44837
|
-
taskId,
|
|
44838
|
-
agent: task.agent
|
|
44839
|
-
});
|
|
44840
|
-
}
|
|
44841
|
-
}
|
|
44842
|
-
}
|
|
44843
|
-
const cancelled = deps.taskEngine.cancelTask(taskId);
|
|
44844
|
-
if (!cancelled) {
|
|
44845
|
-
throw new MeshyError("VALIDATION_ERROR", `Task ${taskId} cannot be cancelled`, 400);
|
|
44846
|
-
}
|
|
44847
|
-
return {
|
|
44848
|
-
task,
|
|
44849
|
-
cancelled: true,
|
|
44850
|
-
engineCancelRequested,
|
|
44851
|
-
missing: false,
|
|
44852
|
-
terminal: false
|
|
44853
|
-
};
|
|
44748
|
+
return result;
|
|
44854
44749
|
}
|
|
44855
44750
|
|
|
44856
|
-
// ../../packages/api/src/
|
|
44857
|
-
var
|
|
44858
|
-
|
|
44859
|
-
|
|
44860
|
-
|
|
44861
|
-
var crypto3 = __toESM(require("crypto"), 1);
|
|
44862
|
-
var fs16 = __toESM(require("fs"), 1);
|
|
44863
|
-
var path17 = __toESM(require("path"), 1);
|
|
44864
|
-
var http2 = __toESM(require("http"), 1);
|
|
44865
|
-
var import_node_stream3 = require("stream");
|
|
44866
|
-
var import_promises6 = require("stream/promises");
|
|
44867
|
-
|
|
44868
|
-
// ../../packages/api/src/preview/preview-request.ts
|
|
44869
|
-
var PREVIEW_AUTHORIZATION_HEADER = "x-meshy-preview-authorization";
|
|
44870
|
-
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
44871
|
-
"connection",
|
|
44872
|
-
"keep-alive",
|
|
44873
|
-
"proxy-authenticate",
|
|
44874
|
-
"proxy-authorization",
|
|
44875
|
-
"te",
|
|
44876
|
-
"trailer",
|
|
44877
|
-
"transfer-encoding",
|
|
44878
|
-
"upgrade"
|
|
44879
|
-
]);
|
|
44880
|
-
function isReadOnlyPreviewMethod(method) {
|
|
44881
|
-
const normalizedMethod = method?.toUpperCase() ?? "GET";
|
|
44882
|
-
return normalizedMethod === "GET" || normalizedMethod === "HEAD";
|
|
44883
|
-
}
|
|
44884
|
-
function methodAllowsRequestBody(method) {
|
|
44885
|
-
return !isReadOnlyPreviewMethod(method);
|
|
44886
|
-
}
|
|
44887
|
-
function toArrayBuffer(value) {
|
|
44888
|
-
const copy = new Uint8Array(value.byteLength);
|
|
44889
|
-
copy.set(value);
|
|
44890
|
-
return copy.buffer;
|
|
44891
|
-
}
|
|
44892
|
-
function getParsedRequestBody(req) {
|
|
44893
|
-
const body = req.body;
|
|
44894
|
-
if (body === void 0) {
|
|
44895
|
-
return void 0;
|
|
44751
|
+
// ../../packages/api/src/node/node-operation-service.ts
|
|
44752
|
+
var NodeOperationFailure = class extends Error {
|
|
44753
|
+
constructor(message, result) {
|
|
44754
|
+
super(message);
|
|
44755
|
+
this.result = result;
|
|
44896
44756
|
}
|
|
44897
|
-
|
|
44898
|
-
|
|
44757
|
+
};
|
|
44758
|
+
var InMemoryNodeOperationStore = class {
|
|
44759
|
+
operations = /* @__PURE__ */ new Map();
|
|
44760
|
+
get(id) {
|
|
44761
|
+
const operation = this.operations.get(id);
|
|
44762
|
+
return operation ? clone2(operation) : null;
|
|
44899
44763
|
}
|
|
44900
|
-
|
|
44901
|
-
return
|
|
44764
|
+
list(filter = {}) {
|
|
44765
|
+
return Array.from(this.operations.values()).filter((operation) => matchesFilter(operation, filter)).sort((a, b) => b.createdAt - a.createdAt).map((operation) => clone2(operation));
|
|
44902
44766
|
}
|
|
44903
|
-
|
|
44904
|
-
|
|
44767
|
+
upsert(operation) {
|
|
44768
|
+
const copy = clone2(operation);
|
|
44769
|
+
this.operations.set(copy.id, copy);
|
|
44770
|
+
return clone2(copy);
|
|
44905
44771
|
}
|
|
44906
|
-
|
|
44907
|
-
|
|
44908
|
-
|
|
44909
|
-
|
|
44910
|
-
|
|
44772
|
+
update(id, updates) {
|
|
44773
|
+
const current = this.operations.get(id);
|
|
44774
|
+
if (!current) return null;
|
|
44775
|
+
const next = { ...current, ...clone2(updates), id, createdAt: current.createdAt };
|
|
44776
|
+
this.operations.set(id, next);
|
|
44777
|
+
return clone2(next);
|
|
44911
44778
|
}
|
|
44912
|
-
|
|
44913
|
-
|
|
44914
|
-
|
|
44779
|
+
};
|
|
44780
|
+
var FileNodeOperationStore = class {
|
|
44781
|
+
constructor(storagePath) {
|
|
44782
|
+
this.storagePath = storagePath;
|
|
44915
44783
|
}
|
|
44916
|
-
|
|
44917
|
-
|
|
44918
|
-
|
|
44784
|
+
operations = /* @__PURE__ */ new Map();
|
|
44785
|
+
loaded = false;
|
|
44786
|
+
get(id) {
|
|
44787
|
+
this.load();
|
|
44788
|
+
const operation = this.operations.get(id);
|
|
44789
|
+
return operation ? clone2(operation) : null;
|
|
44919
44790
|
}
|
|
44920
|
-
|
|
44921
|
-
|
|
44922
|
-
|
|
44923
|
-
const headers = {};
|
|
44924
|
-
if (!req) {
|
|
44925
|
-
return headers;
|
|
44791
|
+
list(filter = {}) {
|
|
44792
|
+
this.load();
|
|
44793
|
+
return Array.from(this.operations.values()).filter((operation) => matchesFilter(operation, filter)).sort((a, b) => b.createdAt - a.createdAt).map((operation) => clone2(operation));
|
|
44926
44794
|
}
|
|
44927
|
-
|
|
44928
|
-
|
|
44929
|
-
const
|
|
44930
|
-
|
|
44931
|
-
|
|
44932
|
-
|
|
44933
|
-
if (normalizedKey === PREVIEW_AUTHORIZATION_HEADER) {
|
|
44934
|
-
previewAuthorization = Array.isArray(value) ? value[0] : value;
|
|
44935
|
-
continue;
|
|
44936
|
-
}
|
|
44937
|
-
if (Array.isArray(value)) {
|
|
44938
|
-
headers[key] = value.join(", ");
|
|
44939
|
-
} else if (typeof value === "string") {
|
|
44940
|
-
headers[key] = value;
|
|
44941
|
-
}
|
|
44795
|
+
upsert(operation) {
|
|
44796
|
+
this.load();
|
|
44797
|
+
const copy = clone2(operation);
|
|
44798
|
+
this.operations.set(copy.id, copy);
|
|
44799
|
+
this.persist();
|
|
44800
|
+
return clone2(copy);
|
|
44942
44801
|
}
|
|
44943
|
-
|
|
44944
|
-
|
|
44802
|
+
update(id, updates) {
|
|
44803
|
+
this.load();
|
|
44804
|
+
const current = this.operations.get(id);
|
|
44805
|
+
if (!current) return null;
|
|
44806
|
+
const next = { ...current, ...clone2(updates), id, createdAt: current.createdAt };
|
|
44807
|
+
this.operations.set(id, next);
|
|
44808
|
+
this.persist();
|
|
44809
|
+
return clone2(next);
|
|
44945
44810
|
}
|
|
44946
|
-
|
|
44947
|
-
|
|
44948
|
-
|
|
44949
|
-
|
|
44950
|
-
|
|
44951
|
-
|
|
44952
|
-
const
|
|
44953
|
-
|
|
44954
|
-
|
|
44955
|
-
|
|
44956
|
-
if (normalizedKey === "authorization") {
|
|
44957
|
-
previewAuthorization = Array.isArray(value) ? value[0] : value;
|
|
44958
|
-
continue;
|
|
44959
|
-
}
|
|
44960
|
-
if (Array.isArray(value)) {
|
|
44961
|
-
headers[key] = value.join(", ");
|
|
44962
|
-
} else if (typeof value === "string") {
|
|
44963
|
-
headers[key] = value;
|
|
44811
|
+
load() {
|
|
44812
|
+
if (this.loaded) return;
|
|
44813
|
+
this.loaded = true;
|
|
44814
|
+
if (!fs15.existsSync(this.filePath)) return;
|
|
44815
|
+
const raw = JSON.parse(fs15.readFileSync(this.filePath, "utf-8"));
|
|
44816
|
+
const entries = Array.isArray(raw) ? raw : Object.values(raw);
|
|
44817
|
+
for (const entry of entries) {
|
|
44818
|
+
if (isNodeOperation(entry)) {
|
|
44819
|
+
this.operations.set(entry.id, clone2(entry));
|
|
44820
|
+
}
|
|
44964
44821
|
}
|
|
44965
44822
|
}
|
|
44966
|
-
|
|
44967
|
-
|
|
44968
|
-
|
|
44969
|
-
|
|
44970
|
-
|
|
44971
|
-
|
|
44972
|
-
// ../../packages/api/src/preview/preview-server.ts
|
|
44973
|
-
function resolvePreviewPath(rootPath, relativePath) {
|
|
44974
|
-
const sanitizedPath = relativePath.replace(/\\/g, "/");
|
|
44975
|
-
const resolvedPath = path17.resolve(rootPath, sanitizedPath);
|
|
44976
|
-
const normalizedRoot = path17.resolve(rootPath);
|
|
44977
|
-
if (!resolvedPath.startsWith(normalizedRoot + path17.sep) && resolvedPath !== normalizedRoot) {
|
|
44978
|
-
throw new Error("Invalid preview path");
|
|
44979
|
-
}
|
|
44980
|
-
return {
|
|
44981
|
-
absolutePath: resolvedPath,
|
|
44982
|
-
normalizedPath: path17.relative(normalizedRoot, resolvedPath).split(path17.sep).join("/")
|
|
44983
|
-
};
|
|
44984
|
-
}
|
|
44985
|
-
function resolvePreviewEntryPath(rootPath, entryPath) {
|
|
44986
|
-
const { absolutePath, normalizedPath } = resolvePreviewPath(rootPath, entryPath ?? "index.html");
|
|
44987
|
-
if (!fs16.existsSync(absolutePath) || !fs16.statSync(absolutePath).isFile()) {
|
|
44988
|
-
throw new Error("Preview entry not found");
|
|
44989
|
-
}
|
|
44990
|
-
return normalizedPath;
|
|
44991
|
-
}
|
|
44992
|
-
function normalizeServiceEntryPath(entryPath) {
|
|
44993
|
-
if (!entryPath || entryPath === "/") {
|
|
44994
|
-
return "";
|
|
44823
|
+
persist() {
|
|
44824
|
+
fs15.mkdirSync(this.storagePath, { recursive: true });
|
|
44825
|
+
const body = JSON.stringify(Object.fromEntries(this.operations), null, 2);
|
|
44826
|
+
const tmpPath = `${this.filePath}.tmp`;
|
|
44827
|
+
fs15.writeFileSync(tmpPath, body, "utf-8");
|
|
44828
|
+
fs15.renameSync(tmpPath, this.filePath);
|
|
44995
44829
|
}
|
|
44996
|
-
|
|
44997
|
-
|
|
44998
|
-
function assertPreviewServicePort(port) {
|
|
44999
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
45000
|
-
throw new Error("Invalid preview service port");
|
|
44830
|
+
get filePath() {
|
|
44831
|
+
return path17.join(this.storagePath, "node-operations.json");
|
|
45001
44832
|
}
|
|
45002
|
-
|
|
45003
|
-
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45007
|
-
|
|
45008
|
-
|
|
45009
|
-
}
|
|
45010
|
-
function buildPreviewRouteUrl(origin, route, session) {
|
|
45011
|
-
const encodedEntryPath = session.entryPath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
45012
|
-
const suffix = encodedEntryPath.length > 0 ? `/${encodedEntryPath}` : session.kind === "service" ? "/" : "";
|
|
45013
|
-
return `${origin.replace(/\/+$/, "")}/${route}/${session.token}${suffix}`;
|
|
45014
|
-
}
|
|
45015
|
-
var DEFAULT_TTL_MS = 15 * 60 * 1e3;
|
|
45016
|
-
var PreviewSessionManager = class {
|
|
45017
|
-
sessions = /* @__PURE__ */ new Map();
|
|
45018
|
-
create(options) {
|
|
45019
|
-
const token = crypto3.randomBytes(32).toString("hex");
|
|
45020
|
-
const expiresAt = Date.now() + (options.ttlMs ?? DEFAULT_TTL_MS);
|
|
45021
|
-
if (options.port !== void 0) {
|
|
45022
|
-
const session2 = {
|
|
45023
|
-
token,
|
|
45024
|
-
taskId: options.taskId,
|
|
45025
|
-
kind: "service",
|
|
45026
|
-
port: assertPreviewServicePort(options.port),
|
|
45027
|
-
entryPath: normalizeServiceEntryPath(options.entryPath),
|
|
45028
|
-
expiresAt
|
|
45029
|
-
};
|
|
45030
|
-
this.sessions.set(token, session2);
|
|
45031
|
-
return session2;
|
|
45032
|
-
}
|
|
45033
|
-
if (!options.rootPath) {
|
|
45034
|
-
throw new Error("Preview root path is required");
|
|
45035
|
-
}
|
|
45036
|
-
const entryPath = resolvePreviewEntryPath(options.rootPath, options.entryPath);
|
|
45037
|
-
const session = {
|
|
45038
|
-
token,
|
|
45039
|
-
taskId: options.taskId,
|
|
45040
|
-
kind: "file",
|
|
45041
|
-
rootPath: options.rootPath,
|
|
45042
|
-
entryPath,
|
|
45043
|
-
expiresAt
|
|
45044
|
-
};
|
|
45045
|
-
this.sessions.set(token, session);
|
|
45046
|
-
return session;
|
|
44833
|
+
};
|
|
44834
|
+
var NodeOperationService = class {
|
|
44835
|
+
constructor(deps, store) {
|
|
44836
|
+
this.deps = deps;
|
|
44837
|
+
this.store = store;
|
|
44838
|
+
this.registerHandler("agent.upgrade", runAgentUpgradeOperation);
|
|
44839
|
+
this.registerHandler("workdir.branch-create", runWorkDirBranchCreateOperation);
|
|
45047
44840
|
}
|
|
45048
|
-
|
|
45049
|
-
|
|
45050
|
-
|
|
45051
|
-
if (Date.now() > session.expiresAt) {
|
|
45052
|
-
this.sessions.delete(token);
|
|
45053
|
-
return null;
|
|
45054
|
-
}
|
|
45055
|
-
return session;
|
|
44841
|
+
handlers = /* @__PURE__ */ new Map();
|
|
44842
|
+
registerHandler(kind, handler) {
|
|
44843
|
+
this.handlers.set(kind, handler);
|
|
45056
44844
|
}
|
|
45057
|
-
|
|
44845
|
+
create(kind, nodeId, payload) {
|
|
45058
44846
|
const now = Date.now();
|
|
45059
|
-
|
|
45060
|
-
|
|
45061
|
-
|
|
45062
|
-
|
|
45063
|
-
|
|
45064
|
-
|
|
45065
|
-
|
|
45066
|
-
|
|
45067
|
-
|
|
45068
|
-
|
|
45069
|
-
|
|
45070
|
-
|
|
45071
|
-
|
|
45072
|
-
|
|
45073
|
-
|
|
45074
|
-
|
|
45075
|
-
|
|
45076
|
-
|
|
45077
|
-
|
|
45078
|
-
|
|
45079
|
-
|
|
45080
|
-
|
|
45081
|
-
|
|
45082
|
-
|
|
45083
|
-
|
|
45084
|
-
".pdf": "application/pdf",
|
|
45085
|
-
".md": "text/markdown",
|
|
45086
|
-
".mdx": "text/markdown"
|
|
45087
|
-
};
|
|
45088
|
-
function getMime(filePath) {
|
|
45089
|
-
return MIME_MAP2[path17.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
45090
|
-
}
|
|
45091
|
-
function escapeHtml(value) {
|
|
45092
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
45093
|
-
}
|
|
45094
|
-
function renderInlineMarkdown(value) {
|
|
45095
|
-
let rendered = escapeHtml(value);
|
|
45096
|
-
rendered = rendered.replace(/!\[([^\]]*)]\(([^\s)]+)\)/g, '<img alt="$1" src="$2">');
|
|
45097
|
-
rendered = rendered.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\[([^\]]+)]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" rel="noreferrer noopener" target="_blank">$1</a>');
|
|
45098
|
-
return rendered;
|
|
45099
|
-
}
|
|
45100
|
-
function renderMarkdownBody(markdown) {
|
|
45101
|
-
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
45102
|
-
const blocks = [];
|
|
45103
|
-
let paragraph = [];
|
|
45104
|
-
let listItems = [];
|
|
45105
|
-
let codeLines = null;
|
|
45106
|
-
const flushParagraph = () => {
|
|
45107
|
-
if (paragraph.length === 0) return;
|
|
45108
|
-
blocks.push(`<p>${renderInlineMarkdown(paragraph.join(" "))}</p>`);
|
|
45109
|
-
paragraph = [];
|
|
45110
|
-
};
|
|
45111
|
-
const flushList = () => {
|
|
45112
|
-
if (listItems.length === 0) return;
|
|
45113
|
-
blocks.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</ul>`);
|
|
45114
|
-
listItems = [];
|
|
45115
|
-
};
|
|
45116
|
-
for (const line of lines) {
|
|
45117
|
-
if (line.trim().startsWith("```")) {
|
|
45118
|
-
if (codeLines) {
|
|
45119
|
-
blocks.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
|
|
45120
|
-
codeLines = null;
|
|
45121
|
-
} else {
|
|
45122
|
-
flushParagraph();
|
|
45123
|
-
flushList();
|
|
45124
|
-
codeLines = [];
|
|
45125
|
-
}
|
|
45126
|
-
continue;
|
|
45127
|
-
}
|
|
45128
|
-
if (codeLines) {
|
|
45129
|
-
codeLines.push(line);
|
|
45130
|
-
continue;
|
|
45131
|
-
}
|
|
45132
|
-
const trimmed = line.trim();
|
|
45133
|
-
if (!trimmed) {
|
|
45134
|
-
flushParagraph();
|
|
45135
|
-
flushList();
|
|
45136
|
-
continue;
|
|
44847
|
+
const operation = {
|
|
44848
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
44849
|
+
kind,
|
|
44850
|
+
nodeId,
|
|
44851
|
+
requestedByNodeId: this.deps.nodeRegistry.getSelf().id,
|
|
44852
|
+
status: "queued",
|
|
44853
|
+
payload,
|
|
44854
|
+
createdAt: now,
|
|
44855
|
+
updatedAt: now
|
|
44856
|
+
};
|
|
44857
|
+
const saved = this.store.upsert(operation);
|
|
44858
|
+
this.deps.eventBus.emit("node.operation.created", { operation: saved });
|
|
44859
|
+
return saved;
|
|
44860
|
+
}
|
|
44861
|
+
get(id) {
|
|
44862
|
+
return this.store.get(id);
|
|
44863
|
+
}
|
|
44864
|
+
list(filter) {
|
|
44865
|
+
return this.store.list(filter);
|
|
44866
|
+
}
|
|
44867
|
+
accept(operation) {
|
|
44868
|
+
const current = this.store.get(operation.id);
|
|
44869
|
+
if (current) {
|
|
44870
|
+
if (current.status === "queued") this.scheduleRun(current.id);
|
|
44871
|
+
return current;
|
|
45137
44872
|
}
|
|
45138
|
-
const
|
|
45139
|
-
|
|
45140
|
-
|
|
45141
|
-
|
|
45142
|
-
|
|
45143
|
-
|
|
45144
|
-
|
|
44873
|
+
const saved = this.store.upsert({ ...operation, status: "queued", updatedAt: Date.now() });
|
|
44874
|
+
this.deps.eventBus.emit("node.operation.created", { operation: saved });
|
|
44875
|
+
this.scheduleRun(saved.id);
|
|
44876
|
+
return saved;
|
|
44877
|
+
}
|
|
44878
|
+
runLocal(id) {
|
|
44879
|
+
this.scheduleRun(id);
|
|
44880
|
+
}
|
|
44881
|
+
applyRemoteUpdate(operation) {
|
|
44882
|
+
const current = this.store.get(operation.id);
|
|
44883
|
+
const saved = this.store.upsert(current ? { ...current, ...operation } : operation);
|
|
44884
|
+
this.applyOperationSideEffects(saved);
|
|
44885
|
+
this.deps.eventBus.emit("node.operation.updated", { operation: saved });
|
|
44886
|
+
return saved;
|
|
44887
|
+
}
|
|
44888
|
+
markFailed(id, error) {
|
|
44889
|
+
return this.update(id, {
|
|
44890
|
+
status: "failed",
|
|
44891
|
+
error,
|
|
44892
|
+
completedAt: Date.now()
|
|
44893
|
+
});
|
|
44894
|
+
}
|
|
44895
|
+
scheduleRun(id) {
|
|
44896
|
+
setTimeout(() => {
|
|
44897
|
+
void this.run(id);
|
|
44898
|
+
}, 0);
|
|
44899
|
+
}
|
|
44900
|
+
async run(id) {
|
|
44901
|
+
const operation = this.store.get(id);
|
|
44902
|
+
if (!operation || operation.status !== "queued") return;
|
|
44903
|
+
const running = this.update(id, { status: "running", startedAt: Date.now() });
|
|
44904
|
+
if (!running) return;
|
|
44905
|
+
try {
|
|
44906
|
+
const handler = this.handlers.get(running.kind);
|
|
44907
|
+
if (!handler) throw new Error(`No node operation handler registered for kind: ${running.kind}`);
|
|
44908
|
+
const result = await handler(running, this.deps);
|
|
44909
|
+
this.update(id, { status: "succeeded", result, completedAt: Date.now() });
|
|
44910
|
+
} catch (err) {
|
|
44911
|
+
this.update(id, {
|
|
44912
|
+
status: "failed",
|
|
44913
|
+
result: err instanceof NodeOperationFailure ? err.result : void 0,
|
|
44914
|
+
error: err instanceof Error ? err.message : String(err),
|
|
44915
|
+
completedAt: Date.now()
|
|
44916
|
+
});
|
|
45145
44917
|
}
|
|
45146
|
-
|
|
45147
|
-
|
|
45148
|
-
|
|
45149
|
-
|
|
45150
|
-
|
|
44918
|
+
}
|
|
44919
|
+
update(id, updates) {
|
|
44920
|
+
const saved = this.store.update(id, { ...updates, updatedAt: Date.now() });
|
|
44921
|
+
if (!saved) return null;
|
|
44922
|
+
this.applyOperationSideEffects(saved);
|
|
44923
|
+
this.deps.eventBus.emit("node.operation.updated", { operation: saved });
|
|
44924
|
+
void this.reportToLeader(saved);
|
|
44925
|
+
return saved;
|
|
44926
|
+
}
|
|
44927
|
+
applyOperationSideEffects(operation) {
|
|
44928
|
+
if (operation.kind !== "agent.upgrade" || operation.status !== "succeeded") return;
|
|
44929
|
+
const settingsSnapshot = readSettingsSnapshot(operation.result);
|
|
44930
|
+
if (settingsSnapshot) {
|
|
44931
|
+
this.deps.nodeRegistry.updateSettingsSnapshot(operation.nodeId, settingsSnapshot);
|
|
45151
44932
|
}
|
|
45152
|
-
flushList();
|
|
45153
|
-
paragraph.push(trimmed);
|
|
45154
44933
|
}
|
|
45155
|
-
|
|
45156
|
-
|
|
44934
|
+
async reportToLeader(operation) {
|
|
44935
|
+
if (this.deps.nodeRegistry.isLeader()) return;
|
|
44936
|
+
const leaderEndpoint = this.deps.nodeRegistry.getLeaderEndpoint?.();
|
|
44937
|
+
if (!leaderEndpoint) return;
|
|
44938
|
+
try {
|
|
44939
|
+
await fetch(`${leaderEndpoint}/api/worker/node-operations/${operation.id}`, applyRequestAuthHeaders({
|
|
44940
|
+
method: "POST",
|
|
44941
|
+
headers: { "Content-Type": "application/json" },
|
|
44942
|
+
body: JSON.stringify(operation)
|
|
44943
|
+
}));
|
|
44944
|
+
} catch (err) {
|
|
44945
|
+
this.deps.logger.child("node-operation").warn("failed to report node operation update to leader", {
|
|
44946
|
+
operationId: operation.id,
|
|
44947
|
+
kind: operation.kind,
|
|
44948
|
+
error: err instanceof Error ? err.message : String(err)
|
|
44949
|
+
});
|
|
44950
|
+
}
|
|
45157
44951
|
}
|
|
45158
|
-
|
|
45159
|
-
|
|
45160
|
-
return
|
|
45161
|
-
|
|
45162
|
-
|
|
45163
|
-
|
|
45164
|
-
|
|
45165
|
-
|
|
45166
|
-
<head>
|
|
45167
|
-
<meta charset="utf-8">
|
|
45168
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
45169
|
-
<title>${escapeHtml(title)}</title>
|
|
45170
|
-
<style>
|
|
45171
|
-
body { box-sizing: border-box; max-width: 860px; margin: 0 auto; padding: 32px; color: #111827; font: 16px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
45172
|
-
h1, h2, h3, h4, h5, h6 { line-height: 1.25; margin: 1.5em 0 0.5em; }
|
|
45173
|
-
h1 { border-bottom: 1px solid #e5e7eb; padding-bottom: 0.3em; }
|
|
45174
|
-
pre { overflow: auto; border-radius: 8px; background: #111827; color: #f9fafb; padding: 16px; }
|
|
45175
|
-
code { border-radius: 4px; background: #f3f4f6; padding: 0.1em 0.25em; }
|
|
45176
|
-
pre code { background: transparent; padding: 0; }
|
|
45177
|
-
img { max-width: 100%; }
|
|
45178
|
-
a { color: #2563eb; }
|
|
45179
|
-
</style>
|
|
45180
|
-
</head>
|
|
45181
|
-
<body>
|
|
45182
|
-
${body}
|
|
45183
|
-
</body>
|
|
45184
|
-
</html>`, "utf8");
|
|
44952
|
+
};
|
|
44953
|
+
function getNodeOperationService(deps) {
|
|
44954
|
+
if (deps.nodeOperationService) return deps.nodeOperationService;
|
|
44955
|
+
const store = deps.nodeOperationStore ?? (deps.storagePath ? new FileNodeOperationStore(deps.storagePath) : new InMemoryNodeOperationStore());
|
|
44956
|
+
const service = new NodeOperationService(deps, store);
|
|
44957
|
+
deps.nodeOperationStore = store;
|
|
44958
|
+
deps.nodeOperationService = service;
|
|
44959
|
+
return service;
|
|
45185
44960
|
}
|
|
45186
|
-
function
|
|
45187
|
-
const
|
|
45188
|
-
|
|
45189
|
-
|
|
44961
|
+
async function dispatchNodeOperation(deps, operation, node) {
|
|
44962
|
+
const message = createNodeMessage("node.operation.execute", { operation });
|
|
44963
|
+
const heartbeat = deps.heartbeat;
|
|
44964
|
+
if (heartbeat.canPushToNode?.(node.id) === false) {
|
|
44965
|
+
if (!heartbeat.enqueueNodeMessage) throw new Error(`Cannot queue node operation for ${node.id}`);
|
|
44966
|
+
heartbeat.enqueueNodeMessage(node.id, message);
|
|
44967
|
+
return;
|
|
45190
44968
|
}
|
|
45191
|
-
|
|
44969
|
+
const client = new NodeMessageClient({
|
|
44970
|
+
heartbeat: heartbeat.enqueueNodeMessage ? { enqueueNodeMessage: heartbeat.enqueueNodeMessage.bind(heartbeat) } : void 0,
|
|
44971
|
+
logger: deps.logger
|
|
44972
|
+
});
|
|
44973
|
+
await client.send(node, message);
|
|
45192
44974
|
}
|
|
45193
|
-
function
|
|
45194
|
-
const
|
|
45195
|
-
if (!
|
|
45196
|
-
|
|
44975
|
+
function runAgentUpgradeOperation(operation, deps) {
|
|
44976
|
+
const agent = readPayloadString(operation, "agent");
|
|
44977
|
+
if (!isRuntimeAgentId(agent)) throw new Error(`Unsupported upgrade agent: ${agent}`);
|
|
44978
|
+
const result = upgradeRuntimeAgentForDeps(deps, agent);
|
|
44979
|
+
if (!result.ok) {
|
|
44980
|
+
throw new NodeOperationFailure(result.detail ?? `${agent} upgrade failed`, result);
|
|
45197
44981
|
}
|
|
45198
|
-
return
|
|
44982
|
+
return result;
|
|
45199
44983
|
}
|
|
45200
|
-
function
|
|
45201
|
-
const
|
|
45202
|
-
return
|
|
44984
|
+
function runWorkDirBranchCreateOperation(operation, deps) {
|
|
44985
|
+
const self2 = deps.nodeRegistry.getSelf();
|
|
44986
|
+
return createLocalNodeWorkDirBranch(
|
|
44987
|
+
self2.id,
|
|
44988
|
+
self2.workDir ?? deps.workDir,
|
|
44989
|
+
readPayloadString(operation, "path") || ".",
|
|
44990
|
+
{
|
|
44991
|
+
branchName: readPayloadString(operation, "branchName"),
|
|
44992
|
+
startPoint: readPayloadString(operation, "startPoint"),
|
|
44993
|
+
limit: readPayloadNumber(operation, "limit", 20),
|
|
44994
|
+
offset: readPayloadNumber(operation, "offset", 0),
|
|
44995
|
+
allowAbsolute: readPayloadBoolean(operation, "allowAbsolute", true)
|
|
44996
|
+
}
|
|
44997
|
+
);
|
|
45203
44998
|
}
|
|
45204
|
-
function
|
|
45205
|
-
|
|
44999
|
+
function readPayloadString(operation, key) {
|
|
45000
|
+
const payload = operation.payload;
|
|
45001
|
+
if (!payload || typeof payload !== "object") return "";
|
|
45002
|
+
const value = payload[key];
|
|
45003
|
+
return typeof value === "string" ? value : "";
|
|
45206
45004
|
}
|
|
45207
|
-
function
|
|
45208
|
-
const
|
|
45209
|
-
|
|
45210
|
-
const
|
|
45211
|
-
|
|
45212
|
-
"if(window.top===window)return;",
|
|
45213
|
-
`const previewBase=${JSON.stringify(previewBase)};`,
|
|
45214
|
-
`const previewRoot=${JSON.stringify(previewRoot)};`,
|
|
45215
|
-
"const rewritePreviewRequestUrl=(value)=>{",
|
|
45216
|
-
"if(value==null)return value;",
|
|
45217
|
-
"let raw;",
|
|
45218
|
-
'try{raw=value instanceof URL?value.href:typeof value==="string"?value:value&&typeof value.url==="string"?value.url:undefined;}catch{return value;}',
|
|
45219
|
-
"if(!raw)return value;",
|
|
45220
|
-
"let url;",
|
|
45221
|
-
"try{url=new URL(raw,window.location.href);}catch{return value;}",
|
|
45222
|
-
"if(url.origin!==window.location.origin)return value;",
|
|
45223
|
-
'if(url.pathname===previewBase||url.pathname.startsWith(previewRoot)||url.pathname.startsWith("/preview-open/"))return value;',
|
|
45224
|
-
'return previewRoot+url.pathname.replace(/^\\/+/,"")+url.search+url.hash;',
|
|
45225
|
-
"};",
|
|
45226
|
-
"const rewritePreviewRequestInput=(input)=>{",
|
|
45227
|
-
"const next=rewritePreviewRequestUrl(input);",
|
|
45228
|
-
"if(next===input)return input;",
|
|
45229
|
-
'if(typeof Request==="function"&&input instanceof Request)return new Request(next,input);',
|
|
45230
|
-
"return next;",
|
|
45231
|
-
"};",
|
|
45232
|
-
"const originalFetch=window.fetch;",
|
|
45233
|
-
'if(typeof originalFetch==="function")window.fetch=(input,init)=>originalFetch.call(window,rewritePreviewRequestInput(input),init);',
|
|
45234
|
-
"const XHR=window.XMLHttpRequest;",
|
|
45235
|
-
"if(XHR&&XHR.prototype){const open=XHR.prototype.open;XMLHttpRequest.prototype.open=function(method,url,...rest){return open.call(this,method,rewritePreviewRequestUrl(url),...rest);};}",
|
|
45236
|
-
"const OriginalEventSource=window.EventSource;",
|
|
45237
|
-
'if(typeof OriginalEventSource==="function"){window.EventSource=function(url,config){return new OriginalEventSource(rewritePreviewRequestUrl(url),config);};window.EventSource.prototype=OriginalEventSource.prototype;}',
|
|
45238
|
-
"const currentPath=window.location.pathname;",
|
|
45239
|
-
"if(currentPath===previewBase||currentPath.startsWith(previewRoot)){",
|
|
45240
|
-
"const suffix=currentPath===previewBase?'':currentPath.slice(previewRoot.length);",
|
|
45241
|
-
"const servicePath=suffix?'/' + suffix:'/';",
|
|
45242
|
-
"window.history.replaceState(window.history.state,'',servicePath+window.location.search+window.location.hash);",
|
|
45243
|
-
"}",
|
|
45244
|
-
"})();"
|
|
45245
|
-
].join("");
|
|
45246
|
-
return `<base data-meshy-preview-base href="${escapeHtmlAttribute(previewRoot)}"><script data-meshy-preview-bridge>${script}</script>`;
|
|
45005
|
+
function readPayloadNumber(operation, key, fallback) {
|
|
45006
|
+
const payload = operation.payload;
|
|
45007
|
+
if (!payload || typeof payload !== "object") return fallback;
|
|
45008
|
+
const value = payload[key];
|
|
45009
|
+
return typeof value === "number" ? value : fallback;
|
|
45247
45010
|
}
|
|
45248
|
-
function
|
|
45249
|
-
const
|
|
45250
|
-
|
|
45251
|
-
|
|
45252
|
-
|
|
45253
|
-
}
|
|
45254
|
-
const htmlPattern = /<html(?:\s[^>]*)?>/i;
|
|
45255
|
-
if (htmlPattern.test(value)) {
|
|
45256
|
-
return value.replace(htmlPattern, (match) => `${match}<head>${bridge}</head>`);
|
|
45257
|
-
}
|
|
45258
|
-
return `${bridge}${value}`;
|
|
45011
|
+
function readPayloadBoolean(operation, key, fallback) {
|
|
45012
|
+
const payload = operation.payload;
|
|
45013
|
+
if (!payload || typeof payload !== "object") return fallback;
|
|
45014
|
+
const value = payload[key];
|
|
45015
|
+
return typeof value === "boolean" ? value : fallback;
|
|
45259
45016
|
}
|
|
45260
|
-
function
|
|
45261
|
-
|
|
45017
|
+
function readSettingsSnapshot(value) {
|
|
45018
|
+
if (!value || typeof value !== "object") return void 0;
|
|
45019
|
+
const snapshot = value.settingsSnapshot;
|
|
45020
|
+
return snapshot && typeof snapshot === "object" ? snapshot : void 0;
|
|
45262
45021
|
}
|
|
45263
|
-
function
|
|
45264
|
-
return
|
|
45022
|
+
function matchesFilter(operation, filter) {
|
|
45023
|
+
return (!filter.nodeId || operation.nodeId === filter.nodeId) && (!filter.status || operation.status === filter.status) && (!filter.kind || operation.kind === filter.kind);
|
|
45265
45024
|
}
|
|
45266
|
-
function
|
|
45267
|
-
|
|
45025
|
+
function isNodeOperation(value) {
|
|
45026
|
+
if (!value || typeof value !== "object") return false;
|
|
45027
|
+
const record = value;
|
|
45028
|
+
return typeof record.id === "string" && typeof record.kind === "string" && typeof record.nodeId === "string" && typeof record.status === "string" && typeof record.createdAt === "number" && typeof record.updatedAt === "number";
|
|
45268
45029
|
}
|
|
45269
|
-
function
|
|
45270
|
-
return
|
|
45030
|
+
function clone2(value) {
|
|
45031
|
+
return JSON.parse(JSON.stringify(value));
|
|
45271
45032
|
}
|
|
45272
|
-
|
|
45273
|
-
|
|
45274
|
-
|
|
45033
|
+
|
|
45034
|
+
// ../../packages/api/src/routes/node-workdir.ts
|
|
45035
|
+
function sendLocalNodeWorkDirTree(req, res, nodeId, options = {}) {
|
|
45036
|
+
const query = NodeWorkDirTreeQuery.parse(req.query);
|
|
45037
|
+
const { nodeRegistry, workDir, logger: logger27 } = req.app.locals.deps;
|
|
45038
|
+
const self2 = nodeRegistry.getSelf();
|
|
45039
|
+
if (options.requireSelfNode && nodeId !== self2.id) {
|
|
45040
|
+
throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
|
|
45275
45041
|
}
|
|
45276
|
-
const
|
|
45277
|
-
|
|
45278
|
-
|
|
45279
|
-
|
|
45042
|
+
const startedAt = Date.now();
|
|
45043
|
+
const log2 = logger27?.child("nodes/workdir-local");
|
|
45044
|
+
let logged = false;
|
|
45045
|
+
let result;
|
|
45046
|
+
let serviceDurationMs;
|
|
45047
|
+
const logCompletion = (closed) => {
|
|
45048
|
+
if (logged) return;
|
|
45049
|
+
logged = true;
|
|
45050
|
+
log2?.info?.("completed local node workdir tree request", {
|
|
45051
|
+
nodeId: self2.id,
|
|
45052
|
+
requestedNodeId: nodeId,
|
|
45053
|
+
path: query.path,
|
|
45054
|
+
directoriesOnly: query.directoriesOnly,
|
|
45055
|
+
allowAbsolute: query.allowAbsolute,
|
|
45056
|
+
limit: query.limit,
|
|
45057
|
+
offset: query.offset,
|
|
45058
|
+
entries: result?.entries.length,
|
|
45059
|
+
total: result?.total,
|
|
45060
|
+
serviceDurationMs,
|
|
45061
|
+
durationMs: Date.now() - startedAt,
|
|
45062
|
+
statusCode: res.statusCode,
|
|
45063
|
+
closed
|
|
45064
|
+
});
|
|
45065
|
+
};
|
|
45066
|
+
res.once("finish", () => logCompletion(false));
|
|
45067
|
+
res.once("close", () => {
|
|
45068
|
+
if (!res.writableEnded) {
|
|
45069
|
+
logCompletion(true);
|
|
45070
|
+
}
|
|
45071
|
+
});
|
|
45072
|
+
const serviceStartedAt = Date.now();
|
|
45073
|
+
result = getLocalNodeWorkDirTree(
|
|
45074
|
+
self2.id,
|
|
45075
|
+
self2.workDir ?? workDir,
|
|
45076
|
+
query.path,
|
|
45077
|
+
{
|
|
45078
|
+
limit: query.limit,
|
|
45079
|
+
offset: query.offset,
|
|
45080
|
+
directoriesOnly: query.directoriesOnly,
|
|
45081
|
+
allowAbsolute: query.allowAbsolute
|
|
45082
|
+
}
|
|
45280
45083
|
);
|
|
45084
|
+
serviceDurationMs = Date.now() - serviceStartedAt;
|
|
45085
|
+
res.json(result);
|
|
45281
45086
|
}
|
|
45282
|
-
function
|
|
45283
|
-
const
|
|
45284
|
-
|
|
45285
|
-
|
|
45087
|
+
function sendLocalNodeWorkDirBranchInfo(req, res, nodeId, options = {}) {
|
|
45088
|
+
const query = NodeWorkDirBranchQuery.parse(req.query);
|
|
45089
|
+
const { nodeRegistry, workDir } = req.app.locals.deps;
|
|
45090
|
+
const self2 = nodeRegistry.getSelf();
|
|
45091
|
+
if (options.requireSelfNode && nodeId !== self2.id) {
|
|
45092
|
+
throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
|
|
45286
45093
|
}
|
|
45287
|
-
|
|
45288
|
-
|
|
45289
|
-
|
|
45290
|
-
|
|
45291
|
-
|
|
45292
|
-
|
|
45293
|
-
|
|
45094
|
+
res.json(getLocalNodeWorkDirBranchInfo(
|
|
45095
|
+
self2.id,
|
|
45096
|
+
self2.workDir ?? workDir,
|
|
45097
|
+
query.path,
|
|
45098
|
+
{
|
|
45099
|
+
limit: query.limit,
|
|
45100
|
+
offset: query.offset,
|
|
45101
|
+
allowAbsolute: query.allowAbsolute
|
|
45102
|
+
}
|
|
45103
|
+
));
|
|
45294
45104
|
}
|
|
45295
|
-
function
|
|
45296
|
-
const
|
|
45297
|
-
|
|
45298
|
-
|
|
45105
|
+
async function sendNodeWorkDirBranchCreateOperation(req, res, nodeId) {
|
|
45106
|
+
const body = CreateNodeWorkDirBranchBody.parse(req.body);
|
|
45107
|
+
const deps = req.app.locals.deps;
|
|
45108
|
+
const self2 = deps.nodeRegistry.getSelf();
|
|
45109
|
+
const isSelf = nodeId === self2.id;
|
|
45110
|
+
if (!isSelf && !deps.election.isLeader()) {
|
|
45111
|
+
throw new MeshyError("NOT_LEADER", "Only the leader can create branches on other nodes", 403);
|
|
45299
45112
|
}
|
|
45300
|
-
|
|
45301
|
-
|
|
45113
|
+
const node = isSelf ? self2 : deps.nodeRegistry.getNode(nodeId);
|
|
45114
|
+
if (!node) {
|
|
45115
|
+
throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
|
|
45302
45116
|
}
|
|
45303
|
-
|
|
45304
|
-
|
|
45305
|
-
|
|
45306
|
-
|
|
45307
|
-
|
|
45117
|
+
const payload = {
|
|
45118
|
+
path: body.path,
|
|
45119
|
+
branchName: body.branchName,
|
|
45120
|
+
startPoint: body.startPoint,
|
|
45121
|
+
limit: body.limit,
|
|
45122
|
+
offset: body.offset,
|
|
45123
|
+
allowAbsolute: body.allowAbsolute
|
|
45124
|
+
};
|
|
45125
|
+
const operations = getNodeOperationService(deps);
|
|
45126
|
+
const operation = operations.create("workdir.branch-create", nodeId, payload);
|
|
45127
|
+
if (isSelf) {
|
|
45128
|
+
operations.runLocal(operation.id);
|
|
45129
|
+
} else {
|
|
45130
|
+
try {
|
|
45131
|
+
await dispatchNodeOperation(deps, operation, node);
|
|
45132
|
+
} catch (err) {
|
|
45133
|
+
operations.markFailed(operation.id, err instanceof Error ? err.message : String(err));
|
|
45134
|
+
throw new MeshyError("NODE_OFFLINE", `Cannot reach node ${nodeId} to create a git branch`, 502);
|
|
45308
45135
|
}
|
|
45309
|
-
} catch {
|
|
45310
45136
|
}
|
|
45311
|
-
|
|
45137
|
+
res.status(202).json(operation);
|
|
45312
45138
|
}
|
|
45313
|
-
|
|
45314
|
-
|
|
45315
|
-
|
|
45316
|
-
|
|
45317
|
-
|
|
45318
|
-
|
|
45319
|
-
|
|
45320
|
-
|
|
45321
|
-
|
|
45322
|
-
|
|
45323
|
-
|
|
45324
|
-
|
|
45325
|
-
|
|
45326
|
-
|
|
45327
|
-
|
|
45328
|
-
|
|
45329
|
-
|
|
45139
|
+
|
|
45140
|
+
// ../../packages/api/src/tasks/task-route-utils.ts
|
|
45141
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
45142
|
+
var import_node_stream2 = require("stream");
|
|
45143
|
+
var import_promises5 = require("stream/promises");
|
|
45144
|
+
|
|
45145
|
+
// ../../packages/api/src/node/node-message-compat.ts
|
|
45146
|
+
var LEGACY_KIND_BY_NODE_MESSAGE = {
|
|
45147
|
+
"node.workdir.tree": "node-workdir-tree",
|
|
45148
|
+
"node.workdir.branch-info": "node-workdir-branch-info",
|
|
45149
|
+
"node.workdir.branch-create": "node-workdir-branch-create",
|
|
45150
|
+
"node.sessions.list": "node-sessions-list",
|
|
45151
|
+
"node.transport.set": "devtunnel",
|
|
45152
|
+
"node.agent.upgrade": "node-agent-upgrade",
|
|
45153
|
+
"task.cancel": "task-cancel",
|
|
45154
|
+
"task.logs": "task-logs",
|
|
45155
|
+
"task.output.summary": "task-output-summary",
|
|
45156
|
+
"task.output.tree": "task-output-tree",
|
|
45157
|
+
"task.output.content": "task-output-content",
|
|
45158
|
+
"task.output.download": "task-output-download",
|
|
45159
|
+
"task.output.diff": "task-output-diff",
|
|
45160
|
+
"task.preview.create": "task-preview-session"
|
|
45161
|
+
};
|
|
45162
|
+
function canRequestNodeMessage(heartbeat) {
|
|
45163
|
+
return !!(heartbeat?.requestNodeMessage || heartbeat?.requestWorkerControl);
|
|
45330
45164
|
}
|
|
45331
|
-
|
|
45332
|
-
if (
|
|
45333
|
-
|
|
45334
|
-
|
|
45335
|
-
}
|
|
45336
|
-
try {
|
|
45337
|
-
await (0, import_promises6.pipeline)(import_node_stream3.Readable.fromWeb(upstream.body), res);
|
|
45338
|
-
} catch (error) {
|
|
45339
|
-
if (!res.destroyed && !res.writableEnded) {
|
|
45340
|
-
throw error;
|
|
45341
|
-
}
|
|
45342
|
-
}
|
|
45165
|
+
function requestFallbackNodeMessage(heartbeat, nodeId, message) {
|
|
45166
|
+
if (heartbeat.requestNodeMessage) return heartbeat.requestNodeMessage(nodeId, message);
|
|
45167
|
+
if (heartbeat.requestWorkerControl) return heartbeat.requestWorkerControl(nodeId, toLegacyWorkerControl2(message));
|
|
45168
|
+
throw new Error("Node message fallback is not available");
|
|
45343
45169
|
}
|
|
45344
|
-
|
|
45345
|
-
|
|
45346
|
-
|
|
45347
|
-
|
|
45348
|
-
|
|
45349
|
-
let upstream;
|
|
45350
|
-
try {
|
|
45351
|
-
const body = await readPreviewRequestBody(req);
|
|
45352
|
-
upstream = await fetch(targetUrl, {
|
|
45353
|
-
method: req?.method ?? "GET",
|
|
45354
|
-
headers: buildServiceProxyHeaders(req),
|
|
45355
|
-
...body !== void 0 ? { body } : {},
|
|
45356
|
-
redirect: "manual"
|
|
45357
|
-
});
|
|
45358
|
-
} catch {
|
|
45359
|
-
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
45360
|
-
res.end("Preview service unavailable");
|
|
45361
|
-
return;
|
|
45362
|
-
}
|
|
45363
|
-
const upstreamContentType = upstream.headers.get("content-type");
|
|
45364
|
-
const isHtml = isServiceHtmlContentType(upstreamContentType);
|
|
45365
|
-
const headers = buildServiceResponseHeaders(upstream, session, isHtml);
|
|
45366
|
-
if (isServiceEventStreamContentType(upstreamContentType)) {
|
|
45367
|
-
res.writeHead(upstream.status, headers);
|
|
45368
|
-
await pipeServiceResponseBody(upstream, res);
|
|
45369
|
-
return;
|
|
45370
|
-
}
|
|
45371
|
-
const content = rewriteServiceResponseContent(Buffer.from(await upstream.arrayBuffer()), upstreamContentType, session.token);
|
|
45372
|
-
headers["content-length"] = content.length;
|
|
45373
|
-
res.writeHead(upstream.status, headers);
|
|
45374
|
-
res.end(content);
|
|
45170
|
+
function toLegacyWorkerControl2(message) {
|
|
45171
|
+
return {
|
|
45172
|
+
...typeof message.payload === "object" && message.payload !== null ? message.payload : {},
|
|
45173
|
+
kind: LEGACY_KIND_BY_NODE_MESSAGE[message.kind] ?? message.kind
|
|
45174
|
+
};
|
|
45375
45175
|
}
|
|
45376
|
-
|
|
45377
|
-
|
|
45378
|
-
|
|
45176
|
+
|
|
45177
|
+
// ../../packages/api/src/tasks/task-route-utils.ts
|
|
45178
|
+
function isRecord4(value) {
|
|
45179
|
+
return typeof value === "object" && value !== null;
|
|
45379
45180
|
}
|
|
45380
|
-
function
|
|
45381
|
-
|
|
45382
|
-
|
|
45383
|
-
|
|
45384
|
-
|
|
45385
|
-
|
|
45386
|
-
|
|
45387
|
-
<title>Preview</title>
|
|
45388
|
-
<style>
|
|
45389
|
-
html, body { width: 100%; height: 100%; margin: 0; background: #fff; }
|
|
45390
|
-
iframe { display: block; width: 100%; height: 100%; border: 0; }
|
|
45391
|
-
</style>
|
|
45392
|
-
</head>
|
|
45393
|
-
<body>
|
|
45394
|
-
<iframe src="${safePreviewUrl}" title="Preview"></iframe>
|
|
45395
|
-
</body>
|
|
45396
|
-
</html>`, "utf8");
|
|
45181
|
+
function restoreTaskState(taskEngine, task) {
|
|
45182
|
+
taskEngine.updateTask(task.id, {
|
|
45183
|
+
status: task.status,
|
|
45184
|
+
result: task.result,
|
|
45185
|
+
error: task.error,
|
|
45186
|
+
assignedTo: task.assignedTo
|
|
45187
|
+
});
|
|
45397
45188
|
}
|
|
45398
|
-
function
|
|
45399
|
-
const
|
|
45400
|
-
if (!
|
|
45401
|
-
|
|
45402
|
-
|
|
45403
|
-
|
|
45189
|
+
function readLocalTaskLogs(engineRegistry, taskId, after, agent) {
|
|
45190
|
+
const engine = engineRegistry.get(agent);
|
|
45191
|
+
if (!engine) {
|
|
45192
|
+
throw new MeshyError("VALIDATION_ERROR", `Engine not registered for agent: ${agent}`, 400);
|
|
45193
|
+
}
|
|
45194
|
+
const logPath = engine.getLogPath(taskId);
|
|
45195
|
+
if (!fs16.existsSync(logPath)) {
|
|
45196
|
+
return { logs: [], total: 0 };
|
|
45404
45197
|
}
|
|
45405
|
-
const content =
|
|
45406
|
-
|
|
45407
|
-
|
|
45408
|
-
|
|
45409
|
-
|
|
45410
|
-
|
|
45411
|
-
|
|
45198
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
45199
|
+
const allLines = content.trim().split("\n").filter(Boolean);
|
|
45200
|
+
const logs = [];
|
|
45201
|
+
for (let i = after; i < allLines.length; i++) {
|
|
45202
|
+
try {
|
|
45203
|
+
logs.push(JSON.parse(allLines[i]));
|
|
45204
|
+
} catch {
|
|
45205
|
+
}
|
|
45206
|
+
}
|
|
45207
|
+
return { logs, total: allLines.length };
|
|
45412
45208
|
}
|
|
45413
|
-
|
|
45414
|
-
|
|
45415
|
-
if (!session) {
|
|
45416
|
-
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
45417
|
-
res.end("Invalid or expired preview token");
|
|
45209
|
+
function recordLocalTaskLogs(engineRegistry, task, logs) {
|
|
45210
|
+
if (logs.length === 0) {
|
|
45418
45211
|
return;
|
|
45419
45212
|
}
|
|
45420
|
-
|
|
45421
|
-
|
|
45422
|
-
|
|
45213
|
+
const engine = engineRegistry.get(task.agent);
|
|
45214
|
+
if (!engine) {
|
|
45215
|
+
throw new MeshyError("VALIDATION_ERROR", `Engine not registered for agent: ${task.agent}`, 400);
|
|
45423
45216
|
}
|
|
45424
|
-
|
|
45425
|
-
|
|
45426
|
-
res.end("Method not allowed");
|
|
45427
|
-
return;
|
|
45217
|
+
for (const event of logs) {
|
|
45218
|
+
engine.recordOutput(task.id, event);
|
|
45428
45219
|
}
|
|
45429
|
-
|
|
45430
|
-
|
|
45431
|
-
|
|
45432
|
-
|
|
45433
|
-
resolved = resolvePreviewPath(session.rootPath, resolvedRequestedPath).absolutePath;
|
|
45434
|
-
} catch {
|
|
45435
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
45436
|
-
res.end("Invalid path");
|
|
45437
|
-
return;
|
|
45220
|
+
}
|
|
45221
|
+
function parseEventTimestamp(event) {
|
|
45222
|
+
if (typeof event.timestamp !== "string") {
|
|
45223
|
+
return null;
|
|
45438
45224
|
}
|
|
45439
|
-
|
|
45440
|
-
|
|
45441
|
-
|
|
45442
|
-
|
|
45225
|
+
const parsed = Date.parse(event.timestamp);
|
|
45226
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
45227
|
+
}
|
|
45228
|
+
function getTaskUserMessageSignature(value) {
|
|
45229
|
+
const content = normalizeTaskUserMessageContent(value);
|
|
45230
|
+
if (content.length === 0) {
|
|
45231
|
+
return null;
|
|
45443
45232
|
}
|
|
45444
|
-
|
|
45445
|
-
|
|
45446
|
-
|
|
45447
|
-
|
|
45448
|
-
|
|
45449
|
-
|
|
45450
|
-
|
|
45451
|
-
});
|
|
45452
|
-
res.end(content);
|
|
45233
|
+
return content.map((part) => {
|
|
45234
|
+
if (part.type === "text") return `t:${part.text}`;
|
|
45235
|
+
if (part.type === "tool_result") return `r:${part.toolUseId}:${part.isError === true ? "1" : "0"}:${part.content}`;
|
|
45236
|
+
const dataStart = part.data.slice(0, 32);
|
|
45237
|
+
const dataEnd = part.data.slice(-32);
|
|
45238
|
+
return `i:${part.mediaType}:${part.data.length}:${dataStart}:${dataEnd}`;
|
|
45239
|
+
}).join("|");
|
|
45453
45240
|
}
|
|
45454
|
-
|
|
45455
|
-
|
|
45456
|
-
|
|
45457
|
-
return false;
|
|
45241
|
+
function normalizeStructuredValue(value) {
|
|
45242
|
+
if (Array.isArray(value)) {
|
|
45243
|
+
return value.map((entry) => normalizeStructuredValue(entry));
|
|
45458
45244
|
}
|
|
45459
|
-
|
|
45460
|
-
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
|
|
45464
|
-
|
|
45245
|
+
if (!isRecord4(value)) {
|
|
45246
|
+
return value;
|
|
45247
|
+
}
|
|
45248
|
+
return Object.fromEntries(
|
|
45249
|
+
Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, entryValue]) => [key, normalizeStructuredValue(entryValue)])
|
|
45250
|
+
);
|
|
45251
|
+
}
|
|
45252
|
+
function getTranscriptEventSignature(event) {
|
|
45253
|
+
if (event.type === "user") {
|
|
45254
|
+
const signature = getTaskUserMessageSignature(isRecord4(event.message) ? event.message.content : event.message) ?? getTaskUserMessageSignature(event.content);
|
|
45255
|
+
return signature ? `user:${signature}` : null;
|
|
45256
|
+
}
|
|
45257
|
+
if (event.type === "assistant") {
|
|
45258
|
+
if (typeof event.message === "string") {
|
|
45259
|
+
return `assistant:${JSON.stringify(event.message)}`;
|
|
45260
|
+
}
|
|
45261
|
+
if (isRecord4(event.message) && "content" in event.message) {
|
|
45262
|
+
return `assistant:${JSON.stringify(normalizeStructuredValue(event.message.content))}`;
|
|
45263
|
+
}
|
|
45264
|
+
if (Array.isArray(event.content)) {
|
|
45265
|
+
return `assistant:${JSON.stringify(normalizeStructuredValue(event.content))}`;
|
|
45266
|
+
}
|
|
45267
|
+
if (isRecord4(event.message)) {
|
|
45268
|
+
return `assistant:${JSON.stringify(normalizeStructuredValue(event.message))}`;
|
|
45465
45269
|
}
|
|
45466
|
-
const requestedPath2 = openMatch[2];
|
|
45467
|
-
sendPreviewOpenResponse(sessionManager, openMatch[1], requestedPath2 ? `${requestedPath2}${url.search}` : url.search || void 0, res);
|
|
45468
|
-
return true;
|
|
45469
45270
|
}
|
|
45470
|
-
|
|
45471
|
-
|
|
45472
|
-
|
|
45473
|
-
|
|
45474
|
-
|
|
45271
|
+
return null;
|
|
45272
|
+
}
|
|
45273
|
+
function sortLogsChronologically(events) {
|
|
45274
|
+
return events.map((event, index) => ({ event, index })).sort((left, right) => {
|
|
45275
|
+
const leftTimestamp = parseEventTimestamp(left.event);
|
|
45276
|
+
const rightTimestamp = parseEventTimestamp(right.event);
|
|
45277
|
+
if (leftTimestamp !== null && rightTimestamp !== null && leftTimestamp !== rightTimestamp) {
|
|
45278
|
+
return leftTimestamp - rightTimestamp;
|
|
45279
|
+
}
|
|
45280
|
+
return left.index - right.index;
|
|
45281
|
+
}).map((entry) => entry.event);
|
|
45282
|
+
}
|
|
45283
|
+
function timestampsMatch(left, right) {
|
|
45284
|
+
const leftTimestamp = parseEventTimestamp(left);
|
|
45285
|
+
const rightTimestamp = parseEventTimestamp(right);
|
|
45286
|
+
if (leftTimestamp === null || rightTimestamp === null) {
|
|
45475
45287
|
return true;
|
|
45476
45288
|
}
|
|
45477
|
-
|
|
45478
|
-
await sendPreviewAssetResponse(sessionManager, token, requestedPath ? `${requestedPath}${url.search}` : url.search || void 0, res, req);
|
|
45479
|
-
return true;
|
|
45289
|
+
return Math.abs(leftTimestamp - rightTimestamp) <= 6e4;
|
|
45480
45290
|
}
|
|
45481
|
-
|
|
45482
|
-
|
|
45483
|
-
|
|
45484
|
-
|
|
45485
|
-
|
|
45486
|
-
const errorCategory = error.name === "AbortError" || /aborted/i.test(error.message) ? "abort" : /timeout/i.test(error.message) ? "timeout" : "network";
|
|
45487
|
-
return {
|
|
45488
|
-
errorName: error.name,
|
|
45489
|
-
errorMessage: error.message,
|
|
45490
|
-
errorCategory
|
|
45491
|
-
};
|
|
45492
|
-
}
|
|
45493
|
-
return {
|
|
45494
|
-
errorName: "UnknownError",
|
|
45495
|
-
errorMessage: String(error),
|
|
45496
|
-
errorCategory: "unknown"
|
|
45497
|
-
};
|
|
45291
|
+
function transcriptEvents(events) {
|
|
45292
|
+
return events.flatMap((event, index) => {
|
|
45293
|
+
const signature = getTranscriptEventSignature(event);
|
|
45294
|
+
return signature ? [{ index, event, signature }] : [];
|
|
45295
|
+
});
|
|
45498
45296
|
}
|
|
45499
|
-
function
|
|
45500
|
-
|
|
45501
|
-
|
|
45502
|
-
|
|
45503
|
-
|
|
45504
|
-
|
|
45505
|
-
|
|
45506
|
-
|
|
45507
|
-
|
|
45508
|
-
|
|
45509
|
-
totalEndpoints,
|
|
45510
|
-
timeoutMs
|
|
45511
|
-
});
|
|
45512
|
-
},
|
|
45513
|
-
onResponse: ({ attempt, endpoint, response, timeoutMs, totalEndpoints }) => {
|
|
45514
|
-
log2.debug("preview asset proxy response", {
|
|
45515
|
-
token,
|
|
45516
|
-
nodeId,
|
|
45517
|
-
requestPath,
|
|
45518
|
-
proxyPath,
|
|
45519
|
-
endpoint,
|
|
45520
|
-
attempt,
|
|
45521
|
-
totalEndpoints,
|
|
45522
|
-
timeoutMs,
|
|
45523
|
-
ok: response.ok,
|
|
45524
|
-
statusCode: response.status
|
|
45525
|
-
});
|
|
45526
|
-
},
|
|
45527
|
-
onError: ({ attempt, endpoint, error, timeoutMs, totalEndpoints }) => {
|
|
45528
|
-
log2.warn("preview asset proxy attempt failed", {
|
|
45529
|
-
token,
|
|
45530
|
-
nodeId,
|
|
45531
|
-
requestPath,
|
|
45532
|
-
proxyPath,
|
|
45533
|
-
endpoint,
|
|
45534
|
-
attempt,
|
|
45535
|
-
totalEndpoints,
|
|
45536
|
-
timeoutMs,
|
|
45537
|
-
...describeProxyError(error)
|
|
45538
|
-
});
|
|
45297
|
+
function findLocalTranscriptDuplicates(nativeLogs, localLogs) {
|
|
45298
|
+
const nativeTranscript = transcriptEvents(nativeLogs);
|
|
45299
|
+
const localTranscript = transcriptEvents(localLogs);
|
|
45300
|
+
if (nativeTranscript.length === 0 || localTranscript.length === 0) {
|
|
45301
|
+
return /* @__PURE__ */ new Set();
|
|
45302
|
+
}
|
|
45303
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
45304
|
+
for (const localEvent of localTranscript) {
|
|
45305
|
+
if (nativeTranscript.some((nativeEvent) => nativeEvent.signature === localEvent.signature && timestampsMatch(nativeEvent.event, localEvent.event))) {
|
|
45306
|
+
duplicates.add(localEvent.index);
|
|
45539
45307
|
}
|
|
45540
|
-
}
|
|
45308
|
+
}
|
|
45309
|
+
return duplicates;
|
|
45541
45310
|
}
|
|
45542
|
-
function
|
|
45543
|
-
|
|
45544
|
-
|
|
45545
|
-
const token = match?.[1];
|
|
45546
|
-
if (!token) {
|
|
45547
|
-
return null;
|
|
45311
|
+
function mergeNativeSessionLogs(nativeLogs, localLogs) {
|
|
45312
|
+
if (localLogs.length === 0) {
|
|
45313
|
+
return nativeLogs;
|
|
45548
45314
|
}
|
|
45549
|
-
const
|
|
45550
|
-
|
|
45551
|
-
|
|
45552
|
-
|
|
45553
|
-
}
|
|
45315
|
+
const duplicateLocalIndexes = findLocalTranscriptDuplicates(nativeLogs, localLogs);
|
|
45316
|
+
const retainedLocalLogs = localLogs.filter((_, index) => !duplicateLocalIndexes.has(index));
|
|
45317
|
+
if (retainedLocalLogs.length === 0) {
|
|
45318
|
+
return nativeLogs;
|
|
45319
|
+
}
|
|
45320
|
+
return sortLogsChronologically([...nativeLogs, ...retainedLocalLogs]);
|
|
45554
45321
|
}
|
|
45555
|
-
function
|
|
45556
|
-
const
|
|
45557
|
-
const
|
|
45558
|
-
|
|
45559
|
-
|
|
45560
|
-
|
|
45322
|
+
function readLocalTaskLogsWithNativeHistory(engineRegistry, task, taskId, after, agent) {
|
|
45323
|
+
const local = readLocalTaskLogs(engineRegistry, taskId, after, agent);
|
|
45324
|
+
const sessionId = typeof task?.payload?.sessionId === "string" ? task.payload.sessionId : "";
|
|
45325
|
+
if (after > 0 || task?.conversationKind !== "nativeSession" || !sessionId || task.agent !== "codex" && task.agent !== "claudecode") {
|
|
45326
|
+
return local;
|
|
45327
|
+
}
|
|
45328
|
+
const history = loadNativeSessionHistory({ agent: task.agent, sessionId });
|
|
45329
|
+
if (history.logs.length === 0) {
|
|
45330
|
+
return local;
|
|
45331
|
+
}
|
|
45332
|
+
if (local.total > 0) {
|
|
45333
|
+
const logs = mergeNativeSessionLogs(history.logs, local.logs);
|
|
45334
|
+
return { logs, total: logs.length };
|
|
45335
|
+
}
|
|
45336
|
+
recordLocalTaskLogs(engineRegistry, task, history.logs);
|
|
45337
|
+
return readLocalTaskLogs(engineRegistry, taskId, after, agent);
|
|
45338
|
+
}
|
|
45339
|
+
async function seedTaskSnapshotOnWorker(task, logs, node, log2, timeoutMs = 1e4) {
|
|
45340
|
+
const proxyPath = "/api/worker/import-task";
|
|
45341
|
+
const { endpoint, response } = await fetchNodeWithFallback(
|
|
45342
|
+
node,
|
|
45343
|
+
proxyPath,
|
|
45344
|
+
{
|
|
45345
|
+
method: "POST",
|
|
45346
|
+
headers: { "Content-Type": "application/json" },
|
|
45347
|
+
body: JSON.stringify({ task, logs })
|
|
45348
|
+
},
|
|
45349
|
+
timeoutMs,
|
|
45350
|
+
void 0,
|
|
45351
|
+
{ preferPublicEndpoint: true }
|
|
45352
|
+
);
|
|
45353
|
+
if (!response.ok) {
|
|
45354
|
+
const failure = await response.text().catch(() => "");
|
|
45355
|
+
throw new MeshyError("NODE_OFFLINE", failure || `Failed to seed task on ${node.name} (${response.status})`, 502);
|
|
45561
45356
|
}
|
|
45562
|
-
const
|
|
45563
|
-
const
|
|
45564
|
-
|
|
45565
|
-
|
|
45566
|
-
|
|
45567
|
-
|
|
45357
|
+
const body = await response.json().catch(() => null);
|
|
45358
|
+
const seededLogs = Array.isArray(body?.logs) ? body.logs : logs;
|
|
45359
|
+
log2.info("seeded task snapshot on worker", {
|
|
45360
|
+
taskId: task.id,
|
|
45361
|
+
assignedTo: node.id,
|
|
45362
|
+
endpoint,
|
|
45363
|
+
importedLogCount: seededLogs.length
|
|
45364
|
+
});
|
|
45365
|
+
return seededLogs;
|
|
45568
45366
|
}
|
|
45569
|
-
function
|
|
45570
|
-
|
|
45367
|
+
async function requestTaskLogsOverKeepalive(heartbeat, nodeId, task, after) {
|
|
45368
|
+
if (!canRequestNodeMessage(heartbeat)) return null;
|
|
45369
|
+
const message = createNodeMessage("task.logs", { taskId: task.id, after, task }, { expectsResponse: true });
|
|
45370
|
+
return requestFallbackNodeMessage(heartbeat, nodeId, message);
|
|
45571
45371
|
}
|
|
45572
|
-
function
|
|
45573
|
-
const
|
|
45574
|
-
|
|
45575
|
-
|
|
45372
|
+
async function sendProxyResponse(res, proxyRes) {
|
|
45373
|
+
const contentType = proxyRes.headers.get("content-type");
|
|
45374
|
+
const contentDisposition = proxyRes.headers.get("content-disposition");
|
|
45375
|
+
const cacheControl = proxyRes.headers.get("cache-control");
|
|
45376
|
+
if (contentType) res.setHeader("Content-Type", contentType);
|
|
45377
|
+
if (contentDisposition) res.setHeader("Content-Disposition", contentDisposition);
|
|
45378
|
+
if (cacheControl) res.setHeader("Cache-Control", cacheControl);
|
|
45379
|
+
if (contentType?.split(";")[0]?.trim().toLowerCase() === "text/event-stream") {
|
|
45380
|
+
if (!cacheControl) res.setHeader("Cache-Control", "no-cache");
|
|
45381
|
+
res.status(proxyRes.status);
|
|
45382
|
+
res.flushHeaders();
|
|
45383
|
+
if (!proxyRes.body) {
|
|
45384
|
+
res.end();
|
|
45385
|
+
return;
|
|
45386
|
+
}
|
|
45387
|
+
try {
|
|
45388
|
+
await (0, import_promises5.pipeline)(import_node_stream2.Readable.fromWeb(proxyRes.body), res);
|
|
45389
|
+
} catch (error) {
|
|
45390
|
+
if (!res.destroyed && !res.writableEnded) {
|
|
45391
|
+
throw error;
|
|
45392
|
+
}
|
|
45393
|
+
}
|
|
45394
|
+
return;
|
|
45576
45395
|
}
|
|
45577
|
-
|
|
45396
|
+
const body = Buffer.from(await proxyRes.arrayBuffer());
|
|
45397
|
+
res.status(proxyRes.status).send(body);
|
|
45578
45398
|
}
|
|
45579
|
-
|
|
45580
|
-
|
|
45581
|
-
|
|
45582
|
-
|
|
45399
|
+
async function maybeProxyReadToLeader(req, res, options = {}) {
|
|
45400
|
+
const { dataRouter, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
|
|
45401
|
+
const methods = new Set((options.methods ?? ["GET"]).map((method) => method.toUpperCase()));
|
|
45402
|
+
if (!methods.has(req.method.toUpperCase()) || nodeRegistry?.isLeader?.() !== false || !dataRouter?.proxyToLeader) {
|
|
45403
|
+
return false;
|
|
45583
45404
|
}
|
|
45584
|
-
|
|
45585
|
-
|
|
45586
|
-
|
|
45587
|
-
return null;
|
|
45588
|
-
}
|
|
45589
|
-
if (Date.now() > session.expiresAt) {
|
|
45590
|
-
this.sessions.delete(token);
|
|
45591
|
-
return null;
|
|
45592
|
-
}
|
|
45593
|
-
return session;
|
|
45405
|
+
const leaderEndpoint = dataRouter.getLeaderEndpoint?.();
|
|
45406
|
+
if (!leaderEndpoint) {
|
|
45407
|
+
return false;
|
|
45594
45408
|
}
|
|
45595
|
-
|
|
45596
|
-
const
|
|
45597
|
-
for (const [
|
|
45598
|
-
if (
|
|
45599
|
-
|
|
45600
|
-
}
|
|
45409
|
+
try {
|
|
45410
|
+
const result = await dataRouter.proxyToLeader(req);
|
|
45411
|
+
for (const [key, value] of Object.entries(result.headers)) {
|
|
45412
|
+
if (key.toLowerCase() === "content-length" || key.toLowerCase() === "transfer-encoding") continue;
|
|
45413
|
+
res.setHeader(key, value);
|
|
45601
45414
|
}
|
|
45415
|
+
res.status(result.status).json(result.body);
|
|
45416
|
+
return true;
|
|
45417
|
+
} catch (error) {
|
|
45418
|
+
const log2 = rootLogger?.child?.("tasks/leader-read-proxy") ?? rootLogger;
|
|
45419
|
+
log2?.warn?.("leader read proxy failed", {
|
|
45420
|
+
path: req.originalUrl ?? req.url,
|
|
45421
|
+
leaderEndpoint,
|
|
45422
|
+
error: error instanceof Error ? error.message : String(error)
|
|
45423
|
+
});
|
|
45424
|
+
throw error instanceof MeshyError ? error : new MeshyError("NODE_OFFLINE", "Leader unreachable", 502);
|
|
45602
45425
|
}
|
|
45603
|
-
}
|
|
45604
|
-
|
|
45605
|
-
|
|
45606
|
-
|
|
45607
|
-
return payload;
|
|
45608
|
-
}
|
|
45609
|
-
manager.register(token, nodeId, payload.expiresAt);
|
|
45426
|
+
}
|
|
45427
|
+
|
|
45428
|
+
// ../../packages/api/src/node/node-native-session-service.ts
|
|
45429
|
+
function getLocalNodeNativeSessions(nodeId, agent, limit) {
|
|
45610
45430
|
return {
|
|
45611
|
-
|
|
45612
|
-
|
|
45613
|
-
|
|
45614
|
-
entryPath: payload.entryPath,
|
|
45615
|
-
kind: payload.kind
|
|
45616
|
-
}),
|
|
45617
|
-
...payload.kind === "service" || payload.openUrl ? {
|
|
45618
|
-
openUrl: buildPreviewOpenUrl("", {
|
|
45619
|
-
token,
|
|
45620
|
-
entryPath: payload.entryPath,
|
|
45621
|
-
kind: payload.kind
|
|
45622
|
-
})
|
|
45623
|
-
} : {}
|
|
45431
|
+
nodeId,
|
|
45432
|
+
agent,
|
|
45433
|
+
sessions: listNativeSessions({ agent, limit })
|
|
45624
45434
|
};
|
|
45625
45435
|
}
|
|
45626
|
-
|
|
45627
|
-
|
|
45628
|
-
|
|
45629
|
-
|
|
45630
|
-
|
|
45631
|
-
|
|
45632
|
-
|
|
45436
|
+
|
|
45437
|
+
// ../../packages/api/src/tasks/task-cancellation.ts
|
|
45438
|
+
var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "archived"]);
|
|
45439
|
+
function cancelTaskOnCurrentNode(deps, taskId, options = {}) {
|
|
45440
|
+
const task = deps.taskEngine.getTask(taskId);
|
|
45441
|
+
if (!task) {
|
|
45442
|
+
if (options.missingOk) {
|
|
45443
|
+
return {
|
|
45444
|
+
task: null,
|
|
45445
|
+
cancelled: false,
|
|
45446
|
+
engineCancelRequested: false,
|
|
45447
|
+
missing: true,
|
|
45448
|
+
terminal: false
|
|
45449
|
+
};
|
|
45633
45450
|
}
|
|
45634
|
-
|
|
45635
|
-
res.status(200).set({
|
|
45636
|
-
"Content-Type": "text/html",
|
|
45637
|
-
"Content-Length": String(content.length),
|
|
45638
|
-
"Cache-Control": "no-cache"
|
|
45639
|
-
}).send(content);
|
|
45640
|
-
return true;
|
|
45641
|
-
}
|
|
45642
|
-
const previewRequest = parsePreviewRequest(requestPath);
|
|
45643
|
-
if (!previewRequest) {
|
|
45644
|
-
return false;
|
|
45451
|
+
throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
|
|
45645
45452
|
}
|
|
45646
|
-
|
|
45647
|
-
|
|
45648
|
-
|
|
45453
|
+
if (TERMINAL_STATUSES2.has(task.status)) {
|
|
45454
|
+
if (options.terminalOk) {
|
|
45455
|
+
return {
|
|
45456
|
+
task,
|
|
45457
|
+
cancelled: false,
|
|
45458
|
+
engineCancelRequested: false,
|
|
45459
|
+
missing: false,
|
|
45460
|
+
terminal: true
|
|
45461
|
+
};
|
|
45462
|
+
}
|
|
45463
|
+
throw new MeshyError("VALIDATION_ERROR", `Task ${taskId} cannot be cancelled (status: ${task.status})`, 400);
|
|
45649
45464
|
}
|
|
45650
|
-
|
|
45651
|
-
if (
|
|
45652
|
-
|
|
45465
|
+
let engineCancelRequested = false;
|
|
45466
|
+
if (task.status === "running") {
|
|
45467
|
+
const engine = deps.engineRegistry.get(task.agent);
|
|
45468
|
+
if (!engine || typeof engine.cancelTask !== "function") {
|
|
45469
|
+
deps.logger.warn("engine not registered while cancelling running task", {
|
|
45470
|
+
taskId,
|
|
45471
|
+
agent: task.agent
|
|
45472
|
+
});
|
|
45473
|
+
} else {
|
|
45474
|
+
engineCancelRequested = engine.cancelTask(taskId);
|
|
45475
|
+
if (!engineCancelRequested) {
|
|
45476
|
+
deps.logger.warn("running task had no active engine process to cancel", {
|
|
45477
|
+
taskId,
|
|
45478
|
+
agent: task.agent
|
|
45479
|
+
});
|
|
45480
|
+
}
|
|
45481
|
+
}
|
|
45653
45482
|
}
|
|
45654
|
-
const
|
|
45655
|
-
|
|
45656
|
-
|
|
45657
|
-
const body = await readPreviewRequestBody(req);
|
|
45658
|
-
const { endpoint, response } = await fetchNodeWithFallback(
|
|
45659
|
-
node,
|
|
45660
|
-
proxyPath,
|
|
45661
|
-
{
|
|
45662
|
-
method: req.method,
|
|
45663
|
-
headers: buildPreviewWorkerProxyHeaders(req),
|
|
45664
|
-
...body !== void 0 ? { body } : {}
|
|
45665
|
-
},
|
|
45666
|
-
PREVIEW_ASSET_PROXY_TIMEOUT_MS,
|
|
45667
|
-
createPreviewAssetProxyTrace(previewLog, previewRequest.token, session.nodeId, requestPath, proxyPath),
|
|
45668
|
-
{ preferPublicEndpoint: true }
|
|
45669
|
-
);
|
|
45670
|
-
previewLog.debug("proxying preview asset from assigned worker", {
|
|
45671
|
-
token: previewRequest.token,
|
|
45672
|
-
nodeId: session.nodeId,
|
|
45673
|
-
requestPath,
|
|
45674
|
-
proxyPath,
|
|
45675
|
-
endpoint
|
|
45676
|
-
});
|
|
45677
|
-
await sendProxyResponse(res, response);
|
|
45678
|
-
return true;
|
|
45679
|
-
} catch (err) {
|
|
45680
|
-
previewLog.warn("preview asset proxy error", {
|
|
45681
|
-
token: previewRequest.token,
|
|
45682
|
-
nodeId: session.nodeId,
|
|
45683
|
-
requestPath,
|
|
45684
|
-
proxyPath,
|
|
45685
|
-
timeoutMs: PREVIEW_ASSET_PROXY_TIMEOUT_MS,
|
|
45686
|
-
...describeProxyError(err)
|
|
45687
|
-
});
|
|
45688
|
-
throw new MeshyError("NODE_OFFLINE", "Cannot reach worker for preview asset", 502);
|
|
45483
|
+
const cancelled = deps.taskEngine.cancelTask(taskId);
|
|
45484
|
+
if (!cancelled) {
|
|
45485
|
+
throw new MeshyError("VALIDATION_ERROR", `Task ${taskId} cannot be cancelled`, 400);
|
|
45689
45486
|
}
|
|
45487
|
+
return {
|
|
45488
|
+
task,
|
|
45489
|
+
cancelled: true,
|
|
45490
|
+
engineCancelRequested,
|
|
45491
|
+
missing: false,
|
|
45492
|
+
terminal: false
|
|
45493
|
+
};
|
|
45690
45494
|
}
|
|
45691
45495
|
|
|
45692
|
-
// ../../packages/api/src/tasks/task-output-service.ts
|
|
45693
|
-
var
|
|
45694
|
-
|
|
45695
|
-
|
|
45696
|
-
|
|
45697
|
-
|
|
45698
|
-
|
|
45699
|
-
|
|
45496
|
+
// ../../packages/api/src/tasks/task-output-service.ts
|
|
45497
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
45498
|
+
var path19 = __toESM(require("path"), 1);
|
|
45499
|
+
|
|
45500
|
+
// ../../packages/api/src/preview/preview-server.ts
|
|
45501
|
+
var crypto3 = __toESM(require("crypto"), 1);
|
|
45502
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
45503
|
+
var path18 = __toESM(require("path"), 1);
|
|
45504
|
+
var http2 = __toESM(require("http"), 1);
|
|
45505
|
+
var import_node_stream3 = require("stream");
|
|
45506
|
+
var import_promises6 = require("stream/promises");
|
|
45507
|
+
|
|
45508
|
+
// ../../packages/api/src/preview/preview-request.ts
|
|
45509
|
+
var PREVIEW_AUTHORIZATION_HEADER = "x-meshy-preview-authorization";
|
|
45510
|
+
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
45511
|
+
"connection",
|
|
45512
|
+
"keep-alive",
|
|
45513
|
+
"proxy-authenticate",
|
|
45514
|
+
"proxy-authorization",
|
|
45515
|
+
"te",
|
|
45516
|
+
"trailer",
|
|
45517
|
+
"transfer-encoding",
|
|
45518
|
+
"upgrade"
|
|
45519
|
+
]);
|
|
45520
|
+
function isReadOnlyPreviewMethod(method) {
|
|
45521
|
+
const normalizedMethod = method?.toUpperCase() ?? "GET";
|
|
45522
|
+
return normalizedMethod === "GET" || normalizedMethod === "HEAD";
|
|
45700
45523
|
}
|
|
45701
|
-
function
|
|
45702
|
-
|
|
45703
|
-
if (!task.effectiveProjectPath) {
|
|
45704
|
-
throw new MeshyError("VALIDATION_ERROR", "Task output not available", 400);
|
|
45705
|
-
}
|
|
45706
|
-
return task.effectiveProjectPath;
|
|
45524
|
+
function methodAllowsRequestBody(method) {
|
|
45525
|
+
return !isReadOnlyPreviewMethod(method);
|
|
45707
45526
|
}
|
|
45708
|
-
function
|
|
45709
|
-
|
|
45527
|
+
function toArrayBuffer(value) {
|
|
45528
|
+
const copy = new Uint8Array(value.byteLength);
|
|
45529
|
+
copy.set(value);
|
|
45530
|
+
return copy.buffer;
|
|
45710
45531
|
}
|
|
45711
|
-
function
|
|
45712
|
-
const
|
|
45713
|
-
if (
|
|
45714
|
-
return
|
|
45715
|
-
taskId: task.id,
|
|
45716
|
-
requestedProject: task.project,
|
|
45717
|
-
effectiveProjectPath: null,
|
|
45718
|
-
assignedTo: task.assignedTo,
|
|
45719
|
-
available: false,
|
|
45720
|
-
summary: null
|
|
45721
|
-
};
|
|
45532
|
+
function getParsedRequestBody(req) {
|
|
45533
|
+
const body = req.body;
|
|
45534
|
+
if (body === void 0) {
|
|
45535
|
+
return void 0;
|
|
45722
45536
|
}
|
|
45723
|
-
|
|
45724
|
-
|
|
45725
|
-
|
|
45726
|
-
|
|
45727
|
-
|
|
45728
|
-
|
|
45729
|
-
|
|
45730
|
-
|
|
45537
|
+
if (typeof body === "string" || body instanceof URLSearchParams || body instanceof Blob || body instanceof FormData) {
|
|
45538
|
+
return body;
|
|
45539
|
+
}
|
|
45540
|
+
if (Buffer.isBuffer(body)) {
|
|
45541
|
+
return toArrayBuffer(body);
|
|
45542
|
+
}
|
|
45543
|
+
if (body instanceof Uint8Array) {
|
|
45544
|
+
return toArrayBuffer(body);
|
|
45545
|
+
}
|
|
45546
|
+
return JSON.stringify(body);
|
|
45731
45547
|
}
|
|
45732
|
-
function
|
|
45733
|
-
|
|
45734
|
-
|
|
45735
|
-
|
|
45736
|
-
|
|
45737
|
-
|
|
45738
|
-
|
|
45739
|
-
|
|
45740
|
-
|
|
45741
|
-
|
|
45742
|
-
|
|
45743
|
-
}
|
|
45744
|
-
throw err;
|
|
45548
|
+
async function readPreviewRequestBody(req) {
|
|
45549
|
+
if (!req || !methodAllowsRequestBody(req.method)) {
|
|
45550
|
+
return void 0;
|
|
45551
|
+
}
|
|
45552
|
+
const parsedBody = getParsedRequestBody(req);
|
|
45553
|
+
if (parsedBody !== void 0) {
|
|
45554
|
+
return parsedBody;
|
|
45555
|
+
}
|
|
45556
|
+
const chunks = [];
|
|
45557
|
+
for await (const chunk of req) {
|
|
45558
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
45745
45559
|
}
|
|
45560
|
+
return chunks.length > 0 ? toArrayBuffer(Buffer.concat(chunks)) : void 0;
|
|
45746
45561
|
}
|
|
45747
|
-
function
|
|
45748
|
-
const
|
|
45749
|
-
|
|
45750
|
-
return
|
|
45751
|
-
|
|
45752
|
-
|
|
45753
|
-
|
|
45754
|
-
|
|
45755
|
-
if (
|
|
45756
|
-
|
|
45562
|
+
function buildServiceProxyHeaders(req) {
|
|
45563
|
+
const headers = {};
|
|
45564
|
+
if (!req) {
|
|
45565
|
+
return headers;
|
|
45566
|
+
}
|
|
45567
|
+
let previewAuthorization;
|
|
45568
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
45569
|
+
const normalizedKey = key.toLowerCase();
|
|
45570
|
+
if (HOP_BY_HOP_HEADERS.has(normalizedKey) || normalizedKey === "host" || normalizedKey === "content-length" || normalizedKey === "forwarded" || normalizedKey === "x-real-ip" || normalizedKey.startsWith("x-forwarded-") || normalizedKey === MESHY_AUTHORIZATION_HEADER) {
|
|
45571
|
+
continue;
|
|
45757
45572
|
}
|
|
45758
|
-
if (
|
|
45759
|
-
|
|
45573
|
+
if (normalizedKey === PREVIEW_AUTHORIZATION_HEADER) {
|
|
45574
|
+
previewAuthorization = Array.isArray(value) ? value[0] : value;
|
|
45575
|
+
continue;
|
|
45760
45576
|
}
|
|
45761
|
-
|
|
45577
|
+
if (Array.isArray(value)) {
|
|
45578
|
+
headers[key] = value.join(", ");
|
|
45579
|
+
} else if (typeof value === "string") {
|
|
45580
|
+
headers[key] = value;
|
|
45581
|
+
}
|
|
45582
|
+
}
|
|
45583
|
+
if (previewAuthorization && !headers.authorization) {
|
|
45584
|
+
headers.authorization = previewAuthorization;
|
|
45762
45585
|
}
|
|
45586
|
+
return headers;
|
|
45763
45587
|
}
|
|
45764
|
-
function
|
|
45765
|
-
const
|
|
45766
|
-
|
|
45767
|
-
|
|
45768
|
-
|
|
45769
|
-
|
|
45588
|
+
function buildPreviewWorkerProxyHeaders(req) {
|
|
45589
|
+
const headers = {};
|
|
45590
|
+
let previewAuthorization;
|
|
45591
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
45592
|
+
const normalizedKey = key.toLowerCase();
|
|
45593
|
+
if (HOP_BY_HOP_HEADERS.has(normalizedKey) || normalizedKey === "host" || normalizedKey === "content-length" || normalizedKey === MESHY_AUTHORIZATION_HEADER || normalizedKey === PREVIEW_AUTHORIZATION_HEADER) {
|
|
45594
|
+
continue;
|
|
45770
45595
|
}
|
|
45771
|
-
|
|
45772
|
-
|
|
45773
|
-
|
|
45774
|
-
content: fs17.readFileSync(absolutePath),
|
|
45775
|
-
headers: {
|
|
45776
|
-
"Content-Type": mimeType,
|
|
45777
|
-
"Content-Disposition": `inline; filename="${fileName}"`,
|
|
45778
|
-
"Cache-Control": "no-cache"
|
|
45779
|
-
}
|
|
45780
|
-
};
|
|
45781
|
-
} catch (err) {
|
|
45782
|
-
if (err instanceof MeshyError) {
|
|
45783
|
-
throw err;
|
|
45596
|
+
if (normalizedKey === "authorization") {
|
|
45597
|
+
previewAuthorization = Array.isArray(value) ? value[0] : value;
|
|
45598
|
+
continue;
|
|
45784
45599
|
}
|
|
45785
|
-
if (
|
|
45786
|
-
|
|
45600
|
+
if (Array.isArray(value)) {
|
|
45601
|
+
headers[key] = value.join(", ");
|
|
45602
|
+
} else if (typeof value === "string") {
|
|
45603
|
+
headers[key] = value;
|
|
45787
45604
|
}
|
|
45788
|
-
throw err;
|
|
45789
|
-
}
|
|
45790
|
-
}
|
|
45791
|
-
function getLocalTaskOutputDiff(taskEngine, taskId) {
|
|
45792
|
-
const rootPath = getTaskOutputRoot(taskEngine, taskId);
|
|
45793
|
-
return getGitDiff(rootPath);
|
|
45794
|
-
}
|
|
45795
|
-
async function createPreviewSessionPayload(deps, taskId, optionsOrPath, requestOrigin) {
|
|
45796
|
-
const previewManager = deps.previewSessionManager;
|
|
45797
|
-
if (!previewManager) {
|
|
45798
|
-
throw new MeshyError("VALIDATION_ERROR", "Preview not available on this node", 400);
|
|
45799
45605
|
}
|
|
45800
|
-
|
|
45801
|
-
|
|
45802
|
-
const session = options.port !== void 0 ? previewManager.create({
|
|
45803
|
-
taskId,
|
|
45804
|
-
port: options.port,
|
|
45805
|
-
entryPath: options.path
|
|
45806
|
-
}) : previewManager.create({
|
|
45807
|
-
taskId,
|
|
45808
|
-
rootPath: getTaskOutputRoot(deps.taskEngine, taskId),
|
|
45809
|
-
entryPath: options.path
|
|
45810
|
-
});
|
|
45811
|
-
const origin = requestOrigin ?? deps.dashboardOrigin ?? deps.localDashboardOrigin;
|
|
45812
|
-
if (!origin) {
|
|
45813
|
-
throw new MeshyError("VALIDATION_ERROR", "Preview origin not available", 502);
|
|
45606
|
+
if (previewAuthorization) {
|
|
45607
|
+
headers[PREVIEW_AUTHORIZATION_HEADER] = previewAuthorization;
|
|
45814
45608
|
}
|
|
45815
|
-
return
|
|
45816
|
-
previewUrl: buildPreviewUrl(origin, session),
|
|
45817
|
-
...session.kind === "service" ? { openUrl: buildPreviewOpenUrl(origin, session) } : {},
|
|
45818
|
-
expiresAt: session.expiresAt,
|
|
45819
|
-
entryPath: session.entryPath,
|
|
45820
|
-
kind: session.kind,
|
|
45821
|
-
...session.kind === "service" ? { port: session.port } : {}
|
|
45822
|
-
};
|
|
45609
|
+
return headers;
|
|
45823
45610
|
}
|
|
45824
45611
|
|
|
45825
|
-
// ../../packages/api/src/
|
|
45826
|
-
|
|
45827
|
-
|
|
45828
|
-
|
|
45829
|
-
|
|
45830
|
-
|
|
45831
|
-
|
|
45832
|
-
{ id: "claude", label: "Claude Code", command: "claude", packageName: "@anthropic-ai/claude-code" },
|
|
45833
|
-
{ id: "codex", label: "Codex", command: "codex", packageName: "@openai/codex" }
|
|
45834
|
-
];
|
|
45835
|
-
var runtimeToolUpdateCache = /* @__PURE__ */ new Map();
|
|
45836
|
-
function clearRuntimeToolUpdateCache(packageName) {
|
|
45837
|
-
if (packageName) {
|
|
45838
|
-
runtimeToolUpdateCache.delete(packageName);
|
|
45839
|
-
return;
|
|
45840
|
-
}
|
|
45841
|
-
runtimeToolUpdateCache.clear();
|
|
45842
|
-
}
|
|
45843
|
-
function runRuntimeToolCommand(command, args, platform2) {
|
|
45844
|
-
const result = (0, import_node_child_process8.spawnSync)(command, args, {
|
|
45845
|
-
encoding: "utf-8",
|
|
45846
|
-
shell: platform2 === "win32" && command !== "where",
|
|
45847
|
-
windowsHide: true,
|
|
45848
|
-
timeout: 2500
|
|
45849
|
-
});
|
|
45850
|
-
return {
|
|
45851
|
-
status: result.status,
|
|
45852
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
45853
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
45854
|
-
error: result.error?.message ?? null
|
|
45855
|
-
};
|
|
45856
|
-
}
|
|
45857
|
-
function normalizeOutput(output) {
|
|
45858
|
-
if (!output) {
|
|
45859
|
-
return null;
|
|
45860
|
-
}
|
|
45861
|
-
const firstLine = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
45862
|
-
return firstLine ?? null;
|
|
45863
|
-
}
|
|
45864
|
-
function formatLocalDate(date) {
|
|
45865
|
-
const year = date.getFullYear();
|
|
45866
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
45867
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
45868
|
-
return `${year}-${month}-${day}`;
|
|
45869
|
-
}
|
|
45870
|
-
function resolveCommandPath(command, commandRunner, platform2) {
|
|
45871
|
-
const lookupCommand = platform2 === "win32" ? "where" : "which";
|
|
45872
|
-
const result = commandRunner(lookupCommand, [command]);
|
|
45873
|
-
if (result.status === 0) {
|
|
45874
|
-
return { path: normalizeOutput(result.stdout) };
|
|
45612
|
+
// ../../packages/api/src/preview/preview-server.ts
|
|
45613
|
+
function resolvePreviewPath(rootPath, relativePath) {
|
|
45614
|
+
const sanitizedPath = relativePath.replace(/\\/g, "/");
|
|
45615
|
+
const resolvedPath = path18.resolve(rootPath, sanitizedPath);
|
|
45616
|
+
const normalizedRoot = path18.resolve(rootPath);
|
|
45617
|
+
if (!resolvedPath.startsWith(normalizedRoot + path18.sep) && resolvedPath !== normalizedRoot) {
|
|
45618
|
+
throw new Error("Invalid preview path");
|
|
45875
45619
|
}
|
|
45876
45620
|
return {
|
|
45877
|
-
|
|
45878
|
-
|
|
45621
|
+
absolutePath: resolvedPath,
|
|
45622
|
+
normalizedPath: path18.relative(normalizedRoot, resolvedPath).split(path18.sep).join("/")
|
|
45879
45623
|
};
|
|
45880
45624
|
}
|
|
45881
|
-
function
|
|
45882
|
-
|
|
45625
|
+
function resolvePreviewEntryPath(rootPath, entryPath) {
|
|
45626
|
+
const { absolutePath, normalizedPath } = resolvePreviewPath(rootPath, entryPath ?? "index.html");
|
|
45627
|
+
if (!fs17.existsSync(absolutePath) || !fs17.statSync(absolutePath).isFile()) {
|
|
45628
|
+
throw new Error("Preview entry not found");
|
|
45629
|
+
}
|
|
45630
|
+
return normalizedPath;
|
|
45883
45631
|
}
|
|
45884
|
-
function
|
|
45885
|
-
if (!
|
|
45886
|
-
|
|
45887
|
-
try {
|
|
45888
|
-
const parsed = JSON.parse(trimmed);
|
|
45889
|
-
if (typeof parsed === "string") {
|
|
45890
|
-
return parsed.trim() || null;
|
|
45891
|
-
}
|
|
45892
|
-
} catch {
|
|
45632
|
+
function normalizeServiceEntryPath(entryPath) {
|
|
45633
|
+
if (!entryPath || entryPath === "/") {
|
|
45634
|
+
return "";
|
|
45893
45635
|
}
|
|
45894
|
-
return
|
|
45636
|
+
return entryPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
45895
45637
|
}
|
|
45896
|
-
function
|
|
45897
|
-
if (!
|
|
45898
|
-
|
|
45899
|
-
|
|
45900
|
-
|
|
45901
|
-
|
|
45902
|
-
|
|
45903
|
-
|
|
45638
|
+
function assertPreviewServicePort(port) {
|
|
45639
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
45640
|
+
throw new Error("Invalid preview service port");
|
|
45641
|
+
}
|
|
45642
|
+
return port;
|
|
45643
|
+
}
|
|
45644
|
+
function buildPreviewUrl(origin, session) {
|
|
45645
|
+
return buildPreviewRouteUrl(origin, "preview", session);
|
|
45646
|
+
}
|
|
45647
|
+
function buildPreviewOpenUrl(origin, session) {
|
|
45648
|
+
return buildPreviewRouteUrl(origin, "preview-open", session);
|
|
45649
|
+
}
|
|
45650
|
+
function buildPreviewRouteUrl(origin, route, session) {
|
|
45651
|
+
const encodedEntryPath = session.entryPath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
45652
|
+
const suffix = encodedEntryPath.length > 0 ? `/${encodedEntryPath}` : session.kind === "service" ? "/" : "";
|
|
45653
|
+
return `${origin.replace(/\/+$/, "")}/${route}/${session.token}${suffix}`;
|
|
45654
|
+
}
|
|
45655
|
+
var DEFAULT_TTL_MS = 15 * 60 * 1e3;
|
|
45656
|
+
var PreviewSessionManager = class {
|
|
45657
|
+
sessions = /* @__PURE__ */ new Map();
|
|
45658
|
+
create(options) {
|
|
45659
|
+
const token = crypto3.randomBytes(32).toString("hex");
|
|
45660
|
+
const expiresAt = Date.now() + (options.ttlMs ?? DEFAULT_TTL_MS);
|
|
45661
|
+
if (options.port !== void 0) {
|
|
45662
|
+
const session2 = {
|
|
45663
|
+
token,
|
|
45664
|
+
taskId: options.taskId,
|
|
45665
|
+
kind: "service",
|
|
45666
|
+
port: assertPreviewServicePort(options.port),
|
|
45667
|
+
entryPath: normalizeServiceEntryPath(options.entryPath),
|
|
45668
|
+
expiresAt
|
|
45669
|
+
};
|
|
45670
|
+
this.sessions.set(token, session2);
|
|
45671
|
+
return session2;
|
|
45904
45672
|
}
|
|
45905
|
-
|
|
45673
|
+
if (!options.rootPath) {
|
|
45674
|
+
throw new Error("Preview root path is required");
|
|
45675
|
+
}
|
|
45676
|
+
const entryPath = resolvePreviewEntryPath(options.rootPath, options.entryPath);
|
|
45677
|
+
const session = {
|
|
45678
|
+
token,
|
|
45679
|
+
taskId: options.taskId,
|
|
45680
|
+
kind: "file",
|
|
45681
|
+
rootPath: options.rootPath,
|
|
45682
|
+
entryPath,
|
|
45683
|
+
expiresAt
|
|
45684
|
+
};
|
|
45685
|
+
this.sessions.set(token, session);
|
|
45686
|
+
return session;
|
|
45906
45687
|
}
|
|
45907
|
-
|
|
45688
|
+
get(token) {
|
|
45689
|
+
const session = this.sessions.get(token);
|
|
45690
|
+
if (!session) return null;
|
|
45691
|
+
if (Date.now() > session.expiresAt) {
|
|
45692
|
+
this.sessions.delete(token);
|
|
45693
|
+
return null;
|
|
45694
|
+
}
|
|
45695
|
+
return session;
|
|
45696
|
+
}
|
|
45697
|
+
cleanup() {
|
|
45698
|
+
const now = Date.now();
|
|
45699
|
+
for (const [token, session] of this.sessions) {
|
|
45700
|
+
if (now > session.expiresAt) {
|
|
45701
|
+
this.sessions.delete(token);
|
|
45702
|
+
}
|
|
45703
|
+
}
|
|
45704
|
+
}
|
|
45705
|
+
};
|
|
45706
|
+
var MIME_MAP2 = {
|
|
45707
|
+
".html": "text/html",
|
|
45708
|
+
".htm": "text/html",
|
|
45709
|
+
".css": "text/css",
|
|
45710
|
+
".js": "text/javascript",
|
|
45711
|
+
".mjs": "text/javascript",
|
|
45712
|
+
".json": "application/json",
|
|
45713
|
+
".png": "image/png",
|
|
45714
|
+
".jpg": "image/jpeg",
|
|
45715
|
+
".jpeg": "image/jpeg",
|
|
45716
|
+
".gif": "image/gif",
|
|
45717
|
+
".svg": "image/svg+xml",
|
|
45718
|
+
".webp": "image/webp",
|
|
45719
|
+
".ico": "image/x-icon",
|
|
45720
|
+
".woff": "font/woff",
|
|
45721
|
+
".woff2": "font/woff2",
|
|
45722
|
+
".ttf": "font/ttf",
|
|
45723
|
+
".eot": "application/vnd.ms-fontobject",
|
|
45724
|
+
".pdf": "application/pdf",
|
|
45725
|
+
".md": "text/markdown",
|
|
45726
|
+
".mdx": "text/markdown"
|
|
45727
|
+
};
|
|
45728
|
+
function getMime(filePath) {
|
|
45729
|
+
return MIME_MAP2[path18.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
45908
45730
|
}
|
|
45909
|
-
function
|
|
45910
|
-
|
|
45911
|
-
return normalizeInstalledPackageVersion(result.stdout ?? null, definition.packageName);
|
|
45731
|
+
function escapeHtml(value) {
|
|
45732
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
45912
45733
|
}
|
|
45913
|
-
function
|
|
45914
|
-
|
|
45915
|
-
|
|
45916
|
-
|
|
45917
|
-
|
|
45918
|
-
|
|
45734
|
+
function renderInlineMarkdown(value) {
|
|
45735
|
+
let rendered = escapeHtml(value);
|
|
45736
|
+
rendered = rendered.replace(/!\[([^\]]*)]\(([^\s)]+)\)/g, '<img alt="$1" src="$2">');
|
|
45737
|
+
rendered = rendered.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\[([^\]]+)]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" rel="noreferrer noopener" target="_blank">$1</a>');
|
|
45738
|
+
return rendered;
|
|
45739
|
+
}
|
|
45740
|
+
function renderMarkdownBody(markdown) {
|
|
45741
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
45742
|
+
const blocks = [];
|
|
45743
|
+
let paragraph = [];
|
|
45744
|
+
let listItems = [];
|
|
45745
|
+
let codeLines = null;
|
|
45746
|
+
const flushParagraph = () => {
|
|
45747
|
+
if (paragraph.length === 0) return;
|
|
45748
|
+
blocks.push(`<p>${renderInlineMarkdown(paragraph.join(" "))}</p>`);
|
|
45749
|
+
paragraph = [];
|
|
45750
|
+
};
|
|
45751
|
+
const flushList = () => {
|
|
45752
|
+
if (listItems.length === 0) return;
|
|
45753
|
+
blocks.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</ul>`);
|
|
45754
|
+
listItems = [];
|
|
45755
|
+
};
|
|
45756
|
+
for (const line of lines) {
|
|
45757
|
+
if (line.trim().startsWith("```")) {
|
|
45758
|
+
if (codeLines) {
|
|
45759
|
+
blocks.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
|
|
45760
|
+
codeLines = null;
|
|
45761
|
+
} else {
|
|
45762
|
+
flushParagraph();
|
|
45763
|
+
flushList();
|
|
45764
|
+
codeLines = [];
|
|
45765
|
+
}
|
|
45766
|
+
continue;
|
|
45767
|
+
}
|
|
45768
|
+
if (codeLines) {
|
|
45769
|
+
codeLines.push(line);
|
|
45770
|
+
continue;
|
|
45771
|
+
}
|
|
45772
|
+
const trimmed = line.trim();
|
|
45773
|
+
if (!trimmed) {
|
|
45774
|
+
flushParagraph();
|
|
45775
|
+
flushList();
|
|
45776
|
+
continue;
|
|
45777
|
+
}
|
|
45778
|
+
const heading = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
45779
|
+
if (heading) {
|
|
45780
|
+
flushParagraph();
|
|
45781
|
+
flushList();
|
|
45782
|
+
const level = heading[1].length;
|
|
45783
|
+
blocks.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
|
|
45784
|
+
continue;
|
|
45785
|
+
}
|
|
45786
|
+
const listItem = trimmed.match(/^[-*]\s+(.+)$/);
|
|
45787
|
+
if (listItem) {
|
|
45788
|
+
flushParagraph();
|
|
45789
|
+
listItems.push(listItem[1]);
|
|
45790
|
+
continue;
|
|
45791
|
+
}
|
|
45792
|
+
flushList();
|
|
45793
|
+
paragraph.push(trimmed);
|
|
45919
45794
|
}
|
|
45920
|
-
|
|
45795
|
+
if (codeLines) {
|
|
45796
|
+
blocks.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
|
|
45797
|
+
}
|
|
45798
|
+
flushParagraph();
|
|
45799
|
+
flushList();
|
|
45800
|
+
return blocks.join("\n");
|
|
45921
45801
|
}
|
|
45922
|
-
function
|
|
45923
|
-
const
|
|
45924
|
-
|
|
45925
|
-
|
|
45926
|
-
|
|
45927
|
-
|
|
45928
|
-
|
|
45929
|
-
|
|
45930
|
-
|
|
45931
|
-
|
|
45932
|
-
|
|
45933
|
-
|
|
45934
|
-
|
|
45935
|
-
|
|
45936
|
-
|
|
45937
|
-
|
|
45938
|
-
}
|
|
45802
|
+
function renderMarkdownDocument(markdown, title) {
|
|
45803
|
+
const body = renderMarkdownBody(markdown);
|
|
45804
|
+
return Buffer.from(`<!doctype html>
|
|
45805
|
+
<html>
|
|
45806
|
+
<head>
|
|
45807
|
+
<meta charset="utf-8">
|
|
45808
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
45809
|
+
<title>${escapeHtml(title)}</title>
|
|
45810
|
+
<style>
|
|
45811
|
+
body { box-sizing: border-box; max-width: 860px; margin: 0 auto; padding: 32px; color: #111827; font: 16px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
45812
|
+
h1, h2, h3, h4, h5, h6 { line-height: 1.25; margin: 1.5em 0 0.5em; }
|
|
45813
|
+
h1 { border-bottom: 1px solid #e5e7eb; padding-bottom: 0.3em; }
|
|
45814
|
+
pre { overflow: auto; border-radius: 8px; background: #111827; color: #f9fafb; padding: 16px; }
|
|
45815
|
+
code { border-radius: 4px; background: #f3f4f6; padding: 0.1em 0.25em; }
|
|
45816
|
+
pre code { background: transparent; padding: 0; }
|
|
45817
|
+
img { max-width: 100%; }
|
|
45818
|
+
a { color: #2563eb; }
|
|
45819
|
+
</style>
|
|
45820
|
+
</head>
|
|
45821
|
+
<body>
|
|
45822
|
+
${body}
|
|
45823
|
+
</body>
|
|
45824
|
+
</html>`, "utf8");
|
|
45825
|
+
}
|
|
45826
|
+
function splitPreviewPathAndSearch(value) {
|
|
45827
|
+
const marker = value.indexOf("?");
|
|
45828
|
+
if (marker === -1) {
|
|
45829
|
+
return { pathname: value, search: "" };
|
|
45939
45830
|
}
|
|
45940
|
-
return {
|
|
45941
|
-
|
|
45942
|
-
|
|
45943
|
-
|
|
45944
|
-
updateAvailable: Boolean(currentVersion && latestVersion && compareSemver(latestVersion, currentVersion) > 0),
|
|
45945
|
-
checkedAt: updateCheckedAt,
|
|
45946
|
-
detail
|
|
45947
|
-
};
|
|
45831
|
+
return { pathname: value.slice(0, marker), search: value.slice(marker) };
|
|
45832
|
+
}
|
|
45833
|
+
function decodePreviewPathname(pathname) {
|
|
45834
|
+
return pathname ? decodeURIComponent(pathname) : "";
|
|
45948
45835
|
}
|
|
45949
|
-
function
|
|
45950
|
-
const
|
|
45951
|
-
if (!
|
|
45952
|
-
return
|
|
45953
|
-
id: definition.id,
|
|
45954
|
-
label: definition.label,
|
|
45955
|
-
command: definition.command,
|
|
45956
|
-
available: false,
|
|
45957
|
-
path: null,
|
|
45958
|
-
version: null,
|
|
45959
|
-
checkedAt,
|
|
45960
|
-
detail: resolved.detail ?? "Command not found on PATH",
|
|
45961
|
-
update: checkToolUpdate(definition, null, checkedAt, commandRunner, checkedOn, updateCache)
|
|
45962
|
-
};
|
|
45836
|
+
function normalizeServiceRequestPath(pathname) {
|
|
45837
|
+
const normalized = pathname.replace(/\\/g, "/");
|
|
45838
|
+
if (!normalized || normalized === "/") {
|
|
45839
|
+
return "/";
|
|
45963
45840
|
}
|
|
45964
|
-
|
|
45965
|
-
const commandVersion = normalizeOutput(versionResult.stdout) ?? normalizeOutput(versionResult.stderr);
|
|
45966
|
-
const version2 = extractSemver(commandVersion) ? commandVersion : resolveInstalledPackageVersion(definition, commandRunner);
|
|
45967
|
-
const detail = versionResult.status === 0 ? null : normalizeOutput(versionResult.stderr) ?? versionResult.error ?? "Version check failed";
|
|
45968
|
-
return {
|
|
45969
|
-
id: definition.id,
|
|
45970
|
-
label: definition.label,
|
|
45971
|
-
command: definition.command,
|
|
45972
|
-
available: true,
|
|
45973
|
-
path: resolved.path,
|
|
45974
|
-
version: version2,
|
|
45975
|
-
checkedAt,
|
|
45976
|
-
detail,
|
|
45977
|
-
update: checkToolUpdate(definition, version2, checkedAt, commandRunner, checkedOn, updateCache)
|
|
45978
|
-
};
|
|
45841
|
+
return `/${normalized.replace(/^\/+/, "")}`;
|
|
45979
45842
|
}
|
|
45980
|
-
function
|
|
45981
|
-
const
|
|
45982
|
-
|
|
45983
|
-
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
45984
|
-
const checkedAt = now.toISOString();
|
|
45985
|
-
const checkedOn = formatLocalDate(now);
|
|
45986
|
-
const updateCache = options.updateCache ?? runtimeToolUpdateCache;
|
|
45987
|
-
return RUNTIME_TOOLS.map((definition) => inspectTool(definition, commandRunner, checkedAt, checkedOn, updateCache, platform2));
|
|
45843
|
+
function rewriteServiceRootRelativeUrls(value, token) {
|
|
45844
|
+
const previewRoot = `/preview/${token}/`;
|
|
45845
|
+
return value.replace(/\b(src|href|action)=(['"])\/(?!\/|preview\/)/gi, `$1=$2${previewRoot}`).replace(/url\((['"]?)\/(?!\/|preview\/)/gi, `url($1${previewRoot}`).replace(/\bimport\((['"])\/(?!\/|preview\/)/gi, `import($1${previewRoot}`).replace(/\bfrom\s+(['"])\/(?!\/|preview\/)/gi, `from $1${previewRoot}`).replace(/(['"])\/(assets\/[^'"]+)\1/g, `$1${previewRoot}$2$1`);
|
|
45988
45846
|
}
|
|
45989
|
-
function
|
|
45990
|
-
|
|
45991
|
-
return {
|
|
45992
|
-
platform: os6.platform(),
|
|
45993
|
-
release: os6.release(),
|
|
45994
|
-
arch: os6.arch(),
|
|
45995
|
-
hostname: os6.hostname(),
|
|
45996
|
-
cpuModel: cpus2[0]?.model ?? null,
|
|
45997
|
-
cpuCount: cpus2.length,
|
|
45998
|
-
totalMemoryBytes: os6.totalmem(),
|
|
45999
|
-
freeMemoryBytes: os6.freemem()
|
|
46000
|
-
};
|
|
45847
|
+
function escapeHtmlAttribute(value) {
|
|
45848
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
46001
45849
|
}
|
|
46002
|
-
function
|
|
46003
|
-
const
|
|
46004
|
-
const
|
|
46005
|
-
const
|
|
46006
|
-
|
|
46007
|
-
|
|
46008
|
-
|
|
46009
|
-
|
|
46010
|
-
|
|
46011
|
-
|
|
46012
|
-
|
|
46013
|
-
|
|
46014
|
-
|
|
46015
|
-
|
|
46016
|
-
|
|
46017
|
-
|
|
46018
|
-
|
|
46019
|
-
|
|
46020
|
-
|
|
46021
|
-
|
|
46022
|
-
|
|
46023
|
-
|
|
46024
|
-
|
|
46025
|
-
|
|
46026
|
-
|
|
46027
|
-
|
|
46028
|
-
|
|
46029
|
-
|
|
46030
|
-
},
|
|
46031
|
-
|
|
46032
|
-
|
|
46033
|
-
|
|
46034
|
-
|
|
45850
|
+
function buildServicePreviewBridge(token) {
|
|
45851
|
+
const previewRoot = `/preview/${token}/`;
|
|
45852
|
+
const previewBase = previewRoot.replace(/\/$/, "");
|
|
45853
|
+
const script = [
|
|
45854
|
+
"(()=>{",
|
|
45855
|
+
"if(window.top===window)return;",
|
|
45856
|
+
`const previewBase=${JSON.stringify(previewBase)};`,
|
|
45857
|
+
`const previewRoot=${JSON.stringify(previewRoot)};`,
|
|
45858
|
+
"const rewritePreviewRequestUrl=(value)=>{",
|
|
45859
|
+
"if(value==null)return value;",
|
|
45860
|
+
"let raw;",
|
|
45861
|
+
'try{raw=value instanceof URL?value.href:typeof value==="string"?value:value&&typeof value.url==="string"?value.url:undefined;}catch{return value;}',
|
|
45862
|
+
"if(!raw)return value;",
|
|
45863
|
+
"let url;",
|
|
45864
|
+
"try{url=new URL(raw,window.location.href);}catch{return value;}",
|
|
45865
|
+
"if(url.origin!==window.location.origin)return value;",
|
|
45866
|
+
'if(url.pathname===previewBase||url.pathname.startsWith(previewRoot)||url.pathname.startsWith("/preview-open/"))return value;',
|
|
45867
|
+
'return previewRoot+url.pathname.replace(/^\\/+/,"")+url.search+url.hash;',
|
|
45868
|
+
"};",
|
|
45869
|
+
"const rewritePreviewRequestInput=(input)=>{",
|
|
45870
|
+
"const next=rewritePreviewRequestUrl(input);",
|
|
45871
|
+
"if(next===input)return input;",
|
|
45872
|
+
'if(typeof Request==="function"&&input instanceof Request)return new Request(next,input);',
|
|
45873
|
+
"return next;",
|
|
45874
|
+
"};",
|
|
45875
|
+
"const originalFetch=window.fetch;",
|
|
45876
|
+
'if(typeof originalFetch==="function")window.fetch=(input,init)=>originalFetch.call(window,rewritePreviewRequestInput(input),init);',
|
|
45877
|
+
"const XHR=window.XMLHttpRequest;",
|
|
45878
|
+
"if(XHR&&XHR.prototype){const open=XHR.prototype.open;XMLHttpRequest.prototype.open=function(method,url,...rest){return open.call(this,method,rewritePreviewRequestUrl(url),...rest);};}",
|
|
45879
|
+
"const OriginalEventSource=window.EventSource;",
|
|
45880
|
+
'if(typeof OriginalEventSource==="function"){window.EventSource=function(url,config){return new OriginalEventSource(rewritePreviewRequestUrl(url),config);};window.EventSource.prototype=OriginalEventSource.prototype;}',
|
|
45881
|
+
"const currentPath=window.location.pathname;",
|
|
45882
|
+
"if(currentPath===previewBase||currentPath.startsWith(previewRoot)){",
|
|
45883
|
+
"const suffix=currentPath===previewBase?'':currentPath.slice(previewRoot.length);",
|
|
45884
|
+
"const servicePath=suffix?'/' + suffix:'/';",
|
|
45885
|
+
"window.history.replaceState(window.history.state,'',servicePath+window.location.search+window.location.hash);",
|
|
45886
|
+
"}",
|
|
45887
|
+
"})();"
|
|
45888
|
+
].join("");
|
|
45889
|
+
return `<base data-meshy-preview-base href="${escapeHtmlAttribute(previewRoot)}"><script data-meshy-preview-bridge>${script}</script>`;
|
|
46035
45890
|
}
|
|
46036
|
-
|
|
46037
|
-
|
|
46038
|
-
|
|
46039
|
-
|
|
46040
|
-
|
|
46041
|
-
}
|
|
46042
|
-
|
|
46043
|
-
|
|
46044
|
-
|
|
46045
|
-
|
|
45891
|
+
function injectServicePreviewBridge(value, token) {
|
|
45892
|
+
const bridge = buildServicePreviewBridge(token);
|
|
45893
|
+
const headPattern = /<head(?:\s[^>]*)?>/i;
|
|
45894
|
+
if (headPattern.test(value)) {
|
|
45895
|
+
return value.replace(headPattern, (match) => `${match}${bridge}`);
|
|
45896
|
+
}
|
|
45897
|
+
const htmlPattern = /<html(?:\s[^>]*)?>/i;
|
|
45898
|
+
if (htmlPattern.test(value)) {
|
|
45899
|
+
return value.replace(htmlPattern, (match) => `${match}<head>${bridge}</head>`);
|
|
45900
|
+
}
|
|
45901
|
+
return `${bridge}${value}`;
|
|
46046
45902
|
}
|
|
46047
|
-
function
|
|
46048
|
-
|
|
46049
|
-
encoding: "utf-8",
|
|
46050
|
-
shell: process.platform === "win32",
|
|
46051
|
-
windowsHide: true,
|
|
46052
|
-
timeout: 18e4
|
|
46053
|
-
});
|
|
46054
|
-
return {
|
|
46055
|
-
status: result.status,
|
|
46056
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
46057
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
46058
|
-
error: result.error?.message ?? null
|
|
46059
|
-
};
|
|
45903
|
+
function normalizeContentType(contentType) {
|
|
45904
|
+
return contentType?.split(";")[0]?.trim().toLowerCase();
|
|
46060
45905
|
}
|
|
46061
|
-
function
|
|
46062
|
-
return
|
|
45906
|
+
function isServiceHtmlContentType(contentType) {
|
|
45907
|
+
return normalizeContentType(contentType) === "text/html";
|
|
46063
45908
|
}
|
|
46064
|
-
function
|
|
46065
|
-
|
|
46066
|
-
|
|
46067
|
-
|
|
45909
|
+
function isServiceEventStreamContentType(contentType) {
|
|
45910
|
+
return normalizeContentType(contentType) === "text/event-stream";
|
|
45911
|
+
}
|
|
45912
|
+
function isServiceJavaScriptContentType(normalizedType) {
|
|
45913
|
+
return normalizedType === "text/javascript" || normalizedType === "application/javascript" || normalizedType === "application/x-javascript";
|
|
45914
|
+
}
|
|
45915
|
+
function rewriteServiceJavaScriptRuntimeUrls(value, token) {
|
|
45916
|
+
if (!value.includes("__vite__mapDeps")) {
|
|
45917
|
+
return value;
|
|
46068
45918
|
}
|
|
46069
|
-
|
|
45919
|
+
const previewRoot = `/preview/${token}/`;
|
|
45920
|
+
return value.replace(
|
|
45921
|
+
/return\s*(['"])\/\1\s*\+\s*([A-Za-z_$][\w$]*)/g,
|
|
45922
|
+
(_match, quote, argumentName) => `return${quote}${previewRoot}${quote}+${argumentName}`
|
|
45923
|
+
);
|
|
46070
45924
|
}
|
|
46071
|
-
function
|
|
46072
|
-
const
|
|
46073
|
-
if (!
|
|
46074
|
-
|
|
45925
|
+
function rewriteServiceResponseContent(content, contentType, token) {
|
|
45926
|
+
const normalizedType = normalizeContentType(contentType);
|
|
45927
|
+
if (normalizedType !== "text/html" && normalizedType !== "text/css" && !isServiceJavaScriptContentType(normalizedType)) {
|
|
45928
|
+
return content;
|
|
46075
45929
|
}
|
|
46076
|
-
const
|
|
46077
|
-
const
|
|
46078
|
-
const
|
|
46079
|
-
|
|
46080
|
-
|
|
46081
|
-
|
|
45930
|
+
const source = content.toString("utf8");
|
|
45931
|
+
const bridged = normalizedType === "text/html" ? injectServicePreviewBridge(source, token) : source;
|
|
45932
|
+
const rewritten = rewriteServiceRootRelativeUrls(bridged, token);
|
|
45933
|
+
return Buffer.from(
|
|
45934
|
+
isServiceJavaScriptContentType(normalizedType) ? rewriteServiceJavaScriptRuntimeUrls(rewritten, token) : rewritten,
|
|
45935
|
+
"utf8"
|
|
45936
|
+
);
|
|
45937
|
+
}
|
|
45938
|
+
function rewriteServiceRedirectLocation(value, session) {
|
|
45939
|
+
const previewRoot = `/preview/${session.token}`;
|
|
45940
|
+
if (value.startsWith("/preview/")) {
|
|
45941
|
+
return value;
|
|
46082
45942
|
}
|
|
46083
|
-
|
|
46084
|
-
|
|
46085
|
-
|
|
46086
|
-
|
|
46087
|
-
|
|
46088
|
-
|
|
46089
|
-
|
|
46090
|
-
|
|
46091
|
-
|
|
46092
|
-
|
|
46093
|
-
|
|
46094
|
-
|
|
45943
|
+
if (value.startsWith("/")) {
|
|
45944
|
+
return `${previewRoot}${value}`;
|
|
45945
|
+
}
|
|
45946
|
+
try {
|
|
45947
|
+
const url = new URL(value);
|
|
45948
|
+
const isLocalService = (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]") && url.port === String(session.port);
|
|
45949
|
+
if (isLocalService) {
|
|
45950
|
+
return `${previewRoot}${url.pathname}${url.search}${url.hash}`;
|
|
45951
|
+
}
|
|
45952
|
+
} catch {
|
|
45953
|
+
}
|
|
45954
|
+
return value;
|
|
45955
|
+
}
|
|
45956
|
+
function buildServiceResponseHeaders(upstream, session, isHtml) {
|
|
45957
|
+
const headers = {};
|
|
45958
|
+
upstream.headers.forEach((value, key) => {
|
|
45959
|
+
const normalizedKey = key.toLowerCase();
|
|
45960
|
+
if (HOP_BY_HOP_HEADERS.has(normalizedKey) || normalizedKey === "content-length" || normalizedKey === "content-encoding" || isHtml && normalizedKey === "content-security-policy" || isHtml && normalizedKey === "content-security-policy-report-only" || isHtml && normalizedKey === "x-frame-options") {
|
|
45961
|
+
return;
|
|
45962
|
+
}
|
|
45963
|
+
if (normalizedKey === "location") {
|
|
45964
|
+
headers[key] = rewriteServiceRedirectLocation(value, session);
|
|
45965
|
+
return;
|
|
45966
|
+
}
|
|
45967
|
+
headers[key] = value;
|
|
45968
|
+
});
|
|
45969
|
+
if (typeof headers["cache-control"] !== "string") {
|
|
45970
|
+
headers["cache-control"] = "no-cache";
|
|
45971
|
+
}
|
|
45972
|
+
return headers;
|
|
46095
45973
|
}
|
|
46096
|
-
function
|
|
46097
|
-
|
|
46098
|
-
|
|
46099
|
-
|
|
46100
|
-
|
|
46101
|
-
|
|
46102
|
-
|
|
45974
|
+
async function pipeServiceResponseBody(upstream, res) {
|
|
45975
|
+
if (!upstream.body) {
|
|
45976
|
+
res.end();
|
|
45977
|
+
return;
|
|
45978
|
+
}
|
|
45979
|
+
try {
|
|
45980
|
+
await (0, import_promises6.pipeline)(import_node_stream3.Readable.fromWeb(upstream.body), res);
|
|
45981
|
+
} catch (error) {
|
|
45982
|
+
if (!res.destroyed && !res.writableEnded) {
|
|
45983
|
+
throw error;
|
|
46103
45984
|
}
|
|
46104
45985
|
}
|
|
46105
|
-
return result;
|
|
46106
45986
|
}
|
|
46107
|
-
|
|
46108
|
-
|
|
46109
|
-
|
|
46110
|
-
|
|
46111
|
-
|
|
46112
|
-
|
|
46113
|
-
|
|
46114
|
-
|
|
46115
|
-
|
|
45987
|
+
async function sendServicePreviewResponse(session, requestedPath, req, res) {
|
|
45988
|
+
const { pathname, search } = splitPreviewPathAndSearch(requestedPath ?? session.entryPath);
|
|
45989
|
+
const decodedPathname = decodePreviewPathname(pathname);
|
|
45990
|
+
const targetPath = normalizeServiceRequestPath(decodedPathname || session.entryPath);
|
|
45991
|
+
const targetUrl = `http://127.0.0.1:${session.port}${targetPath}${search}`;
|
|
45992
|
+
let upstream;
|
|
45993
|
+
try {
|
|
45994
|
+
const body = await readPreviewRequestBody(req);
|
|
45995
|
+
upstream = await fetch(targetUrl, {
|
|
45996
|
+
method: req?.method ?? "GET",
|
|
45997
|
+
headers: buildServiceProxyHeaders(req),
|
|
45998
|
+
...body !== void 0 ? { body } : {},
|
|
45999
|
+
redirect: "manual"
|
|
46000
|
+
});
|
|
46001
|
+
} catch {
|
|
46002
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
46003
|
+
res.end("Preview service unavailable");
|
|
46004
|
+
return;
|
|
46116
46005
|
}
|
|
46117
|
-
|
|
46118
|
-
|
|
46119
|
-
|
|
46120
|
-
|
|
46121
|
-
|
|
46122
|
-
|
|
46006
|
+
const upstreamContentType = upstream.headers.get("content-type");
|
|
46007
|
+
const isHtml = isServiceHtmlContentType(upstreamContentType);
|
|
46008
|
+
const headers = buildServiceResponseHeaders(upstream, session, isHtml);
|
|
46009
|
+
if (isServiceEventStreamContentType(upstreamContentType)) {
|
|
46010
|
+
res.writeHead(upstream.status, headers);
|
|
46011
|
+
await pipeServiceResponseBody(upstream, res);
|
|
46012
|
+
return;
|
|
46123
46013
|
}
|
|
46124
|
-
|
|
46125
|
-
|
|
46014
|
+
const content = rewriteServiceResponseContent(Buffer.from(await upstream.arrayBuffer()), upstreamContentType, session.token);
|
|
46015
|
+
headers["content-length"] = content.length;
|
|
46016
|
+
res.writeHead(upstream.status, headers);
|
|
46017
|
+
res.end(content);
|
|
46018
|
+
}
|
|
46019
|
+
function buildPreviewUrlForRequest(token, requestedPath, session) {
|
|
46020
|
+
const { pathname, search } = splitPreviewPathAndSearch(requestedPath ?? session.entryPath);
|
|
46021
|
+
const entryPath = pathname ? decodePreviewPathname(pathname) : session.entryPath;
|
|
46022
|
+
return `${buildPreviewUrl("", { token, entryPath, kind: session.kind })}${search}`;
|
|
46023
|
+
}
|
|
46024
|
+
function renderPreviewOpenDocument(previewUrl) {
|
|
46025
|
+
const safePreviewUrl = escapeHtmlAttribute(previewUrl);
|
|
46026
|
+
return Buffer.from(`<!doctype html>
|
|
46027
|
+
<html>
|
|
46028
|
+
<head>
|
|
46029
|
+
<meta charset="utf-8">
|
|
46030
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
46031
|
+
<title>Preview</title>
|
|
46032
|
+
<style>
|
|
46033
|
+
html, body { width: 100%; height: 100%; margin: 0; background: #fff; }
|
|
46034
|
+
iframe { display: block; width: 100%; height: 100%; border: 0; }
|
|
46035
|
+
</style>
|
|
46036
|
+
</head>
|
|
46037
|
+
<body>
|
|
46038
|
+
<iframe src="${safePreviewUrl}" title="Preview"></iframe>
|
|
46039
|
+
</body>
|
|
46040
|
+
</html>`, "utf8");
|
|
46041
|
+
}
|
|
46042
|
+
function sendPreviewOpenResponse(sessionManager, token, requestedPath, res) {
|
|
46043
|
+
const session = sessionManager.get(token);
|
|
46044
|
+
if (!session) {
|
|
46045
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
46046
|
+
res.end("Invalid or expired preview token");
|
|
46047
|
+
return;
|
|
46126
46048
|
}
|
|
46127
|
-
|
|
46128
|
-
|
|
46129
|
-
|
|
46130
|
-
|
|
46049
|
+
const content = renderPreviewOpenDocument(buildPreviewUrlForRequest(token, requestedPath, session));
|
|
46050
|
+
res.writeHead(200, {
|
|
46051
|
+
"Content-Type": "text/html",
|
|
46052
|
+
"Content-Length": content.length,
|
|
46053
|
+
"Cache-Control": "no-cache"
|
|
46054
|
+
});
|
|
46055
|
+
res.end(content);
|
|
46056
|
+
}
|
|
46057
|
+
async function sendPreviewAssetResponse(sessionManager, token, requestedPath, res, req) {
|
|
46058
|
+
const session = sessionManager.get(token);
|
|
46059
|
+
if (!session) {
|
|
46060
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
46061
|
+
res.end("Invalid or expired preview token");
|
|
46062
|
+
return;
|
|
46131
46063
|
}
|
|
46132
|
-
|
|
46133
|
-
|
|
46134
|
-
|
|
46135
|
-
const next = { ...current, ...clone2(updates), id, createdAt: current.createdAt };
|
|
46136
|
-
this.operations.set(id, next);
|
|
46137
|
-
return clone2(next);
|
|
46064
|
+
if (session.kind === "service") {
|
|
46065
|
+
await sendServicePreviewResponse(session, requestedPath, req, res);
|
|
46066
|
+
return;
|
|
46138
46067
|
}
|
|
46139
|
-
|
|
46140
|
-
|
|
46141
|
-
|
|
46142
|
-
|
|
46068
|
+
if (!isReadOnlyPreviewMethod(req?.method)) {
|
|
46069
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
46070
|
+
res.end("Method not allowed");
|
|
46071
|
+
return;
|
|
46143
46072
|
}
|
|
46144
|
-
|
|
46145
|
-
|
|
46146
|
-
|
|
46147
|
-
|
|
46148
|
-
|
|
46149
|
-
|
|
46073
|
+
const { pathname } = splitPreviewPathAndSearch(requestedPath ?? session.entryPath);
|
|
46074
|
+
const resolvedRequestedPath = pathname ? decodePreviewPathname(pathname) : session.entryPath;
|
|
46075
|
+
let resolved;
|
|
46076
|
+
try {
|
|
46077
|
+
resolved = resolvePreviewPath(session.rootPath, resolvedRequestedPath).absolutePath;
|
|
46078
|
+
} catch {
|
|
46079
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
46080
|
+
res.end("Invalid path");
|
|
46081
|
+
return;
|
|
46150
46082
|
}
|
|
46151
|
-
|
|
46152
|
-
|
|
46153
|
-
|
|
46083
|
+
if (!fs17.existsSync(resolved) || !fs17.statSync(resolved).isFile()) {
|
|
46084
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
46085
|
+
res.end("File not found");
|
|
46086
|
+
return;
|
|
46154
46087
|
}
|
|
46155
|
-
|
|
46156
|
-
|
|
46157
|
-
|
|
46158
|
-
|
|
46159
|
-
|
|
46160
|
-
|
|
46088
|
+
const ext = path18.extname(resolved).toLowerCase();
|
|
46089
|
+
const mime = ext === ".md" || ext === ".mdx" ? "text/html" : getMime(resolved);
|
|
46090
|
+
const content = ext === ".md" || ext === ".mdx" ? renderMarkdownDocument(fs17.readFileSync(resolved, "utf8"), path18.basename(resolved)) : fs17.readFileSync(resolved);
|
|
46091
|
+
res.writeHead(200, {
|
|
46092
|
+
"Content-Type": mime,
|
|
46093
|
+
"Content-Length": content.length,
|
|
46094
|
+
"Cache-Control": "no-cache"
|
|
46095
|
+
});
|
|
46096
|
+
res.end(content);
|
|
46097
|
+
}
|
|
46098
|
+
async function handlePreviewRequest(sessionManager, req, res) {
|
|
46099
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
46100
|
+
if (!url.pathname.startsWith("/preview/") && !url.pathname.startsWith("/preview-open/")) {
|
|
46101
|
+
return false;
|
|
46161
46102
|
}
|
|
46162
|
-
|
|
46163
|
-
|
|
46164
|
-
|
|
46165
|
-
|
|
46166
|
-
|
|
46167
|
-
|
|
46168
|
-
|
|
46169
|
-
|
|
46103
|
+
const openMatch = url.pathname.match(/^\/preview-open\/([a-f0-9]+)(?:\/(.*))?$/i);
|
|
46104
|
+
if (openMatch?.[1]) {
|
|
46105
|
+
if (!isReadOnlyPreviewMethod(req.method)) {
|
|
46106
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
46107
|
+
res.end("Method not allowed");
|
|
46108
|
+
return true;
|
|
46109
|
+
}
|
|
46110
|
+
const requestedPath2 = openMatch[2];
|
|
46111
|
+
sendPreviewOpenResponse(sessionManager, openMatch[1], requestedPath2 ? `${requestedPath2}${url.search}` : url.search || void 0, res);
|
|
46112
|
+
return true;
|
|
46170
46113
|
}
|
|
46171
|
-
|
|
46172
|
-
|
|
46173
|
-
|
|
46174
|
-
|
|
46175
|
-
|
|
46176
|
-
|
|
46177
|
-
|
|
46178
|
-
|
|
46179
|
-
|
|
46180
|
-
|
|
46114
|
+
const match = url.pathname.match(/^\/preview\/([a-f0-9]+)(?:\/(.*))?$/i);
|
|
46115
|
+
const token = match?.[1];
|
|
46116
|
+
if (!token) {
|
|
46117
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
46118
|
+
res.end("Preview not found");
|
|
46119
|
+
return true;
|
|
46120
|
+
}
|
|
46121
|
+
const requestedPath = match?.[2];
|
|
46122
|
+
await sendPreviewAssetResponse(sessionManager, token, requestedPath ? `${requestedPath}${url.search}` : url.search || void 0, res, req);
|
|
46123
|
+
return true;
|
|
46124
|
+
}
|
|
46125
|
+
|
|
46126
|
+
// ../../packages/api/src/preview/preview-proxy.ts
|
|
46127
|
+
var PREVIEW_ASSET_PROXY_TIMEOUT_MS = 1e4;
|
|
46128
|
+
function describeProxyError(error) {
|
|
46129
|
+
if (error instanceof Error) {
|
|
46130
|
+
const errorCategory = error.name === "AbortError" || /aborted/i.test(error.message) ? "abort" : /timeout/i.test(error.message) ? "timeout" : "network";
|
|
46131
|
+
return {
|
|
46132
|
+
errorName: error.name,
|
|
46133
|
+
errorMessage: error.message,
|
|
46134
|
+
errorCategory
|
|
46135
|
+
};
|
|
46136
|
+
}
|
|
46137
|
+
return {
|
|
46138
|
+
errorName: "UnknownError",
|
|
46139
|
+
errorMessage: String(error),
|
|
46140
|
+
errorCategory: "unknown"
|
|
46141
|
+
};
|
|
46142
|
+
}
|
|
46143
|
+
function createPreviewAssetProxyTrace(log2, token, nodeId, requestPath, proxyPath) {
|
|
46144
|
+
return {
|
|
46145
|
+
onAttempt: ({ attempt, endpoint, timeoutMs, totalEndpoints }) => {
|
|
46146
|
+
log2.debug("preview asset proxy attempt", {
|
|
46147
|
+
token,
|
|
46148
|
+
nodeId,
|
|
46149
|
+
requestPath,
|
|
46150
|
+
proxyPath,
|
|
46151
|
+
endpoint,
|
|
46152
|
+
attempt,
|
|
46153
|
+
totalEndpoints,
|
|
46154
|
+
timeoutMs
|
|
46155
|
+
});
|
|
46156
|
+
},
|
|
46157
|
+
onResponse: ({ attempt, endpoint, response, timeoutMs, totalEndpoints }) => {
|
|
46158
|
+
log2.debug("preview asset proxy response", {
|
|
46159
|
+
token,
|
|
46160
|
+
nodeId,
|
|
46161
|
+
requestPath,
|
|
46162
|
+
proxyPath,
|
|
46163
|
+
endpoint,
|
|
46164
|
+
attempt,
|
|
46165
|
+
totalEndpoints,
|
|
46166
|
+
timeoutMs,
|
|
46167
|
+
ok: response.ok,
|
|
46168
|
+
statusCode: response.status
|
|
46169
|
+
});
|
|
46170
|
+
},
|
|
46171
|
+
onError: ({ attempt, endpoint, error, timeoutMs, totalEndpoints }) => {
|
|
46172
|
+
log2.warn("preview asset proxy attempt failed", {
|
|
46173
|
+
token,
|
|
46174
|
+
nodeId,
|
|
46175
|
+
requestPath,
|
|
46176
|
+
proxyPath,
|
|
46177
|
+
endpoint,
|
|
46178
|
+
attempt,
|
|
46179
|
+
totalEndpoints,
|
|
46180
|
+
timeoutMs,
|
|
46181
|
+
...describeProxyError(error)
|
|
46182
|
+
});
|
|
46181
46183
|
}
|
|
46184
|
+
};
|
|
46185
|
+
}
|
|
46186
|
+
function parsePreviewRequest(previewUrl) {
|
|
46187
|
+
const url = new URL(previewUrl, "http://localhost");
|
|
46188
|
+
const match = url.pathname.match(/^\/preview\/([a-f0-9]+)(?:\/(.*))?$/i);
|
|
46189
|
+
const token = match?.[1];
|
|
46190
|
+
if (!token) {
|
|
46191
|
+
return null;
|
|
46182
46192
|
}
|
|
46183
|
-
|
|
46184
|
-
|
|
46185
|
-
|
|
46186
|
-
|
|
46187
|
-
|
|
46188
|
-
|
|
46193
|
+
const requestedPath = match?.[2];
|
|
46194
|
+
return {
|
|
46195
|
+
token,
|
|
46196
|
+
requestedPath: requestedPath ? `${requestedPath}${url.search}` : url.search || void 0
|
|
46197
|
+
};
|
|
46198
|
+
}
|
|
46199
|
+
function parsePreviewOpenRequest(previewUrl) {
|
|
46200
|
+
const url = new URL(previewUrl, "http://localhost");
|
|
46201
|
+
const match = url.pathname.match(/^\/preview-open\/([a-f0-9]+)(?:\/(.*))?$/i);
|
|
46202
|
+
const token = match?.[1];
|
|
46203
|
+
if (!token) {
|
|
46204
|
+
return null;
|
|
46189
46205
|
}
|
|
46190
|
-
|
|
46191
|
-
|
|
46206
|
+
const requestedPath = match?.[2];
|
|
46207
|
+
const suffix = requestedPath !== void 0 ? `/${requestedPath}` : "";
|
|
46208
|
+
return {
|
|
46209
|
+
token,
|
|
46210
|
+
previewUrl: `/preview/${token}${suffix}${url.search}`
|
|
46211
|
+
};
|
|
46212
|
+
}
|
|
46213
|
+
function extractPreviewToken(previewUrl) {
|
|
46214
|
+
return parsePreviewRequest(previewUrl)?.token ?? null;
|
|
46215
|
+
}
|
|
46216
|
+
function buildPreviewAssetProxyPath(token, requestedPath) {
|
|
46217
|
+
const search = new URLSearchParams({ token });
|
|
46218
|
+
if (requestedPath) {
|
|
46219
|
+
search.set("path", requestedPath);
|
|
46192
46220
|
}
|
|
46193
|
-
}
|
|
46194
|
-
|
|
46195
|
-
|
|
46196
|
-
|
|
46197
|
-
|
|
46198
|
-
this.
|
|
46221
|
+
return `/api/worker/preview-asset?${search.toString()}`;
|
|
46222
|
+
}
|
|
46223
|
+
var PreviewProxyManager = class {
|
|
46224
|
+
sessions = /* @__PURE__ */ new Map();
|
|
46225
|
+
register(token, nodeId, expiresAt) {
|
|
46226
|
+
this.sessions.set(token, { token, nodeId, expiresAt });
|
|
46199
46227
|
}
|
|
46200
|
-
|
|
46201
|
-
|
|
46202
|
-
|
|
46228
|
+
get(token) {
|
|
46229
|
+
const session = this.sessions.get(token);
|
|
46230
|
+
if (!session) {
|
|
46231
|
+
return null;
|
|
46232
|
+
}
|
|
46233
|
+
if (Date.now() > session.expiresAt) {
|
|
46234
|
+
this.sessions.delete(token);
|
|
46235
|
+
return null;
|
|
46236
|
+
}
|
|
46237
|
+
return session;
|
|
46203
46238
|
}
|
|
46204
|
-
|
|
46239
|
+
cleanup() {
|
|
46205
46240
|
const now = Date.now();
|
|
46206
|
-
const
|
|
46207
|
-
|
|
46208
|
-
|
|
46209
|
-
|
|
46210
|
-
|
|
46211
|
-
status: "queued",
|
|
46212
|
-
payload,
|
|
46213
|
-
createdAt: now,
|
|
46214
|
-
updatedAt: now
|
|
46215
|
-
};
|
|
46216
|
-
const saved = this.store.upsert(operation);
|
|
46217
|
-
this.deps.eventBus.emit("node.operation.created", { operation: saved });
|
|
46218
|
-
return saved;
|
|
46219
|
-
}
|
|
46220
|
-
get(id) {
|
|
46221
|
-
return this.store.get(id);
|
|
46241
|
+
for (const [token, session] of this.sessions) {
|
|
46242
|
+
if (session.expiresAt <= now) {
|
|
46243
|
+
this.sessions.delete(token);
|
|
46244
|
+
}
|
|
46245
|
+
}
|
|
46222
46246
|
}
|
|
46223
|
-
|
|
46224
|
-
|
|
46247
|
+
};
|
|
46248
|
+
function rewritePreviewSessionPayloadForProxy(manager, payload, nodeId) {
|
|
46249
|
+
const token = extractPreviewToken(payload.previewUrl);
|
|
46250
|
+
if (!token) {
|
|
46251
|
+
return payload;
|
|
46225
46252
|
}
|
|
46226
|
-
|
|
46227
|
-
|
|
46228
|
-
|
|
46229
|
-
|
|
46230
|
-
|
|
46253
|
+
manager.register(token, nodeId, payload.expiresAt);
|
|
46254
|
+
return {
|
|
46255
|
+
...payload,
|
|
46256
|
+
previewUrl: buildPreviewUrl("", {
|
|
46257
|
+
token,
|
|
46258
|
+
entryPath: payload.entryPath,
|
|
46259
|
+
kind: payload.kind
|
|
46260
|
+
}),
|
|
46261
|
+
...payload.kind === "service" || payload.openUrl ? {
|
|
46262
|
+
openUrl: buildPreviewOpenUrl("", {
|
|
46263
|
+
token,
|
|
46264
|
+
entryPath: payload.entryPath,
|
|
46265
|
+
kind: payload.kind
|
|
46266
|
+
})
|
|
46267
|
+
} : {}
|
|
46268
|
+
};
|
|
46269
|
+
}
|
|
46270
|
+
async function handlePreviewProxyRequest(manager, nodeRegistry, logger27, req, res) {
|
|
46271
|
+
const requestPath = req.originalUrl ?? req.url;
|
|
46272
|
+
const previewOpenRequest = parsePreviewOpenRequest(requestPath);
|
|
46273
|
+
if (previewOpenRequest) {
|
|
46274
|
+
const session2 = manager.get(previewOpenRequest.token);
|
|
46275
|
+
if (!session2) {
|
|
46276
|
+
return false;
|
|
46231
46277
|
}
|
|
46232
|
-
const
|
|
46233
|
-
|
|
46234
|
-
|
|
46235
|
-
|
|
46278
|
+
const content = renderPreviewOpenDocument(previewOpenRequest.previewUrl);
|
|
46279
|
+
res.status(200).set({
|
|
46280
|
+
"Content-Type": "text/html",
|
|
46281
|
+
"Content-Length": String(content.length),
|
|
46282
|
+
"Cache-Control": "no-cache"
|
|
46283
|
+
}).send(content);
|
|
46284
|
+
return true;
|
|
46236
46285
|
}
|
|
46237
|
-
|
|
46238
|
-
|
|
46286
|
+
const previewRequest = parsePreviewRequest(requestPath);
|
|
46287
|
+
if (!previewRequest) {
|
|
46288
|
+
return false;
|
|
46239
46289
|
}
|
|
46240
|
-
|
|
46241
|
-
|
|
46242
|
-
|
|
46243
|
-
this.applyOperationSideEffects(saved);
|
|
46244
|
-
this.deps.eventBus.emit("node.operation.updated", { operation: saved });
|
|
46245
|
-
return saved;
|
|
46290
|
+
const session = manager.get(previewRequest.token);
|
|
46291
|
+
if (!session) {
|
|
46292
|
+
return false;
|
|
46246
46293
|
}
|
|
46247
|
-
|
|
46248
|
-
|
|
46249
|
-
|
|
46250
|
-
|
|
46251
|
-
|
|
46294
|
+
const node = nodeRegistry.getNode(session.nodeId);
|
|
46295
|
+
if (!node) {
|
|
46296
|
+
throw new MeshyError("NODE_NOT_FOUND", `Preview worker ${session.nodeId} not found`, 404);
|
|
46297
|
+
}
|
|
46298
|
+
const previewLog = logger27.child("preview-proxy");
|
|
46299
|
+
const proxyPath = buildPreviewAssetProxyPath(previewRequest.token, previewRequest.requestedPath);
|
|
46300
|
+
try {
|
|
46301
|
+
const body = await readPreviewRequestBody(req);
|
|
46302
|
+
const { endpoint, response } = await fetchNodeWithFallback(
|
|
46303
|
+
node,
|
|
46304
|
+
proxyPath,
|
|
46305
|
+
{
|
|
46306
|
+
method: req.method,
|
|
46307
|
+
headers: buildPreviewWorkerProxyHeaders(req),
|
|
46308
|
+
...body !== void 0 ? { body } : {}
|
|
46309
|
+
},
|
|
46310
|
+
PREVIEW_ASSET_PROXY_TIMEOUT_MS,
|
|
46311
|
+
createPreviewAssetProxyTrace(previewLog, previewRequest.token, session.nodeId, requestPath, proxyPath),
|
|
46312
|
+
{ preferPublicEndpoint: true }
|
|
46313
|
+
);
|
|
46314
|
+
previewLog.debug("proxying preview asset from assigned worker", {
|
|
46315
|
+
token: previewRequest.token,
|
|
46316
|
+
nodeId: session.nodeId,
|
|
46317
|
+
requestPath,
|
|
46318
|
+
proxyPath,
|
|
46319
|
+
endpoint
|
|
46320
|
+
});
|
|
46321
|
+
await sendProxyResponse(res, response);
|
|
46322
|
+
return true;
|
|
46323
|
+
} catch (err) {
|
|
46324
|
+
previewLog.warn("preview asset proxy error", {
|
|
46325
|
+
token: previewRequest.token,
|
|
46326
|
+
nodeId: session.nodeId,
|
|
46327
|
+
requestPath,
|
|
46328
|
+
proxyPath,
|
|
46329
|
+
timeoutMs: PREVIEW_ASSET_PROXY_TIMEOUT_MS,
|
|
46330
|
+
...describeProxyError(err)
|
|
46252
46331
|
});
|
|
46332
|
+
throw new MeshyError("NODE_OFFLINE", "Cannot reach worker for preview asset", 502);
|
|
46253
46333
|
}
|
|
46254
|
-
|
|
46255
|
-
|
|
46256
|
-
|
|
46257
|
-
|
|
46334
|
+
}
|
|
46335
|
+
|
|
46336
|
+
// ../../packages/api/src/tasks/task-output-service.ts
|
|
46337
|
+
var TASK_OUTPUT_ROUTE_PREFIX = "/api/tasks";
|
|
46338
|
+
function getTask(taskEngine, taskId) {
|
|
46339
|
+
const task = taskEngine.getTask(taskId);
|
|
46340
|
+
if (!task) {
|
|
46341
|
+
throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
|
|
46258
46342
|
}
|
|
46259
|
-
|
|
46260
|
-
|
|
46261
|
-
|
|
46262
|
-
|
|
46263
|
-
|
|
46264
|
-
|
|
46265
|
-
|
|
46266
|
-
|
|
46267
|
-
|
|
46268
|
-
|
|
46269
|
-
|
|
46270
|
-
|
|
46271
|
-
|
|
46272
|
-
|
|
46273
|
-
|
|
46274
|
-
|
|
46275
|
-
|
|
46343
|
+
return task;
|
|
46344
|
+
}
|
|
46345
|
+
function getTaskOutputRoot(taskEngine, taskId) {
|
|
46346
|
+
const task = getTask(taskEngine, taskId);
|
|
46347
|
+
if (!task.effectiveProjectPath) {
|
|
46348
|
+
throw new MeshyError("VALIDATION_ERROR", "Task output not available", 400);
|
|
46349
|
+
}
|
|
46350
|
+
return task.effectiveProjectPath;
|
|
46351
|
+
}
|
|
46352
|
+
function buildTaskOutputDownloadUrl(taskId, filePath) {
|
|
46353
|
+
return `${TASK_OUTPUT_ROUTE_PREFIX}/${taskId}/output/download?path=${encodeURIComponent(filePath)}`;
|
|
46354
|
+
}
|
|
46355
|
+
function getLocalTaskOutputSummary(taskEngine, taskId) {
|
|
46356
|
+
const task = getTask(taskEngine, taskId);
|
|
46357
|
+
if (!task.effectiveProjectPath) {
|
|
46358
|
+
return {
|
|
46359
|
+
taskId: task.id,
|
|
46360
|
+
requestedProject: task.project,
|
|
46361
|
+
effectiveProjectPath: null,
|
|
46362
|
+
assignedTo: task.assignedTo,
|
|
46363
|
+
available: false,
|
|
46364
|
+
summary: null
|
|
46365
|
+
};
|
|
46366
|
+
}
|
|
46367
|
+
return {
|
|
46368
|
+
taskId: task.id,
|
|
46369
|
+
requestedProject: task.project,
|
|
46370
|
+
effectiveProjectPath: task.effectiveProjectPath,
|
|
46371
|
+
assignedTo: task.assignedTo,
|
|
46372
|
+
available: true,
|
|
46373
|
+
summary: getOutputSummary(task.effectiveProjectPath)
|
|
46374
|
+
};
|
|
46375
|
+
}
|
|
46376
|
+
function getLocalTaskOutputTree(taskEngine, taskId, currentPath) {
|
|
46377
|
+
const rootPath = getTaskOutputRoot(taskEngine, taskId);
|
|
46378
|
+
try {
|
|
46379
|
+
return {
|
|
46380
|
+
rootPath,
|
|
46381
|
+
currentPath,
|
|
46382
|
+
entries: listDirectory(rootPath, currentPath)
|
|
46383
|
+
};
|
|
46384
|
+
} catch (err) {
|
|
46385
|
+
if (err instanceof Error && err.message === "Path traversal detected") {
|
|
46386
|
+
throw new MeshyError("VALIDATION_ERROR", "Invalid directory path", 400);
|
|
46276
46387
|
}
|
|
46388
|
+
throw err;
|
|
46277
46389
|
}
|
|
46278
|
-
|
|
46279
|
-
|
|
46280
|
-
|
|
46281
|
-
|
|
46282
|
-
|
|
46283
|
-
|
|
46284
|
-
|
|
46285
|
-
|
|
46286
|
-
|
|
46287
|
-
if (
|
|
46288
|
-
|
|
46289
|
-
|
|
46290
|
-
|
|
46390
|
+
}
|
|
46391
|
+
function getLocalTaskOutputContent(taskEngine, taskId, filePath) {
|
|
46392
|
+
const rootPath = getTaskOutputRoot(taskEngine, taskId);
|
|
46393
|
+
try {
|
|
46394
|
+
return {
|
|
46395
|
+
...readFileContent(rootPath, filePath),
|
|
46396
|
+
downloadUrl: buildTaskOutputDownloadUrl(taskId, filePath)
|
|
46397
|
+
};
|
|
46398
|
+
} catch (err) {
|
|
46399
|
+
if (err instanceof Error && err.message === "Path traversal detected") {
|
|
46400
|
+
throw new MeshyError("VALIDATION_ERROR", "Invalid file path", 400);
|
|
46401
|
+
}
|
|
46402
|
+
if (err instanceof Error && err.message === "File not found") {
|
|
46403
|
+
throw new MeshyError("TASK_NOT_FOUND", `File not found: ${filePath}`, 404);
|
|
46291
46404
|
}
|
|
46405
|
+
throw err;
|
|
46292
46406
|
}
|
|
46293
|
-
|
|
46294
|
-
|
|
46295
|
-
|
|
46296
|
-
|
|
46297
|
-
|
|
46298
|
-
|
|
46299
|
-
|
|
46300
|
-
|
|
46301
|
-
|
|
46302
|
-
|
|
46303
|
-
|
|
46304
|
-
|
|
46305
|
-
|
|
46306
|
-
|
|
46307
|
-
|
|
46308
|
-
|
|
46407
|
+
}
|
|
46408
|
+
function getLocalTaskOutputDownload(taskEngine, taskId, filePath) {
|
|
46409
|
+
const rootPath = getTaskOutputRoot(taskEngine, taskId);
|
|
46410
|
+
try {
|
|
46411
|
+
const absolutePath = resolveOutputPath(rootPath, filePath);
|
|
46412
|
+
if (!fs18.existsSync(absolutePath) || !fs18.statSync(absolutePath).isFile()) {
|
|
46413
|
+
throw new MeshyError("TASK_NOT_FOUND", `File not found: ${filePath}`, 404);
|
|
46414
|
+
}
|
|
46415
|
+
const { mimeType } = classifyFile(absolutePath);
|
|
46416
|
+
const fileName = path19.basename(absolutePath).replace(/"/g, "");
|
|
46417
|
+
return {
|
|
46418
|
+
content: fs18.readFileSync(absolutePath),
|
|
46419
|
+
headers: {
|
|
46420
|
+
"Content-Type": mimeType,
|
|
46421
|
+
"Content-Disposition": `inline; filename="${fileName}"`,
|
|
46422
|
+
"Cache-Control": "no-cache"
|
|
46423
|
+
}
|
|
46424
|
+
};
|
|
46425
|
+
} catch (err) {
|
|
46426
|
+
if (err instanceof MeshyError) {
|
|
46427
|
+
throw err;
|
|
46428
|
+
}
|
|
46429
|
+
if (err instanceof Error && err.message === "Path traversal detected") {
|
|
46430
|
+
throw new MeshyError("VALIDATION_ERROR", "Invalid file path", 400);
|
|
46309
46431
|
}
|
|
46432
|
+
throw err;
|
|
46310
46433
|
}
|
|
46311
|
-
};
|
|
46312
|
-
function getNodeOperationService(deps) {
|
|
46313
|
-
if (deps.nodeOperationService) return deps.nodeOperationService;
|
|
46314
|
-
const store = deps.nodeOperationStore ?? (deps.storagePath ? new FileNodeOperationStore(deps.storagePath) : new InMemoryNodeOperationStore());
|
|
46315
|
-
const service = new NodeOperationService(deps, store);
|
|
46316
|
-
deps.nodeOperationStore = store;
|
|
46317
|
-
deps.nodeOperationService = service;
|
|
46318
|
-
return service;
|
|
46319
46434
|
}
|
|
46320
|
-
|
|
46321
|
-
const
|
|
46322
|
-
|
|
46323
|
-
|
|
46324
|
-
|
|
46325
|
-
|
|
46326
|
-
|
|
46435
|
+
function getLocalTaskOutputDiff(taskEngine, taskId) {
|
|
46436
|
+
const rootPath = getTaskOutputRoot(taskEngine, taskId);
|
|
46437
|
+
return getGitDiff(rootPath);
|
|
46438
|
+
}
|
|
46439
|
+
async function createPreviewSessionPayload(deps, taskId, optionsOrPath, requestOrigin) {
|
|
46440
|
+
const previewManager = deps.previewSessionManager;
|
|
46441
|
+
if (!previewManager) {
|
|
46442
|
+
throw new MeshyError("VALIDATION_ERROR", "Preview not available on this node", 400);
|
|
46327
46443
|
}
|
|
46328
|
-
const
|
|
46329
|
-
|
|
46330
|
-
|
|
46444
|
+
const options = typeof optionsOrPath === "string" ? { path: optionsOrPath } : optionsOrPath ?? {};
|
|
46445
|
+
getTask(deps.taskEngine, taskId);
|
|
46446
|
+
const session = options.port !== void 0 ? previewManager.create({
|
|
46447
|
+
taskId,
|
|
46448
|
+
port: options.port,
|
|
46449
|
+
entryPath: options.path
|
|
46450
|
+
}) : previewManager.create({
|
|
46451
|
+
taskId,
|
|
46452
|
+
rootPath: getTaskOutputRoot(deps.taskEngine, taskId),
|
|
46453
|
+
entryPath: options.path
|
|
46331
46454
|
});
|
|
46332
|
-
|
|
46333
|
-
|
|
46334
|
-
|
|
46335
|
-
const agent = readPayloadString(operation, "agent");
|
|
46336
|
-
if (!isRuntimeAgentId(agent)) throw new Error(`Unsupported upgrade agent: ${agent}`);
|
|
46337
|
-
const result = upgradeRuntimeAgentForDeps(deps, agent);
|
|
46338
|
-
if (!result.ok) {
|
|
46339
|
-
throw new NodeOperationFailure(result.detail ?? `${agent} upgrade failed`, result);
|
|
46455
|
+
const origin = requestOrigin ?? deps.dashboardOrigin ?? deps.localDashboardOrigin;
|
|
46456
|
+
if (!origin) {
|
|
46457
|
+
throw new MeshyError("VALIDATION_ERROR", "Preview origin not available", 502);
|
|
46340
46458
|
}
|
|
46341
|
-
return
|
|
46342
|
-
|
|
46343
|
-
|
|
46344
|
-
|
|
46345
|
-
|
|
46346
|
-
|
|
46347
|
-
|
|
46348
|
-
}
|
|
46349
|
-
function readSettingsSnapshot(value) {
|
|
46350
|
-
if (!value || typeof value !== "object") return void 0;
|
|
46351
|
-
const snapshot = value.settingsSnapshot;
|
|
46352
|
-
return snapshot && typeof snapshot === "object" ? snapshot : void 0;
|
|
46353
|
-
}
|
|
46354
|
-
function matchesFilter(operation, filter) {
|
|
46355
|
-
return (!filter.nodeId || operation.nodeId === filter.nodeId) && (!filter.status || operation.status === filter.status) && (!filter.kind || operation.kind === filter.kind);
|
|
46356
|
-
}
|
|
46357
|
-
function isNodeOperation(value) {
|
|
46358
|
-
if (!value || typeof value !== "object") return false;
|
|
46359
|
-
const record = value;
|
|
46360
|
-
return typeof record.id === "string" && typeof record.kind === "string" && typeof record.nodeId === "string" && typeof record.status === "string" && typeof record.createdAt === "number" && typeof record.updatedAt === "number";
|
|
46361
|
-
}
|
|
46362
|
-
function clone2(value) {
|
|
46363
|
-
return JSON.parse(JSON.stringify(value));
|
|
46459
|
+
return {
|
|
46460
|
+
previewUrl: buildPreviewUrl(origin, session),
|
|
46461
|
+
...session.kind === "service" ? { openUrl: buildPreviewOpenUrl(origin, session) } : {},
|
|
46462
|
+
expiresAt: session.expiresAt,
|
|
46463
|
+
entryPath: session.entryPath,
|
|
46464
|
+
kind: session.kind,
|
|
46465
|
+
...session.kind === "service" ? { port: session.port } : {}
|
|
46466
|
+
};
|
|
46364
46467
|
}
|
|
46365
46468
|
|
|
46366
46469
|
// ../../packages/api/src/node/worker-control.ts
|
|
@@ -46559,6 +46662,17 @@ async function executeWorkerControlRequest(deps, request) {
|
|
|
46559
46662
|
});
|
|
46560
46663
|
break;
|
|
46561
46664
|
}
|
|
46665
|
+
case "task.delete": {
|
|
46666
|
+
const taskId = payloadValue(request, "taskId", "");
|
|
46667
|
+
const deleted = deps.taskEngine.deleteTask(taskId);
|
|
46668
|
+
response = jsonResponse(request.id, 200, {
|
|
46669
|
+
ok: true,
|
|
46670
|
+
taskId,
|
|
46671
|
+
deleted,
|
|
46672
|
+
missing: !deleted
|
|
46673
|
+
});
|
|
46674
|
+
break;
|
|
46675
|
+
}
|
|
46562
46676
|
case "task.logs": {
|
|
46563
46677
|
const taskId = payloadValue(request, "taskId", "");
|
|
46564
46678
|
const task = deps.taskEngine.getTask(taskId);
|
|
@@ -46913,79 +47027,6 @@ async function maybeHandleRemoteNodeWorkDirBranchInfoRequest(req, res, nodeId) {
|
|
|
46913
47027
|
throw new MeshyError("NODE_OFFLINE", `Cannot reach node ${nodeId} to inspect its git branch`, 502);
|
|
46914
47028
|
}
|
|
46915
47029
|
}
|
|
46916
|
-
async function maybeHandleRemoteNodeWorkDirBranchCreateRequest(req, res, nodeId) {
|
|
46917
|
-
const body = CreateNodeWorkDirBranchBody.parse(req.body);
|
|
46918
|
-
const { nodeRegistry, heartbeat, logger: rootLogger } = req.app.locals.deps;
|
|
46919
|
-
const selfId = nodeRegistry.getSelf().id;
|
|
46920
|
-
if (nodeId === selfId) {
|
|
46921
|
-
return false;
|
|
46922
|
-
}
|
|
46923
|
-
const node = nodeRegistry.getNode(nodeId);
|
|
46924
|
-
if (!node) {
|
|
46925
|
-
throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
|
|
46926
|
-
}
|
|
46927
|
-
const log2 = rootLogger.child("nodes/workdir-branch-create");
|
|
46928
|
-
const proxyPath = req.originalUrl ?? `/api/nodes/${nodeId}/workdir/branch`;
|
|
46929
|
-
const fallbackRequest = createNodeMessage("node.workdir.branch-create", {
|
|
46930
|
-
path: body.path,
|
|
46931
|
-
branchName: body.branchName,
|
|
46932
|
-
startPoint: body.startPoint,
|
|
46933
|
-
limit: body.limit,
|
|
46934
|
-
offset: body.offset,
|
|
46935
|
-
allowAbsolute: body.allowAbsolute
|
|
46936
|
-
}, { expectsResponse: true });
|
|
46937
|
-
const canPushToNode = heartbeat?.canPushToNode?.(nodeId) ?? true;
|
|
46938
|
-
if (!canPushToNode && canRequestNodeMessage(heartbeat)) {
|
|
46939
|
-
log2.warn("node workdir branch create request falling back to keepalive control", {
|
|
46940
|
-
nodeId,
|
|
46941
|
-
proxyPath
|
|
46942
|
-
});
|
|
46943
|
-
const controlResponse = await requestFallbackNodeMessage(heartbeat, nodeId, fallbackRequest);
|
|
46944
|
-
sendWorkerControlResponse(res, controlResponse);
|
|
46945
|
-
return true;
|
|
46946
|
-
}
|
|
46947
|
-
try {
|
|
46948
|
-
const { endpoint, response } = await fetchNodeWithFallback(
|
|
46949
|
-
node,
|
|
46950
|
-
proxyPath,
|
|
46951
|
-
{
|
|
46952
|
-
method: "POST",
|
|
46953
|
-
headers: { "Content-Type": "application/json" },
|
|
46954
|
-
body: JSON.stringify(body)
|
|
46955
|
-
},
|
|
46956
|
-
NODE_WORKDIR_BRANCH_PROXY_TIMEOUT_MS,
|
|
46957
|
-
createNodeWorkdirProxyTrace(log2, nodeId, proxyPath),
|
|
46958
|
-
{ preferPublicEndpoint: true }
|
|
46959
|
-
);
|
|
46960
|
-
log2.debug("proxying node workdir branch create request", {
|
|
46961
|
-
nodeId,
|
|
46962
|
-
endpoint,
|
|
46963
|
-
proxyPath
|
|
46964
|
-
});
|
|
46965
|
-
await sendProxyResponse(res, response);
|
|
46966
|
-
return true;
|
|
46967
|
-
} catch (err) {
|
|
46968
|
-
const errorDetails = describeProxyError2(err);
|
|
46969
|
-
log2.warn("node workdir branch create proxy error", {
|
|
46970
|
-
nodeId,
|
|
46971
|
-
proxyPath,
|
|
46972
|
-
timeoutMs: NODE_WORKDIR_BRANCH_PROXY_TIMEOUT_MS,
|
|
46973
|
-
...errorDetails
|
|
46974
|
-
});
|
|
46975
|
-
if (canRequestNodeMessage(heartbeat)) {
|
|
46976
|
-
log2.warn("node workdir branch create proxy failed, falling back to keepalive control", {
|
|
46977
|
-
nodeId,
|
|
46978
|
-
proxyPath,
|
|
46979
|
-
timeoutMs: NODE_WORKDIR_BRANCH_PROXY_TIMEOUT_MS,
|
|
46980
|
-
...errorDetails
|
|
46981
|
-
});
|
|
46982
|
-
const controlResponse = await requestFallbackNodeMessage(heartbeat, nodeId, fallbackRequest);
|
|
46983
|
-
sendWorkerControlResponse(res, controlResponse);
|
|
46984
|
-
return true;
|
|
46985
|
-
}
|
|
46986
|
-
throw new MeshyError("NODE_OFFLINE", `Cannot reach node ${nodeId} to create a git branch`, 502);
|
|
46987
|
-
}
|
|
46988
|
-
}
|
|
46989
47030
|
async function maybeHandleRemoteNodeNativeSessionsRequest(req, res, nodeId) {
|
|
46990
47031
|
const query = NodeNativeSessionsQuery.parse(req.query);
|
|
46991
47032
|
const { nodeRegistry, heartbeat, logger: rootLogger } = req.app.locals.deps;
|
|
@@ -47119,11 +47160,7 @@ function createNodeRoutes() {
|
|
|
47119
47160
|
}));
|
|
47120
47161
|
router.post("/:id/workdir/branch", asyncHandler3(async (req, res) => {
|
|
47121
47162
|
const nodeId = req.params.id;
|
|
47122
|
-
|
|
47123
|
-
if (handled) {
|
|
47124
|
-
return;
|
|
47125
|
-
}
|
|
47126
|
-
sendLocalNodeWorkDirBranchCreate(req, res, nodeId);
|
|
47163
|
+
await sendNodeWorkDirBranchCreateOperation(req, res, nodeId);
|
|
47127
47164
|
}));
|
|
47128
47165
|
router.post("/:id/agents/:agent/upgrade", asyncHandler3(async (req, res) => {
|
|
47129
47166
|
await sendNodeAgentUpgrade(req, res, req.params.id, req.params.agent);
|
|
@@ -48085,6 +48122,7 @@ function createTaskOutputRoutes(options = {}) {
|
|
|
48085
48122
|
var TERMINAL_STATUSES3 = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "archived"]);
|
|
48086
48123
|
var ARCHIVABLE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "archived"]);
|
|
48087
48124
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running"]);
|
|
48125
|
+
var TASK_DELETE_NOTIFY_TIMEOUT_MS = 1500;
|
|
48088
48126
|
function shouldGenerateTitle(task) {
|
|
48089
48127
|
return task.payload.titleSource === "derived";
|
|
48090
48128
|
}
|
|
@@ -48198,6 +48236,39 @@ function toTaskResponse(task, nodeRegistry, taskEngine, shareOrigin) {
|
|
|
48198
48236
|
function buildShareUrl(origin, shareId) {
|
|
48199
48237
|
return `${origin.replace(/\/+$/, "")}/shared/tasks/${encodeURIComponent(shareId)}`;
|
|
48200
48238
|
}
|
|
48239
|
+
async function notifyAssignedWorkerTaskDeleted(deps, task) {
|
|
48240
|
+
if (!task.assignedTo || task.assignedTo === deps.nodeRegistry.getSelf()?.id) {
|
|
48241
|
+
return;
|
|
48242
|
+
}
|
|
48243
|
+
const node = deps.nodeRegistry.getNode(task.assignedTo);
|
|
48244
|
+
const log2 = deps.logger.child("tasks/delete");
|
|
48245
|
+
if (!node) {
|
|
48246
|
+
log2.warn("skipping worker task delete notification because assigned node is missing", {
|
|
48247
|
+
taskId: task.id,
|
|
48248
|
+
assignedTo: task.assignedTo
|
|
48249
|
+
});
|
|
48250
|
+
return;
|
|
48251
|
+
}
|
|
48252
|
+
try {
|
|
48253
|
+
const client = new NodeMessageClient({
|
|
48254
|
+
heartbeat: deps.heartbeat,
|
|
48255
|
+
logger: deps.logger,
|
|
48256
|
+
timeoutMs: TASK_DELETE_NOTIFY_TIMEOUT_MS
|
|
48257
|
+
});
|
|
48258
|
+
const delivery = await client.send(node, createNodeMessage("task.delete", { taskId: task.id }));
|
|
48259
|
+
log2.info("notified assigned worker of task delete", {
|
|
48260
|
+
taskId: task.id,
|
|
48261
|
+
assignedTo: task.assignedTo,
|
|
48262
|
+
queued: delivery.queued
|
|
48263
|
+
});
|
|
48264
|
+
} catch (err) {
|
|
48265
|
+
log2.warn("failed to notify assigned worker of task delete", {
|
|
48266
|
+
taskId: task.id,
|
|
48267
|
+
assignedTo: task.assignedTo,
|
|
48268
|
+
error: err instanceof Error ? err.message : String(err)
|
|
48269
|
+
});
|
|
48270
|
+
}
|
|
48271
|
+
}
|
|
48201
48272
|
function createTaskRoutes() {
|
|
48202
48273
|
const router = (0, import_express8.Router)();
|
|
48203
48274
|
router.post("/", asyncHandler6(async (req, res) => {
|
|
@@ -48249,16 +48320,21 @@ function createTaskRoutes() {
|
|
|
48249
48320
|
});
|
|
48250
48321
|
}));
|
|
48251
48322
|
router.post("/batch/delete", asyncHandler6(async (req, res) => {
|
|
48252
|
-
const
|
|
48323
|
+
const deps = req.app.locals.deps;
|
|
48324
|
+
const { taskEngine } = deps;
|
|
48253
48325
|
const { ids } = BatchTaskIdsBody.parse(req.body);
|
|
48326
|
+
const deletedTasks = [];
|
|
48254
48327
|
const results = ids.map((id) => {
|
|
48255
48328
|
try {
|
|
48329
|
+
const task = taskEngine.getTask(id);
|
|
48256
48330
|
const ok = taskEngine.deleteTask(id);
|
|
48331
|
+
if (ok && task) deletedTasks.push(task);
|
|
48257
48332
|
return ok ? { id, ok: true } : { id, ok: false, error: "Task not found" };
|
|
48258
48333
|
} catch (err) {
|
|
48259
48334
|
return { id, ok: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
48260
48335
|
}
|
|
48261
48336
|
});
|
|
48337
|
+
await Promise.all(deletedTasks.map((task) => notifyAssignedWorkerTaskDeleted(deps, task)));
|
|
48262
48338
|
res.json({ results });
|
|
48263
48339
|
}));
|
|
48264
48340
|
router.post("/batch/archive", asyncHandler6(async (req, res) => {
|
|
@@ -48380,12 +48456,17 @@ function createTaskRoutes() {
|
|
|
48380
48456
|
res.json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
48381
48457
|
}));
|
|
48382
48458
|
router.delete("/:id", asyncHandler6(async (req, res) => {
|
|
48383
|
-
const
|
|
48459
|
+
const deps = req.app.locals.deps;
|
|
48460
|
+
const { taskEngine } = deps;
|
|
48384
48461
|
const taskId = req.params.id;
|
|
48462
|
+
const task = taskEngine.getTask(taskId);
|
|
48385
48463
|
const result = taskEngine.deleteTask(taskId);
|
|
48386
48464
|
if (!result) {
|
|
48387
48465
|
throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
|
|
48388
48466
|
}
|
|
48467
|
+
if (task) {
|
|
48468
|
+
await notifyAssignedWorkerTaskDeleted(deps, task);
|
|
48469
|
+
}
|
|
48389
48470
|
res.json({ ok: true });
|
|
48390
48471
|
}));
|
|
48391
48472
|
router.post("/:id/cancel", asyncHandler6(async (req, res) => {
|
|
@@ -49092,6 +49173,7 @@ function parseStatus(value) {
|
|
|
49092
49173
|
function createNodeOperationRoutes() {
|
|
49093
49174
|
const router = (0, import_express13.Router)();
|
|
49094
49175
|
router.get("/", asyncHandler10(async (req, res) => {
|
|
49176
|
+
if (await maybeProxyReadToLeader(req, res)) return;
|
|
49095
49177
|
const deps = req.app.locals.deps;
|
|
49096
49178
|
const operations = getNodeOperationService(deps).list({
|
|
49097
49179
|
nodeId: typeof req.query.nodeId === "string" ? req.query.nodeId : void 0,
|
|
@@ -49101,6 +49183,7 @@ function createNodeOperationRoutes() {
|
|
|
49101
49183
|
res.json({ operations });
|
|
49102
49184
|
}));
|
|
49103
49185
|
router.get("/:id", asyncHandler10(async (req, res) => {
|
|
49186
|
+
if (await maybeProxyReadToLeader(req, res)) return;
|
|
49104
49187
|
const deps = req.app.locals.deps;
|
|
49105
49188
|
const operation = getNodeOperationService(deps).get(req.params.id);
|
|
49106
49189
|
if (!operation) {
|