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/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 = (process.env.PATH ?? "").split(path8.delimiter).filter(Boolean);
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 = (process.env.PATH ?? "").split(path8.delimiter).filter(Boolean);
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/routes/node-workdir.ts
44409
- function sendLocalNodeWorkDirTree(req, res, nodeId, options = {}) {
44410
- const query = NodeWorkDirTreeQuery.parse(req.query);
44411
- const { nodeRegistry, workDir, logger: logger27 } = req.app.locals.deps;
44412
- const self2 = nodeRegistry.getSelf();
44413
- if (options.requireSelfNode && nodeId !== self2.id) {
44414
- throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
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
- const startedAt = Date.now();
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 sendLocalNodeWorkDirBranchInfo(req, res, nodeId, options = {}) {
44462
- const query = NodeWorkDirBranchQuery.parse(req.query);
44463
- const { nodeRegistry, workDir } = req.app.locals.deps;
44464
- const self2 = nodeRegistry.getSelf();
44465
- if (options.requireSelfNode && nodeId !== self2.id) {
44466
- throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
44467
- }
44468
- res.json(getLocalNodeWorkDirBranchInfo(
44469
- self2.id,
44470
- self2.workDir ?? workDir,
44471
- query.path,
44472
- {
44473
- limit: query.limit,
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 sendLocalNodeWorkDirBranchCreate(req, res, nodeId, options = {}) {
44480
- const body = CreateNodeWorkDirBranchBody.parse(req.body);
44481
- const { nodeRegistry, workDir } = req.app.locals.deps;
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
- res.json(createLocalNodeWorkDirBranch(
44487
- self2.id,
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 requestFallbackNodeMessage(heartbeat, nodeId, message) {
44526
- if (heartbeat.requestNodeMessage) return heartbeat.requestNodeMessage(nodeId, message);
44527
- if (heartbeat.requestWorkerControl) return heartbeat.requestWorkerControl(nodeId, toLegacyWorkerControl2(message));
44528
- throw new Error("Node message fallback is not available");
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 toLegacyWorkerControl2(message) {
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
- ...typeof message.payload === "object" && message.payload !== null ? message.payload : {},
44533
- kind: LEGACY_KIND_BY_NODE_MESSAGE[message.kind] ?? message.kind
44520
+ path: null,
44521
+ detail: normalizeOutput(result.stderr) ?? result.error ?? null
44534
44522
  };
44535
44523
  }
44536
-
44537
- // ../../packages/api/src/tasks/task-route-utils.ts
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 readLocalTaskLogs(engineRegistry, taskId, after, agent) {
44550
- const engine = engineRegistry.get(agent);
44551
- if (!engine) {
44552
- throw new MeshyError("VALIDATION_ERROR", `Engine not registered for agent: ${agent}`, 400);
44553
- }
44554
- const logPath = engine.getLogPath(taskId);
44555
- if (!fs15.existsSync(logPath)) {
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 { logs, total: allLines.length };
44537
+ return normalizeOutput(trimmed);
44568
44538
  }
44569
- function recordLocalTaskLogs(engineRegistry, task, logs) {
44570
- if (logs.length === 0) {
44571
- return;
44572
- }
44573
- const engine = engineRegistry.get(task.agent);
44574
- if (!engine) {
44575
- throw new MeshyError("VALIDATION_ERROR", `Engine not registered for agent: ${task.agent}`, 400);
44576
- }
44577
- for (const event of logs) {
44578
- engine.recordOutput(task.id, event);
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 parseEventTimestamp(event) {
44582
- if (typeof event.timestamp !== "string") {
44583
- return null;
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 getTaskUserMessageSignature(value) {
44589
- const content = normalizeTaskUserMessageContent(value);
44590
- if (content.length === 0) {
44591
- return null;
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 content.map((part) => {
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 normalizeStructuredValue(value) {
44602
- if (Array.isArray(value)) {
44603
- return value.map((entry) => normalizeStructuredValue(entry));
44604
- }
44605
- if (!isRecord4(value)) {
44606
- return value;
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 Object.fromEntries(
44609
- Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, entryValue]) => [key, normalizeStructuredValue(entryValue)])
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 getTranscriptEventSignature(event) {
44613
- if (event.type === "user") {
44614
- const signature = getTaskUserMessageSignature(isRecord4(event.message) ? event.message.content : event.message) ?? getTaskUserMessageSignature(event.content);
44615
- return signature ? `user:${signature}` : null;
44616
- }
44617
- if (event.type === "assistant") {
44618
- if (typeof event.message === "string") {
44619
- return `assistant:${JSON.stringify(event.message)}`;
44620
- }
44621
- if (isRecord4(event.message) && "content" in event.message) {
44622
- return `assistant:${JSON.stringify(normalizeStructuredValue(event.message.content))}`;
44623
- }
44624
- if (Array.isArray(event.content)) {
44625
- return `assistant:${JSON.stringify(normalizeStructuredValue(event.content))}`;
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
- return null;
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 sortLogsChronologically(events) {
44634
- return events.map((event, index) => ({ event, index })).sort((left, right) => {
44635
- const leftTimestamp = parseEventTimestamp(left.event);
44636
- const rightTimestamp = parseEventTimestamp(right.event);
44637
- if (leftTimestamp !== null && rightTimestamp !== null && leftTimestamp !== rightTimestamp) {
44638
- return leftTimestamp - rightTimestamp;
44639
- }
44640
- return left.index - right.index;
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 timestampsMatch(left, right) {
44644
- const leftTimestamp = parseEventTimestamp(left);
44645
- const rightTimestamp = parseEventTimestamp(right);
44646
- if (leftTimestamp === null || rightTimestamp === null) {
44647
- return true;
44648
- }
44649
- return Math.abs(leftTimestamp - rightTimestamp) <= 6e4;
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 transcriptEvents(events) {
44652
- return events.flatMap((event, index) => {
44653
- const signature = getTranscriptEventSignature(event);
44654
- return signature ? [{ index, event, signature }] : [];
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 findLocalTranscriptDuplicates(nativeLogs, localLogs) {
44658
- const nativeTranscript = transcriptEvents(nativeLogs);
44659
- const localTranscript = transcriptEvents(localLogs);
44660
- if (nativeTranscript.length === 0 || localTranscript.length === 0) {
44661
- return /* @__PURE__ */ new Set();
44662
- }
44663
- const duplicates = /* @__PURE__ */ new Set();
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 duplicates;
44712
+ return args;
44670
44713
  }
44671
- function mergeNativeSessionLogs(nativeLogs, localLogs) {
44672
- if (localLogs.length === 0) {
44673
- return nativeLogs;
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 duplicateLocalIndexes = findLocalTranscriptDuplicates(nativeLogs, localLogs);
44676
- const retainedLocalLogs = localLogs.filter((_, index) => !duplicateLocalIndexes.has(index));
44677
- if (retainedLocalLogs.length === 0) {
44678
- return nativeLogs;
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
- return sortLogsChronologically([...nativeLogs, ...retainedLocalLogs]);
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 readLocalTaskLogsWithNativeHistory(engineRegistry, task, taskId, after, agent) {
44683
- const local = readLocalTaskLogs(engineRegistry, taskId, after, agent);
44684
- const sessionId = typeof task?.payload?.sessionId === "string" ? task.payload.sessionId : "";
44685
- if (after > 0 || task?.conversationKind !== "nativeSession" || !sessionId || task.agent !== "codex" && task.agent !== "claudecode") {
44686
- return local;
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
- const history = loadNativeSessionHistory({ agent: task.agent, sessionId });
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/tasks/task-output-service.ts
44857
- var fs17 = __toESM(require("fs"), 1);
44858
- var path18 = __toESM(require("path"), 1);
44859
-
44860
- // ../../packages/api/src/preview/preview-server.ts
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
- if (typeof body === "string" || body instanceof URLSearchParams || body instanceof Blob || body instanceof FormData) {
44898
- return body;
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
- if (Buffer.isBuffer(body)) {
44901
- return toArrayBuffer(body);
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
- if (body instanceof Uint8Array) {
44904
- return toArrayBuffer(body);
44767
+ upsert(operation) {
44768
+ const copy = clone2(operation);
44769
+ this.operations.set(copy.id, copy);
44770
+ return clone2(copy);
44905
44771
  }
44906
- return JSON.stringify(body);
44907
- }
44908
- async function readPreviewRequestBody(req) {
44909
- if (!req || !methodAllowsRequestBody(req.method)) {
44910
- return void 0;
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
- const parsedBody = getParsedRequestBody(req);
44913
- if (parsedBody !== void 0) {
44914
- return parsedBody;
44779
+ };
44780
+ var FileNodeOperationStore = class {
44781
+ constructor(storagePath) {
44782
+ this.storagePath = storagePath;
44915
44783
  }
44916
- const chunks = [];
44917
- for await (const chunk of req) {
44918
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
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
- return chunks.length > 0 ? toArrayBuffer(Buffer.concat(chunks)) : void 0;
44921
- }
44922
- function buildServiceProxyHeaders(req) {
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
- let previewAuthorization;
44928
- for (const [key, value] of Object.entries(req.headers)) {
44929
- const normalizedKey = key.toLowerCase();
44930
- 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) {
44931
- continue;
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
- if (previewAuthorization && !headers.authorization) {
44944
- headers.authorization = previewAuthorization;
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
- return headers;
44947
- }
44948
- function buildPreviewWorkerProxyHeaders(req) {
44949
- const headers = {};
44950
- let previewAuthorization;
44951
- for (const [key, value] of Object.entries(req.headers)) {
44952
- const normalizedKey = key.toLowerCase();
44953
- if (HOP_BY_HOP_HEADERS.has(normalizedKey) || normalizedKey === "host" || normalizedKey === "content-length" || normalizedKey === MESHY_AUTHORIZATION_HEADER || normalizedKey === PREVIEW_AUTHORIZATION_HEADER) {
44954
- continue;
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
- if (previewAuthorization) {
44967
- headers[PREVIEW_AUTHORIZATION_HEADER] = previewAuthorization;
44968
- }
44969
- return headers;
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
- return entryPath.replace(/\\/g, "/").replace(/^\/+/, "");
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
- return port;
45003
- }
45004
- function buildPreviewUrl(origin, session) {
45005
- return buildPreviewRouteUrl(origin, "preview", session);
45006
- }
45007
- function buildPreviewOpenUrl(origin, session) {
45008
- return buildPreviewRouteUrl(origin, "preview-open", session);
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
- get(token) {
45049
- const session = this.sessions.get(token);
45050
- if (!session) return null;
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
- cleanup() {
44845
+ create(kind, nodeId, payload) {
45058
44846
  const now = Date.now();
45059
- for (const [token, session] of this.sessions) {
45060
- if (now > session.expiresAt) {
45061
- this.sessions.delete(token);
45062
- }
45063
- }
45064
- }
45065
- };
45066
- var MIME_MAP2 = {
45067
- ".html": "text/html",
45068
- ".htm": "text/html",
45069
- ".css": "text/css",
45070
- ".js": "text/javascript",
45071
- ".mjs": "text/javascript",
45072
- ".json": "application/json",
45073
- ".png": "image/png",
45074
- ".jpg": "image/jpeg",
45075
- ".jpeg": "image/jpeg",
45076
- ".gif": "image/gif",
45077
- ".svg": "image/svg+xml",
45078
- ".webp": "image/webp",
45079
- ".ico": "image/x-icon",
45080
- ".woff": "font/woff",
45081
- ".woff2": "font/woff2",
45082
- ".ttf": "font/ttf",
45083
- ".eot": "application/vnd.ms-fontobject",
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 heading = trimmed.match(/^(#{1,6})\s+(.+)$/);
45139
- if (heading) {
45140
- flushParagraph();
45141
- flushList();
45142
- const level = heading[1].length;
45143
- blocks.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
45144
- continue;
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
- const listItem = trimmed.match(/^[-*]\s+(.+)$/);
45147
- if (listItem) {
45148
- flushParagraph();
45149
- listItems.push(listItem[1]);
45150
- continue;
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
- if (codeLines) {
45156
- blocks.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
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
- flushParagraph();
45159
- flushList();
45160
- return blocks.join("\n");
45161
- }
45162
- function renderMarkdownDocument(markdown, title) {
45163
- const body = renderMarkdownBody(markdown);
45164
- return Buffer.from(`<!doctype html>
45165
- <html>
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 splitPreviewPathAndSearch(value) {
45187
- const marker = value.indexOf("?");
45188
- if (marker === -1) {
45189
- return { pathname: value, search: "" };
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
- return { pathname: value.slice(0, marker), search: value.slice(marker) };
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 normalizeServiceRequestPath(pathname) {
45194
- const normalized = pathname.replace(/\\/g, "/");
45195
- if (!normalized || normalized === "/") {
45196
- return "/";
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 `/${normalized.replace(/^\/+/, "")}`;
44982
+ return result;
45199
44983
  }
45200
- function rewriteServiceRootRelativeUrls(value, token) {
45201
- const previewRoot = `/preview/${token}/`;
45202
- 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`);
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 escapeHtmlAttribute(value) {
45205
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
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 buildServicePreviewBridge(token) {
45208
- const previewRoot = `/preview/${token}/`;
45209
- const previewBase = previewRoot.replace(/\/$/, "");
45210
- const script = [
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 injectServicePreviewBridge(value, token) {
45249
- const bridge = buildServicePreviewBridge(token);
45250
- const headPattern = /<head(?:\s[^>]*)?>/i;
45251
- if (headPattern.test(value)) {
45252
- return value.replace(headPattern, (match) => `${match}${bridge}`);
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 normalizeContentType(contentType) {
45261
- return contentType?.split(";")[0]?.trim().toLowerCase();
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 isServiceHtmlContentType(contentType) {
45264
- return normalizeContentType(contentType) === "text/html";
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 isServiceEventStreamContentType(contentType) {
45267
- return normalizeContentType(contentType) === "text/event-stream";
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 isServiceJavaScriptContentType(normalizedType) {
45270
- return normalizedType === "text/javascript" || normalizedType === "application/javascript" || normalizedType === "application/x-javascript";
45030
+ function clone2(value) {
45031
+ return JSON.parse(JSON.stringify(value));
45271
45032
  }
45272
- function rewriteServiceJavaScriptRuntimeUrls(value, token) {
45273
- if (!value.includes("__vite__mapDeps")) {
45274
- return value;
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 previewRoot = `/preview/${token}/`;
45277
- return value.replace(
45278
- /return\s*(['"])\/\1\s*\+\s*([A-Za-z_$][\w$]*)/g,
45279
- (_match, quote, argumentName) => `return${quote}${previewRoot}${quote}+${argumentName}`
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 rewriteServiceResponseContent(content, contentType, token) {
45283
- const normalizedType = normalizeContentType(contentType);
45284
- if (normalizedType !== "text/html" && normalizedType !== "text/css" && !isServiceJavaScriptContentType(normalizedType)) {
45285
- return content;
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
- const source = content.toString("utf8");
45288
- const bridged = normalizedType === "text/html" ? injectServicePreviewBridge(source, token) : source;
45289
- const rewritten = rewriteServiceRootRelativeUrls(bridged, token);
45290
- return Buffer.from(
45291
- isServiceJavaScriptContentType(normalizedType) ? rewriteServiceJavaScriptRuntimeUrls(rewritten, token) : rewritten,
45292
- "utf8"
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 rewriteServiceRedirectLocation(value, session) {
45296
- const previewRoot = `/preview/${session.token}`;
45297
- if (value.startsWith("/preview/")) {
45298
- return value;
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
- if (value.startsWith("/")) {
45301
- return `${previewRoot}${value}`;
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
- try {
45304
- const url = new URL(value);
45305
- const isLocalService = (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]") && url.port === String(session.port);
45306
- if (isLocalService) {
45307
- return `${previewRoot}${url.pathname}${url.search}${url.hash}`;
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
- return value;
45137
+ res.status(202).json(operation);
45312
45138
  }
45313
- function buildServiceResponseHeaders(upstream, session, isHtml) {
45314
- const headers = {};
45315
- upstream.headers.forEach((value, key) => {
45316
- const normalizedKey = key.toLowerCase();
45317
- 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") {
45318
- return;
45319
- }
45320
- if (normalizedKey === "location") {
45321
- headers[key] = rewriteServiceRedirectLocation(value, session);
45322
- return;
45323
- }
45324
- headers[key] = value;
45325
- });
45326
- if (typeof headers["cache-control"] !== "string") {
45327
- headers["cache-control"] = "no-cache";
45328
- }
45329
- return headers;
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
- async function pipeServiceResponseBody(upstream, res) {
45332
- if (!upstream.body) {
45333
- res.end();
45334
- return;
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
- async function sendServicePreviewResponse(session, requestedPath, req, res) {
45345
- const decodedPath = decodeURIComponent(requestedPath ?? session.entryPath);
45346
- const { pathname, search } = splitPreviewPathAndSearch(decodedPath);
45347
- const targetPath = normalizeServiceRequestPath(pathname || session.entryPath);
45348
- const targetUrl = `http://127.0.0.1:${session.port}${targetPath}${search}`;
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
- function buildPreviewUrlForRequest(token, requestedPath, session) {
45377
- const { pathname, search } = splitPreviewPathAndSearch(requestedPath ?? session.entryPath);
45378
- return `${buildPreviewUrl("", { token, entryPath: pathname, kind: session.kind })}${search}`;
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 renderPreviewOpenDocument(previewUrl) {
45381
- const safePreviewUrl = escapeHtmlAttribute(previewUrl);
45382
- return Buffer.from(`<!doctype html>
45383
- <html>
45384
- <head>
45385
- <meta charset="utf-8">
45386
- <meta name="viewport" content="width=device-width, initial-scale=1">
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 sendPreviewOpenResponse(sessionManager, token, requestedPath, res) {
45399
- const session = sessionManager.get(token);
45400
- if (!session) {
45401
- res.writeHead(403, { "Content-Type": "text/plain" });
45402
- res.end("Invalid or expired preview token");
45403
- return;
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 = renderPreviewOpenDocument(buildPreviewUrlForRequest(token, requestedPath, session));
45406
- res.writeHead(200, {
45407
- "Content-Type": "text/html",
45408
- "Content-Length": content.length,
45409
- "Cache-Control": "no-cache"
45410
- });
45411
- res.end(content);
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
- async function sendPreviewAssetResponse(sessionManager, token, requestedPath, res, req) {
45414
- const session = sessionManager.get(token);
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
- if (session.kind === "service") {
45421
- await sendServicePreviewResponse(session, requestedPath, req, res);
45422
- return;
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
- if (!isReadOnlyPreviewMethod(req?.method)) {
45425
- res.writeHead(405, { "Content-Type": "text/plain" });
45426
- res.end("Method not allowed");
45427
- return;
45217
+ for (const event of logs) {
45218
+ engine.recordOutput(task.id, event);
45428
45219
  }
45429
- const { pathname } = splitPreviewPathAndSearch(decodeURIComponent(requestedPath ?? session.entryPath));
45430
- const resolvedRequestedPath = pathname || session.entryPath;
45431
- let resolved;
45432
- try {
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
- if (!fs16.existsSync(resolved) || !fs16.statSync(resolved).isFile()) {
45440
- res.writeHead(404, { "Content-Type": "text/plain" });
45441
- res.end("File not found");
45442
- return;
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
- const ext = path17.extname(resolved).toLowerCase();
45445
- const mime = ext === ".md" || ext === ".mdx" ? "text/html" : getMime(resolved);
45446
- const content = ext === ".md" || ext === ".mdx" ? renderMarkdownDocument(fs16.readFileSync(resolved, "utf8"), path17.basename(resolved)) : fs16.readFileSync(resolved);
45447
- res.writeHead(200, {
45448
- "Content-Type": mime,
45449
- "Content-Length": content.length,
45450
- "Cache-Control": "no-cache"
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
- async function handlePreviewRequest(sessionManager, req, res) {
45455
- const url = new URL(req.url ?? "/", "http://localhost");
45456
- if (!url.pathname.startsWith("/preview/") && !url.pathname.startsWith("/preview-open/")) {
45457
- return false;
45241
+ function normalizeStructuredValue(value) {
45242
+ if (Array.isArray(value)) {
45243
+ return value.map((entry) => normalizeStructuredValue(entry));
45458
45244
  }
45459
- const openMatch = url.pathname.match(/^\/preview-open\/([a-f0-9]+)(?:\/(.*))?$/i);
45460
- if (openMatch?.[1]) {
45461
- if (!isReadOnlyPreviewMethod(req.method)) {
45462
- res.writeHead(405, { "Content-Type": "text/plain" });
45463
- res.end("Method not allowed");
45464
- return true;
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
- const match = url.pathname.match(/^\/preview\/([a-f0-9]+)(?:\/(.*))?$/i);
45471
- const token = match?.[1];
45472
- if (!token) {
45473
- res.writeHead(404, { "Content-Type": "text/plain" });
45474
- res.end("Preview not found");
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
- const requestedPath = match?.[2];
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
- // ../../packages/api/src/preview/preview-proxy.ts
45483
- var PREVIEW_ASSET_PROXY_TIMEOUT_MS = 1e4;
45484
- function describeProxyError(error) {
45485
- if (error instanceof Error) {
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 createPreviewAssetProxyTrace(log2, token, nodeId, requestPath, proxyPath) {
45500
- return {
45501
- onAttempt: ({ attempt, endpoint, timeoutMs, totalEndpoints }) => {
45502
- log2.debug("preview asset proxy attempt", {
45503
- token,
45504
- nodeId,
45505
- requestPath,
45506
- proxyPath,
45507
- endpoint,
45508
- attempt,
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 parsePreviewRequest(previewUrl) {
45543
- const url = new URL(previewUrl, "http://localhost");
45544
- const match = url.pathname.match(/^\/preview\/([a-f0-9]+)(?:\/(.*))?$/i);
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 requestedPath = match?.[2];
45550
- return {
45551
- token,
45552
- requestedPath: requestedPath ? `${requestedPath}${url.search}` : url.search || void 0
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 parsePreviewOpenRequest(previewUrl) {
45556
- const url = new URL(previewUrl, "http://localhost");
45557
- const match = url.pathname.match(/^\/preview-open\/([a-f0-9]+)(?:\/(.*))?$/i);
45558
- const token = match?.[1];
45559
- if (!token) {
45560
- return null;
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 requestedPath = match?.[2];
45563
- const suffix = requestedPath !== void 0 ? `/${requestedPath}` : "";
45564
- return {
45565
- token,
45566
- previewUrl: `/preview/${token}${suffix}${url.search}`
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 extractPreviewToken(previewUrl) {
45570
- return parsePreviewRequest(previewUrl)?.token ?? null;
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 buildPreviewAssetProxyPath(token, requestedPath) {
45573
- const search = new URLSearchParams({ token });
45574
- if (requestedPath) {
45575
- search.set("path", decodeURIComponent(requestedPath));
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
- return `/api/worker/preview-asset?${search.toString()}`;
45396
+ const body = Buffer.from(await proxyRes.arrayBuffer());
45397
+ res.status(proxyRes.status).send(body);
45578
45398
  }
45579
- var PreviewProxyManager = class {
45580
- sessions = /* @__PURE__ */ new Map();
45581
- register(token, nodeId, expiresAt) {
45582
- this.sessions.set(token, { token, nodeId, expiresAt });
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
- get(token) {
45585
- const session = this.sessions.get(token);
45586
- if (!session) {
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
- cleanup() {
45596
- const now = Date.now();
45597
- for (const [token, session] of this.sessions) {
45598
- if (session.expiresAt <= now) {
45599
- this.sessions.delete(token);
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
- function rewritePreviewSessionPayloadForProxy(manager, payload, nodeId) {
45605
- const token = extractPreviewToken(payload.previewUrl);
45606
- if (!token) {
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
- ...payload,
45612
- previewUrl: buildPreviewUrl("", {
45613
- token,
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
- async function handlePreviewProxyRequest(manager, nodeRegistry, logger27, req, res) {
45627
- const requestPath = req.originalUrl ?? req.url;
45628
- const previewOpenRequest = parsePreviewOpenRequest(requestPath);
45629
- if (previewOpenRequest) {
45630
- const session2 = manager.get(previewOpenRequest.token);
45631
- if (!session2) {
45632
- return false;
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
- const content = renderPreviewOpenDocument(previewOpenRequest.previewUrl);
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
- const session = manager.get(previewRequest.token);
45647
- if (!session) {
45648
- return false;
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
- const node = nodeRegistry.getNode(session.nodeId);
45651
- if (!node) {
45652
- throw new MeshyError("NODE_NOT_FOUND", `Preview worker ${session.nodeId} not found`, 404);
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 previewLog = logger27.child("preview-proxy");
45655
- const proxyPath = buildPreviewAssetProxyPath(previewRequest.token, previewRequest.requestedPath);
45656
- try {
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 TASK_OUTPUT_ROUTE_PREFIX = "/api/tasks";
45694
- function getTask(taskEngine, taskId) {
45695
- const task = taskEngine.getTask(taskId);
45696
- if (!task) {
45697
- throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
45698
- }
45699
- return task;
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 getTaskOutputRoot(taskEngine, taskId) {
45702
- const task = getTask(taskEngine, taskId);
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 buildTaskOutputDownloadUrl(taskId, filePath) {
45709
- return `${TASK_OUTPUT_ROUTE_PREFIX}/${taskId}/output/download?path=${encodeURIComponent(filePath)}`;
45527
+ function toArrayBuffer(value) {
45528
+ const copy = new Uint8Array(value.byteLength);
45529
+ copy.set(value);
45530
+ return copy.buffer;
45710
45531
  }
45711
- function getLocalTaskOutputSummary(taskEngine, taskId) {
45712
- const task = getTask(taskEngine, taskId);
45713
- if (!task.effectiveProjectPath) {
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
- return {
45724
- taskId: task.id,
45725
- requestedProject: task.project,
45726
- effectiveProjectPath: task.effectiveProjectPath,
45727
- assignedTo: task.assignedTo,
45728
- available: true,
45729
- summary: getOutputSummary(task.effectiveProjectPath)
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 getLocalTaskOutputTree(taskEngine, taskId, currentPath) {
45733
- const rootPath = getTaskOutputRoot(taskEngine, taskId);
45734
- try {
45735
- return {
45736
- rootPath,
45737
- currentPath,
45738
- entries: listDirectory(rootPath, currentPath)
45739
- };
45740
- } catch (err) {
45741
- if (err instanceof Error && err.message === "Path traversal detected") {
45742
- throw new MeshyError("VALIDATION_ERROR", "Invalid directory path", 400);
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 getLocalTaskOutputContent(taskEngine, taskId, filePath) {
45748
- const rootPath = getTaskOutputRoot(taskEngine, taskId);
45749
- try {
45750
- return {
45751
- ...readFileContent(rootPath, filePath),
45752
- downloadUrl: buildTaskOutputDownloadUrl(taskId, filePath)
45753
- };
45754
- } catch (err) {
45755
- if (err instanceof Error && err.message === "Path traversal detected") {
45756
- throw new MeshyError("VALIDATION_ERROR", "Invalid file path", 400);
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 (err instanceof Error && err.message === "File not found") {
45759
- throw new MeshyError("TASK_NOT_FOUND", `File not found: ${filePath}`, 404);
45573
+ if (normalizedKey === PREVIEW_AUTHORIZATION_HEADER) {
45574
+ previewAuthorization = Array.isArray(value) ? value[0] : value;
45575
+ continue;
45760
45576
  }
45761
- throw err;
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 getLocalTaskOutputDownload(taskEngine, taskId, filePath) {
45765
- const rootPath = getTaskOutputRoot(taskEngine, taskId);
45766
- try {
45767
- const absolutePath = resolveOutputPath(rootPath, filePath);
45768
- if (!fs17.existsSync(absolutePath) || !fs17.statSync(absolutePath).isFile()) {
45769
- throw new MeshyError("TASK_NOT_FOUND", `File not found: ${filePath}`, 404);
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
- const { mimeType } = classifyFile(absolutePath);
45772
- const fileName = path18.basename(absolutePath).replace(/"/g, "");
45773
- return {
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 (err instanceof Error && err.message === "Path traversal detected") {
45786
- throw new MeshyError("VALIDATION_ERROR", "Invalid file path", 400);
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
- const options = typeof optionsOrPath === "string" ? { path: optionsOrPath } : optionsOrPath ?? {};
45801
- getTask(deps.taskEngine, taskId);
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/node/agent-upgrade-service.ts
45826
- var import_node_child_process9 = require("child_process");
45827
-
45828
- // ../../packages/api/src/app/system-info.ts
45829
- var os6 = __toESM(require("os"), 1);
45830
- var import_node_child_process8 = require("child_process");
45831
- var RUNTIME_TOOLS = [
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
- path: null,
45878
- detail: normalizeOutput(result.stderr) ?? result.error ?? null
45621
+ absolutePath: resolvedPath,
45622
+ normalizedPath: path18.relative(normalizedRoot, resolvedPath).split(path18.sep).join("/")
45879
45623
  };
45880
45624
  }
45881
- function extractSemver(value) {
45882
- return value?.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? null;
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 normalizePackageVersion(output) {
45885
- if (!output) return null;
45886
- const trimmed = output.trim();
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 normalizeOutput(trimmed);
45636
+ return entryPath.replace(/\\/g, "/").replace(/^\/+/, "");
45895
45637
  }
45896
- function normalizeInstalledPackageVersion(output, packageName) {
45897
- if (!output) return null;
45898
- const trimmed = output.trim();
45899
- try {
45900
- const parsed = JSON.parse(trimmed);
45901
- const version2 = parsed.dependencies?.[packageName]?.version;
45902
- if (typeof version2 === "string") {
45903
- return version2.trim() || null;
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
- } catch {
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
- return extractSemver(normalizeOutput(trimmed));
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 resolveInstalledPackageVersion(definition, commandRunner) {
45910
- const result = commandRunner("npm", ["list", "-g", definition.packageName, "--depth=0", "--json"]);
45911
- return normalizeInstalledPackageVersion(result.stdout ?? null, definition.packageName);
45731
+ function escapeHtml(value) {
45732
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
45912
45733
  }
45913
- function compareSemver(left, right) {
45914
- const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10));
45915
- const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10));
45916
- for (let index = 0; index < 3; index += 1) {
45917
- const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
45918
- if (delta !== 0) return delta;
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
- return 0;
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 checkToolUpdate(definition, installedVersion, checkedAt, commandRunner, checkedOn, updateCache) {
45923
- const currentVersion = extractSemver(installedVersion);
45924
- const cached = updateCache.get(definition.packageName);
45925
- let latestVersion = cached?.latestVersion ?? null;
45926
- let detail = cached?.detail ?? null;
45927
- let updateCheckedAt = cached?.checkedAt ?? checkedAt;
45928
- if (cached?.checkedOn !== checkedOn) {
45929
- const latestResult = commandRunner("npm", ["view", definition.packageName, "version", "--json"]);
45930
- latestVersion = latestResult.status === 0 ? normalizePackageVersion(latestResult.stdout ?? null) : null;
45931
- detail = latestResult.status === 0 ? null : normalizeOutput(latestResult.stderr) ?? latestResult.error ?? "Update check failed";
45932
- updateCheckedAt = checkedAt;
45933
- updateCache.set(definition.packageName, {
45934
- checkedOn,
45935
- checkedAt,
45936
- latestVersion,
45937
- detail
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
- packageName: definition.packageName,
45942
- currentVersion,
45943
- latestVersion,
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 inspectTool(definition, commandRunner, checkedAt, checkedOn, updateCache, platform2) {
45950
- const resolved = resolveCommandPath(definition.command, commandRunner, platform2);
45951
- if (!resolved.path) {
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
- const versionResult = commandRunner(definition.command, ["--version"]);
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 inspectRuntimeTools(options = {}) {
45981
- const platform2 = options.platform ?? process.platform;
45982
- const commandRunner = options.commandRunner ?? ((command, args) => runRuntimeToolCommand(command, args, platform2));
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 getOsSnapshot() {
45990
- const cpus2 = os6.cpus();
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, "&amp;").replace(/"/g, "&quot;");
46001
45849
  }
46002
- function buildNodeSettingsSnapshot(options) {
46003
- const uptime = process.uptime();
46004
- const runtimeMetadata = options.runtimeMetadata;
46005
- const agents = (options.inspectRuntimeTools?.() ?? inspectRuntimeTools()).map((agent) => ({
46006
- ...agent,
46007
- metadataStatus: runtimeMetadata?.components?.[agent.id]?.status ?? null
46008
- }));
46009
- return {
46010
- collectedAt: Date.now(),
46011
- version: runtimeMetadata?.packageVersion ?? "0.1.0",
46012
- packageName: runtimeMetadata?.packageName ?? "meshy",
46013
- uptime,
46014
- auth: options.auth,
46015
- os: getOsSnapshot(),
46016
- runtime: {
46017
- nodeVersion: process.version,
46018
- pid: process.pid,
46019
- startedAt: Date.now() - Math.floor(uptime * 1e3),
46020
- cwd: process.cwd(),
46021
- workDir: options.workDir ?? null,
46022
- storagePath: options.storagePath ?? null,
46023
- localDashboardOrigin: options.localDashboardOrigin ?? null
46024
- },
46025
- agents,
46026
- startupRequirements: {
46027
- lastCheckedAt: runtimeMetadata?.startupRequirementsLastCheckedAt,
46028
- lastCheckedOn: runtimeMetadata?.startupRequirementsLastCheckedOn,
46029
- components: runtimeMetadata?.components ?? {}
46030
- },
46031
- components: runtimeMetadata?.components ?? {},
46032
- repository: runtimeMetadata?.repository ?? {},
46033
- packages: runtimeMetadata?.packages ?? {}
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
- // ../../packages/api/src/node/agent-upgrade-service.ts
46038
- var AGENT_PACKAGES = {
46039
- claude: "@anthropic-ai/claude-code",
46040
- codex: "@openai/codex"
46041
- };
46042
- var OUTPUT_LIMIT = 4e3;
46043
- function summarizeOutput(value) {
46044
- const text = value ?? "";
46045
- return text.length > OUTPUT_LIMIT ? `${text.slice(0, OUTPUT_LIMIT)}\u2026` : text;
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 defaultCommandRunner(command, args) {
46048
- const result = (0, import_node_child_process9.spawnSync)(command, args, {
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 isRuntimeAgentId(value) {
46062
- return value === "claude" || value === "codex";
45906
+ function isServiceHtmlContentType(contentType) {
45907
+ return normalizeContentType(contentType) === "text/html";
46063
45908
  }
46064
- function buildUpgradeArgs(agent, packageName) {
46065
- const args = ["install", "-g", `${packageName}@latest`];
46066
- if (agent === "claude") {
46067
- args.push("--include=optional", "--ignore-scripts=false");
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
- return args;
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 upgradeRuntimeAgent(agent, options = {}) {
46072
- const packageName = AGENT_PACKAGES[agent];
46073
- if (!packageName) {
46074
- throw new MeshyError("VALIDATION_ERROR", `Unsupported upgrade agent: ${agent}`, 400);
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 command = "npm";
46077
- const args = buildUpgradeArgs(agent, packageName);
46078
- const result = (options.commandRunner ?? defaultCommandRunner)(command, args);
46079
- const ok = result.status === 0 && !result.error;
46080
- if (ok) {
46081
- clearRuntimeToolUpdateCache(packageName);
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
- const detail = result.error ?? (summarizeOutput(result.stderr) || "Agent upgrade failed");
46084
- return {
46085
- ok,
46086
- agent,
46087
- packageName,
46088
- command,
46089
- args,
46090
- status: result.status,
46091
- stdout: summarizeOutput(result.stdout),
46092
- stderr: summarizeOutput(result.stderr),
46093
- detail: ok ? null : detail
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 upgradeRuntimeAgentForDeps(deps, agent) {
46097
- const result = deps.upgradeRuntimeAgent?.(agent) ?? upgradeRuntimeAgent(agent);
46098
- if (result.ok) {
46099
- const settingsSnapshot = deps.refreshSettingsSnapshot?.();
46100
- if (settingsSnapshot) {
46101
- deps.nodeRegistry.updateSettingsSnapshot(deps.nodeRegistry.getSelf().id, settingsSnapshot);
46102
- return { ...result, settingsSnapshot };
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
- // ../../packages/api/src/node/node-operation-service.ts
46109
- var fs18 = __toESM(require("fs"), 1);
46110
- var path19 = __toESM(require("path"), 1);
46111
- var import_node_crypto7 = require("crypto");
46112
- var NodeOperationFailure = class extends Error {
46113
- constructor(message, result) {
46114
- super(message);
46115
- this.result = result;
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
- var InMemoryNodeOperationStore = class {
46119
- operations = /* @__PURE__ */ new Map();
46120
- get(id) {
46121
- const operation = this.operations.get(id);
46122
- return operation ? clone2(operation) : null;
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
- list(filter = {}) {
46125
- return Array.from(this.operations.values()).filter((operation) => matchesFilter(operation, filter)).sort((a, b) => b.createdAt - a.createdAt).map((operation) => clone2(operation));
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
- upsert(operation) {
46128
- const copy = clone2(operation);
46129
- this.operations.set(copy.id, copy);
46130
- return clone2(copy);
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
- update(id, updates) {
46133
- const current = this.operations.get(id);
46134
- if (!current) return null;
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
- var FileNodeOperationStore = class {
46141
- constructor(storagePath) {
46142
- this.storagePath = storagePath;
46068
+ if (!isReadOnlyPreviewMethod(req?.method)) {
46069
+ res.writeHead(405, { "Content-Type": "text/plain" });
46070
+ res.end("Method not allowed");
46071
+ return;
46143
46072
  }
46144
- operations = /* @__PURE__ */ new Map();
46145
- loaded = false;
46146
- get(id) {
46147
- this.load();
46148
- const operation = this.operations.get(id);
46149
- return operation ? clone2(operation) : null;
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
- list(filter = {}) {
46152
- this.load();
46153
- return Array.from(this.operations.values()).filter((operation) => matchesFilter(operation, filter)).sort((a, b) => b.createdAt - a.createdAt).map((operation) => clone2(operation));
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
- upsert(operation) {
46156
- this.load();
46157
- const copy = clone2(operation);
46158
- this.operations.set(copy.id, copy);
46159
- this.persist();
46160
- return clone2(copy);
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
- update(id, updates) {
46163
- this.load();
46164
- const current = this.operations.get(id);
46165
- if (!current) return null;
46166
- const next = { ...current, ...clone2(updates), id, createdAt: current.createdAt };
46167
- this.operations.set(id, next);
46168
- this.persist();
46169
- return clone2(next);
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
- load() {
46172
- if (this.loaded) return;
46173
- this.loaded = true;
46174
- if (!fs18.existsSync(this.filePath)) return;
46175
- const raw = JSON.parse(fs18.readFileSync(this.filePath, "utf-8"));
46176
- const entries = Array.isArray(raw) ? raw : Object.values(raw);
46177
- for (const entry of entries) {
46178
- if (isNodeOperation(entry)) {
46179
- this.operations.set(entry.id, clone2(entry));
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
- persist() {
46184
- fs18.mkdirSync(this.storagePath, { recursive: true });
46185
- const body = JSON.stringify(Object.fromEntries(this.operations), null, 2);
46186
- const tmpPath = `${this.filePath}.tmp`;
46187
- fs18.writeFileSync(tmpPath, body, "utf-8");
46188
- fs18.renameSync(tmpPath, this.filePath);
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
- get filePath() {
46191
- return path19.join(this.storagePath, "node-operations.json");
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
- var NodeOperationService = class {
46195
- constructor(deps, store) {
46196
- this.deps = deps;
46197
- this.store = store;
46198
- this.registerHandler("agent.upgrade", runAgentUpgradeOperation);
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
- handlers = /* @__PURE__ */ new Map();
46201
- registerHandler(kind, handler) {
46202
- this.handlers.set(kind, handler);
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
- create(kind, nodeId, payload) {
46239
+ cleanup() {
46205
46240
  const now = Date.now();
46206
- const operation = {
46207
- id: (0, import_node_crypto7.randomUUID)(),
46208
- kind,
46209
- nodeId,
46210
- requestedByNodeId: this.deps.nodeRegistry.getSelf().id,
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
- list(filter) {
46224
- return this.store.list(filter);
46247
+ };
46248
+ function rewritePreviewSessionPayloadForProxy(manager, payload, nodeId) {
46249
+ const token = extractPreviewToken(payload.previewUrl);
46250
+ if (!token) {
46251
+ return payload;
46225
46252
  }
46226
- accept(operation) {
46227
- const current = this.store.get(operation.id);
46228
- if (current) {
46229
- if (current.status === "queued") this.scheduleRun(current.id);
46230
- return current;
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 saved = this.store.upsert({ ...operation, status: "queued", updatedAt: Date.now() });
46233
- this.deps.eventBus.emit("node.operation.created", { operation: saved });
46234
- this.scheduleRun(saved.id);
46235
- return saved;
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
- runLocal(id) {
46238
- this.scheduleRun(id);
46286
+ const previewRequest = parsePreviewRequest(requestPath);
46287
+ if (!previewRequest) {
46288
+ return false;
46239
46289
  }
46240
- applyRemoteUpdate(operation) {
46241
- const current = this.store.get(operation.id);
46242
- const saved = this.store.upsert(current ? { ...current, ...operation } : operation);
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
- markFailed(id, error) {
46248
- return this.update(id, {
46249
- status: "failed",
46250
- error,
46251
- completedAt: Date.now()
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
- scheduleRun(id) {
46255
- setTimeout(() => {
46256
- void this.run(id);
46257
- }, 0);
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
- async run(id) {
46260
- const operation = this.store.get(id);
46261
- if (!operation || operation.status !== "queued") return;
46262
- const running = this.update(id, { status: "running", startedAt: Date.now() });
46263
- if (!running) return;
46264
- try {
46265
- const handler = this.handlers.get(running.kind);
46266
- if (!handler) throw new Error(`No node operation handler registered for kind: ${running.kind}`);
46267
- const result = await handler(running, this.deps);
46268
- this.update(id, { status: "succeeded", result, completedAt: Date.now() });
46269
- } catch (err) {
46270
- this.update(id, {
46271
- status: "failed",
46272
- result: err instanceof NodeOperationFailure ? err.result : void 0,
46273
- error: err instanceof Error ? err.message : String(err),
46274
- completedAt: Date.now()
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
- update(id, updates) {
46279
- const saved = this.store.update(id, { ...updates, updatedAt: Date.now() });
46280
- if (!saved) return null;
46281
- this.applyOperationSideEffects(saved);
46282
- this.deps.eventBus.emit("node.operation.updated", { operation: saved });
46283
- void this.reportToLeader(saved);
46284
- return saved;
46285
- }
46286
- applyOperationSideEffects(operation) {
46287
- if (operation.kind !== "agent.upgrade" || operation.status !== "succeeded") return;
46288
- const settingsSnapshot = readSettingsSnapshot(operation.result);
46289
- if (settingsSnapshot) {
46290
- this.deps.nodeRegistry.updateSettingsSnapshot(operation.nodeId, settingsSnapshot);
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
- async reportToLeader(operation) {
46294
- if (this.deps.nodeRegistry.isLeader()) return;
46295
- const leaderEndpoint = this.deps.nodeRegistry.getLeaderEndpoint?.();
46296
- if (!leaderEndpoint) return;
46297
- try {
46298
- await fetch(`${leaderEndpoint}/api/worker/node-operations/${operation.id}`, applyRequestAuthHeaders({
46299
- method: "POST",
46300
- headers: { "Content-Type": "application/json" },
46301
- body: JSON.stringify(operation)
46302
- }));
46303
- } catch (err) {
46304
- this.deps.logger.child("node-operation").warn("failed to report node operation update to leader", {
46305
- operationId: operation.id,
46306
- kind: operation.kind,
46307
- error: err instanceof Error ? err.message : String(err)
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
- async function dispatchNodeOperation(deps, operation, node) {
46321
- const message = createNodeMessage("node.operation.execute", { operation });
46322
- const heartbeat = deps.heartbeat;
46323
- if (heartbeat.canPushToNode?.(node.id) === false) {
46324
- if (!heartbeat.enqueueNodeMessage) throw new Error(`Cannot queue node operation for ${node.id}`);
46325
- heartbeat.enqueueNodeMessage(node.id, message);
46326
- return;
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 client = new NodeMessageClient({
46329
- heartbeat: heartbeat.enqueueNodeMessage ? { enqueueNodeMessage: heartbeat.enqueueNodeMessage.bind(heartbeat) } : void 0,
46330
- logger: deps.logger
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
- await client.send(node, message);
46333
- }
46334
- function runAgentUpgradeOperation(operation, deps) {
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 result;
46342
- }
46343
- function readPayloadString(operation, key) {
46344
- const payload = operation.payload;
46345
- if (!payload || typeof payload !== "object") return "";
46346
- const value = payload[key];
46347
- return typeof value === "string" ? value : "";
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
- const handled = await maybeHandleRemoteNodeWorkDirBranchCreateRequest(req, res, nodeId);
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 { taskEngine } = req.app.locals.deps;
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 { taskEngine } = req.app.locals.deps;
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) {