@runweave/server-linux-x64 0.1.11 → 0.1.13

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/manifest.json CHANGED
@@ -6,8 +6,8 @@
6
6
  "protocolVersion": 1,
7
7
  "entry": "pty-host/index.cjs"
8
8
  },
9
- "version": "0.1.11",
10
- "sourceRevision": "7578c0782181a4db7b089ce1955082ecf8b53fcb",
9
+ "version": "0.1.13",
10
+ "sourceRevision": "da67d523de925abb4f2b958f949fd50a3ac2b5bb",
11
11
  "platform": "linux",
12
12
  "arch": "x64",
13
13
  "nodeModuleAbi": "127",
@@ -30,7 +30,7 @@
30
30
  {
31
31
  "path": "app-server/build-info.json",
32
32
  "size": 286,
33
- "sha256": "bbb0b4273c1f90b6f3577e33ea7da89d6f91b9596f5a6e9aedd2c560f720dd0e"
33
+ "sha256": "9c7c169c3553261b2cc8b7d83bc7d2fb7f7e16d75e8d3ae8bd8b0fa0ffc28307"
34
34
  },
35
35
  {
36
36
  "path": "app-server/configuration-compatibility.json",
@@ -40,7 +40,7 @@
40
40
  {
41
41
  "path": "app-server/index.cjs",
42
42
  "size": 1991601,
43
- "sha256": "51c5d8d6d0dcd308b4682ecb08ad681410bef9ff9d15f7cf1f26fb78ad1b0acc"
43
+ "sha256": "d99ba7fb4934b29a6cc499e8212fb1f098d01127b499b1b1d241b0ea20f28211"
44
44
  },
45
45
  {
46
46
  "path": "app-server/index.cjs.map",
@@ -55,7 +55,7 @@
55
55
  {
56
56
  "path": "backend/build-info.json",
57
57
  "size": 286,
58
- "sha256": "42555f54ba0b0546c76b8bedb9edbc46a68dd816151deab1608e10310dd903a1"
58
+ "sha256": "bd960ff86288c8d4553d3d0444a66ae4dec7d3f902d34b53810f00f4be4f931b"
59
59
  },
60
60
  {
61
61
  "path": "backend/dev-resources/control.cjs",
@@ -69,8 +69,8 @@
69
69
  },
70
70
  {
71
71
  "path": "backend/index.cjs",
72
- "size": 5746488,
73
- "sha256": "37c4df8a5e14a0fe94d3572fb7f6ddfbd0635a88b017710471b0ee90b68e2256"
72
+ "size": 5753065,
73
+ "sha256": "55247579573e991db66d087db63c29a17a3147ea2fb43560c1ef61eed54f23e7"
74
74
  },
75
75
  {
76
76
  "path": "backend/ios-simulators/device_shutdown.py",
@@ -505,12 +505,12 @@
505
505
  {
506
506
  "path": "frontend/dist/build-info.json",
507
507
  "size": 286,
508
- "sha256": "3bf893d05786880a4e9ee41f559595360ff1177a2f1714b7c4512814ed97322a"
508
+ "sha256": "77cffa6df1475be5a25363546a40125d700ae5736c04a33b80312ce440dbaca7"
509
509
  },
510
510
  {
511
511
  "path": "frontend/dist/companion.html",
512
512
  "size": 822,
513
- "sha256": "ff5dd3f246e8a17291f12f00b563aed5041782edf9d025fbbe470220091f458c"
513
+ "sha256": "724bf62ecacd1f23e651185eb112ff0f1d6f180076a31c4c61cd6801b69f7ba1"
514
514
  },
515
515
  {
516
516
  "path": "frontend/dist/icons/apple-touch-icon.png",
@@ -535,7 +535,7 @@
535
535
  {
536
536
  "path": "frontend/dist/index.html",
537
537
  "size": 1028,
538
- "sha256": "eedae45687039503548caa713d5e4f03dcb7457e5f59f34323c567aaed4a269d"
538
+ "sha256": "468e70be64ca45305732605cbc4f3c7e5338661a523783be8a705525c58a6c1c"
539
539
  },
540
540
  {
541
541
  "path": "frontend/dist/manifest.webmanifest",
@@ -1859,8 +1859,8 @@
1859
1859
  },
1860
1860
  {
1861
1861
  "path": "pty-host/index.cjs",
1862
- "size": 918397,
1863
- "sha256": "796bcb6e2b758c27db284ca11c5080a17f015ee1f880fca73aaf88e170c01bf5"
1862
+ "size": 918936,
1863
+ "sha256": "764642ecafe1b4e3f28ef9a84451e17d469257878e9730e73e6953eb060e2125"
1864
1864
  },
1865
1865
  {
1866
1866
  "path": "scripts/toolkit/codex-state.mjs",
package/native.tar.gz CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runweave/server-linux-x64",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "buildId": "20261109T095851541Z-7578c078",
4
- "sourceRevision": "7578c0782181a4db7b089ce1955082ecf8b53fcb",
3
+ "buildId": "20261109T123037865Z-da67d523",
4
+ "sourceRevision": "da67d523de925abb4f2b958f949fd50a3ac2b5bb",
5
5
  "sourceDirty": false,
6
- "builtAt": "2026-11-09T09:58:51.541Z",
6
+ "builtAt": "2026-11-09T12:30:37.865Z",
7
7
  "contentSha256": "ee24c2adfe7a38b0f7698d68d8493b560042b699baba92c63e79961518d9a9af"
8
8
  }
@@ -1,4 +1,4 @@
1
- /* runweave:build-info */var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T095851541Z-7578c078","sourceRevision":"7578c0782181a4db7b089ce1955082ecf8b53fcb","sourceDirty":false,"builtAt":"2026-11-09T09:58:51.541Z","contentSha256":"ee24c2adfe7a38b0f7698d68d8493b560042b699baba92c63e79961518d9a9af"};
1
+ /* runweave:build-info */var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T123037865Z-da67d523","sourceRevision":"da67d523de925abb4f2b958f949fd50a3ac2b5bb","sourceDirty":false,"builtAt":"2026-11-09T12:30:37.865Z","contentSha256":"ee24c2adfe7a38b0f7698d68d8493b560042b699baba92c63e79961518d9a9af"};
2
2
  const __IMPORT_META_URL__ = require('url').pathToFileURL(__filename).href;
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "buildId": "20261109T095851036Z-7578c078",
4
- "sourceRevision": "7578c0782181a4db7b089ce1955082ecf8b53fcb",
3
+ "buildId": "20261109T123037121Z-da67d523",
4
+ "sourceRevision": "da67d523de925abb4f2b958f949fd50a3ac2b5bb",
5
5
  "sourceDirty": false,
6
- "builtAt": "2026-11-09T09:58:51.036Z",
7
- "contentSha256": "594e17077399e80b4363f9e842cbedd629e811d5e8779f22aa6fc4b9aa4128f1"
6
+ "builtAt": "2026-11-09T12:30:37.121Z",
7
+ "contentSha256": "fd2cc32d5678e48707cff4a53f44897eb73eb0eff1b7584fc1da101e402cb2a2"
8
8
  }
@@ -1,4 +1,4 @@
1
- /* runweave:build-info */var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T095851036Z-7578c078","sourceRevision":"7578c0782181a4db7b089ce1955082ecf8b53fcb","sourceDirty":false,"builtAt":"2026-11-09T09:58:51.036Z","contentSha256":"594e17077399e80b4363f9e842cbedd629e811d5e8779f22aa6fc4b9aa4128f1"};
1
+ /* runweave:build-info */var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T123037121Z-da67d523","sourceRevision":"da67d523de925abb4f2b958f949fd50a3ac2b5bb","sourceDirty":false,"builtAt":"2026-11-09T12:30:37.121Z","contentSha256":"fd2cc32d5678e48707cff4a53f44897eb73eb0eff1b7584fc1da101e402cb2a2"};
2
2
  const __IMPORT_META_URL__ = require('url').pathToFileURL(__filename).href;
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -71434,6 +71434,21 @@ function buildAgentResumeArgs(agent, thread) {
71434
71434
  return [resumeFlags[agent] ?? "resume", thread];
71435
71435
  }
71436
71436
 
71437
+ // packages/shared/src/terminal/shell.ts
71438
+ function terminalShellName(command) {
71439
+ return (command.split(/[\\/]/).at(-1) ?? command).toLowerCase().replace(/\.exe$/, "");
71440
+ }
71441
+ function isInteractiveShellLaunch(launch) {
71442
+ const name = terminalShellName(launch.command);
71443
+ if (!["sh", "bash", "zsh", "dash", "ksh", "fish", "powershell", "pwsh", "cmd"].includes(name)) return false;
71444
+ return !(launch.args ?? []).some((arg) => {
71445
+ const value = arg.toLowerCase();
71446
+ if (name === "cmd") return !/^\/(?:d|q|a|u|e:(?:on|off)|f:(?:on|off)|v:(?:on|off))$/.test(value);
71447
+ if (name === "powershell" || name === "pwsh") return !["-nologo", "-noprofile", "-noexit", "-login", "-l"].includes(value);
71448
+ return ["--help", "--version"].includes(value) || !value.startsWith("-") || value === "--" || /^-[^-]*[cs]/.test(value) || /^--(?:command|init-command)(?:=|$)/.test(value);
71449
+ });
71450
+ }
71451
+
71437
71452
  // backend/src/terminal/runtime/pi-editor.ts
71438
71453
  var import_node_crypto12 = require("node:crypto");
71439
71454
  var import_node_http = require("node:http");
@@ -71575,7 +71590,19 @@ function buildAgentResumeCommand(thread) {
71575
71590
  thread.sessionFile ?? thread.threadId
71576
71591
  )
71577
71592
  ];
71578
- return `${shellQuote(thread.executable ?? thread.provider)} ${args.map(shellQuote).join(" ")}
71593
+ const configured = thread.provider === "codex" ? settingText("agents.codex.binary")?.trim() : ["trae", "traex", "traecli"].includes(thread.provider) ? settingText("agents.traex.binary")?.trim() : void 0;
71594
+ const executable = thread.executable ?? configured ?? thread.provider;
71595
+ const shell = terminalShellName(thread.shell ?? "sh");
71596
+ if (["powershell", "pwsh", "cmd"].includes(shell)) {
71597
+ const parts = [executable, ...args];
71598
+ if (parts.some((part) => [...part].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)))
71599
+ throw new Error("Agent resume arguments contain terminal control characters");
71600
+ const command = `& ${parts.map((part) => `'${part.replaceAll("'", "''")}'`).join(" ")}`;
71601
+ return shell === "cmd" ? `powershell.exe -NoProfile -EncodedCommand ${Buffer.from(command, "utf16le").toString("base64")}
71602
+ ` : `${command}
71603
+ `;
71604
+ }
71605
+ return `${shellQuote(executable)} ${args.map(shellQuote).join(" ")}
71579
71606
  `;
71580
71607
  }
71581
71608
  function shellQuote(value) {
@@ -71878,21 +71905,6 @@ var aiDiagnosticLog = createAiDiagnosticLog({
71878
71905
  source: "backend"
71879
71906
  });
71880
71907
 
71881
- // packages/shared/src/terminal/shell.ts
71882
- function terminalShellName(command) {
71883
- return (command.split(/[\\/]/).at(-1) ?? command).toLowerCase().replace(/\.exe$/, "");
71884
- }
71885
- function isInteractiveShellLaunch(launch) {
71886
- const name = terminalShellName(launch.command);
71887
- if (!["sh", "bash", "zsh", "dash", "ksh", "fish", "powershell", "pwsh", "cmd"].includes(name)) return false;
71888
- return !(launch.args ?? []).some((arg) => {
71889
- const value = arg.toLowerCase();
71890
- if (name === "cmd") return !/^\/(?:d|q|a|u|e:(?:on|off)|f:(?:on|off)|v:(?:on|off))$/.test(value);
71891
- if (name === "powershell" || name === "pwsh") return !["-nologo", "-noprofile", "-noexit", "-login", "-l"].includes(value);
71892
- return ["--help", "--version"].includes(value) || !value.startsWith("-") || value === "--" || /^-[^-]*[cs]/.test(value) || /^--(?:command|init-command)(?:=|$)/.test(value);
71893
- });
71894
- }
71895
-
71896
71908
  // backend/src/terminal/runtime/pty-host-client.ts
71897
71909
  var import_node_crypto15 = require("node:crypto");
71898
71910
 
@@ -71985,10 +71997,7 @@ var PtyHostClient = class _PtyHostClient {
71985
71997
  });
71986
71998
  this.socket.on("close", () => {
71987
71999
  this.connected = false;
71988
- for (const request2 of this.pending.values()) {
71989
- clearTimeout(request2.timer);
71990
- request2.reject(new Error("HOST_CONNECTION_LOST"));
71991
- }
72000
+ for (const request2 of this.pending.values()) request2.reject(new Error("HOST_CONNECTION_LOST"));
71992
72001
  this.pending.clear();
71993
72002
  for (const listener of this.disconnected) listener();
71994
72003
  });
@@ -71997,8 +72006,6 @@ var PtyHostClient = class _PtyHostClient {
71997
72006
  if (message2.requestId) {
71998
72007
  const request2 = this.pending.get(message2.requestId);
71999
72008
  if (!request2) return;
72000
- clearTimeout(request2.timer);
72001
- this.pending.delete(message2.requestId);
72002
72009
  if (message2.error) request2.reject(new Error(message2.error));
72003
72010
  else request2.resolve(message2.result);
72004
72011
  } else if (message2.type) for (const listener of this.events) listener(value);
@@ -72030,16 +72037,38 @@ var PtyHostClient = class _PtyHostClient {
72030
72037
  throw error2;
72031
72038
  }
72032
72039
  }
72033
- request(action, body2 = {}) {
72040
+ /** Aborting only releases this caller's wait; it does not cancel or replay Host operations. */
72041
+ request(action, body2 = {}, signal) {
72042
+ if (signal?.aborted) return Promise.reject(new Error("HOST_REQUEST_ABORTED"));
72034
72043
  if (!this.connected) return Promise.reject(new Error("HOST_CONNECTION_LOST"));
72035
72044
  const requestId = (0, import_node_crypto14.randomUUID)();
72036
72045
  return new Promise((resolve, reject) => {
72037
- const timer = setTimeout(() => {
72046
+ const cleanup = () => {
72047
+ clearTimeout(timer);
72048
+ signal?.removeEventListener("abort", abort);
72038
72049
  this.pending.delete(requestId);
72039
- reject(new Error("HOST_REQUEST_UNCERTAIN"));
72040
- }, 15e3);
72041
- this.pending.set(requestId, { resolve: (value) => resolve(value), reject, timer });
72042
- sendJson(this.socket, { requestId, token: this.token, hostInstanceId: this.descriptor.hostInstanceId, action, body: body2 });
72050
+ };
72051
+ const request2 = {
72052
+ resolve: (value) => {
72053
+ cleanup();
72054
+ resolve(value);
72055
+ },
72056
+ reject: (error2) => {
72057
+ cleanup();
72058
+ reject(error2);
72059
+ }
72060
+ };
72061
+ const abort = () => request2.reject(new Error("HOST_REQUEST_ABORTED"));
72062
+ const timer = setTimeout(() => request2.reject(new Error("HOST_REQUEST_UNCERTAIN")), 15e3);
72063
+ this.pending.set(requestId, request2);
72064
+ signal?.addEventListener("abort", abort, { once: true });
72065
+ try {
72066
+ if (!sendJson(this.socket, { requestId, token: this.token, hostInstanceId: this.descriptor.hostInstanceId, action, body: body2 })) {
72067
+ request2.reject(new Error("HOST_CONNECTION_LOST"));
72068
+ }
72069
+ } catch (error2) {
72070
+ request2.reject(error2);
72071
+ }
72043
72072
  });
72044
72073
  }
72045
72074
  close() {
@@ -72052,27 +72081,37 @@ var ownerId = (0, import_node_crypto15.randomUUID)();
72052
72081
  var connection;
72053
72082
  var generation;
72054
72083
  var hostInstanceId;
72084
+ var upstreams;
72055
72085
  var stopped = false;
72056
72086
  function usesPtyHost() {
72057
72087
  return process.platform === "win32" && Boolean(process.env.RUNWEAVE_RUNTIME_RELEASE_ID);
72058
72088
  }
72059
72089
  async function getPtyHost() {
72060
72090
  if (stopped) throw new Error("HOST_CLIENT_STOPPED");
72061
- connection ??= PtyHostClient.connect(configuration().context, ownerId, generation, hostInstanceId).then((client) => {
72091
+ connection ??= PtyHostClient.connect(configuration().context, ownerId, generation, hostInstanceId).then(async (client) => {
72062
72092
  generation = client.generation;
72063
72093
  hostInstanceId = client.descriptor.hostInstanceId;
72064
72094
  client.disconnected.add(() => {
72065
72095
  connection = void 0;
72066
72096
  });
72067
- return client;
72097
+ try {
72098
+ if (upstreams) await client.request("setUpstreams", upstreams);
72099
+ return client;
72100
+ } catch (error2) {
72101
+ client.close();
72102
+ throw error2;
72103
+ }
72068
72104
  }).catch((error2) => {
72069
72105
  connection = void 0;
72070
72106
  throw error2;
72071
72107
  });
72072
72108
  return connection;
72073
72109
  }
72074
- async function syncPtyHostUpstreams(upstreams) {
72075
- if (usesPtyHost()) await (await getPtyHost()).request("setUpstreams", upstreams);
72110
+ async function syncPtyHostUpstreams(value) {
72111
+ if (usesPtyHost()) {
72112
+ upstreams = { ...value };
72113
+ await (await getPtyHost()).request("setUpstreams", value);
72114
+ }
72076
72115
  }
72077
72116
  async function createHostedPty(options) {
72078
72117
  const client = await getPtyHost();
@@ -73169,63 +73208,139 @@ function createTerminalRuntimeRecorder(terminalSessionManager, terminalSessionId
73169
73208
  };
73170
73209
  }
73171
73210
 
73211
+ // backend/src/terminal/runtime/recovery-policy.ts
73212
+ var RebuildWindowMs = 6e4;
73213
+ var MaxRebuildAttempts = 3;
73214
+ var TerminalRebuildLimitError = class extends Error {
73215
+ constructor(terminalSessionId, count, windowMs, maxAttempts, runtime = "terminal") {
73216
+ super(`${runtime} session rebuild exceeded ${maxAttempts} attempts in ${windowMs} ms for ${terminalSessionId}`);
73217
+ this.terminalSessionId = terminalSessionId;
73218
+ this.count = count;
73219
+ this.windowMs = windowMs;
73220
+ this.maxAttempts = maxAttempts;
73221
+ this.name = "TerminalRebuildLimitError";
73222
+ }
73223
+ };
73224
+ function recordTerminalRebuildAttempt(history, terminalSessionId, now2 = Date.now()) {
73225
+ const attempts = (history.get(terminalSessionId) ?? []).filter((timestamp3) => timestamp3 >= now2 - RebuildWindowMs);
73226
+ history.set(terminalSessionId, attempts);
73227
+ if (attempts.length >= MaxRebuildAttempts)
73228
+ throw new TerminalRebuildLimitError(terminalSessionId, attempts.length + 1, RebuildWindowMs, MaxRebuildAttempts);
73229
+ attempts.push(now2);
73230
+ return { allowed: true, count: attempts.length, windowMs: RebuildWindowMs, maxAttempts: MaxRebuildAttempts };
73231
+ }
73232
+ function resolveAgentThreadToResume(session) {
73233
+ const activeProvider = getAgentForCommand(session.activeCommand) ?? (session.terminalState?.state.startsWith("agent_") ? session.terminalState.agent : null);
73234
+ if (!activeProvider) return null;
73235
+ const currentThreadId = session.threadId?.trim();
73236
+ const currentProvider = session.threadProvider ?? (currentThreadId ? "codex" : void 0);
73237
+ if (currentThreadId && currentProvider === activeProvider)
73238
+ return { provider: activeProvider, threadId: currentThreadId };
73239
+ const recentThreadId = session.lastThreadId?.trim();
73240
+ return recentThreadId && session.lastThreadProvider === activeProvider ? { provider: activeProvider, threadId: recentThreadId } : null;
73241
+ }
73242
+
73172
73243
  // backend/src/terminal/runtime/pty-host-runtime.ts
73173
73244
  var launches = /* @__PURE__ */ new Map();
73245
+ var rebuildAttempts = /* @__PURE__ */ new Map();
73174
73246
  function ensureHostedTerminal(options) {
73175
73247
  const previous = launches.get(options.session.id);
73176
73248
  if (previous) return previous;
73177
- const launch = (async () => {
73178
- const { session, terminalSessionManager, runtimeRegistry } = options;
73179
- const existing = runtimeRegistry.getSessionRuntime(session.id);
73180
- if (existing) return existing;
73181
- let binding = session.ptyHost;
73182
- const creating = !binding;
73183
- if (!binding) {
73184
- binding = { hostInstanceId: (await getPtyHost()).descriptor.hostInstanceId, sessionInstanceId: (0, import_node_crypto16.randomUUID)() };
73185
- await terminalSessionManager.updateRuntimeMetadata(session.id, { runtimeKind: "pty", ptyHost: binding, recoverable: true });
73186
- }
73187
- const fallback = creating ? resolveTerminalFallbackLaunchConfig(session) : null;
73188
- const terminalEnv = buildTerminalRuntimeEnvironment({ terminalSessionId: session.id, projectId: session.projectId, source: session.source });
73189
- const runtime = await createHostedPty({
73190
- terminalSessionId: session.id,
73191
- binding,
73192
- onLaunch: async (launch2) => {
73193
- if (launch2.fallbackReason) logger.warn("terminal.pty-host.fallback.activated", { terminalSessionId: session.id, command: launch2.command, reason: launch2.fallbackReason });
73194
- await terminalSessionManager.updateSessionLaunch(session.id, { command: launch2.command, args: launch2.args });
73195
- },
73196
- create: creating ? {
73197
- sessionInstanceId: binding.sessionInstanceId,
73198
- terminalSessionId: session.id,
73199
- projectId: session.projectId,
73200
- command: session.command,
73201
- args: session.args,
73202
- cwd: session.cwd,
73203
- cols: 80,
73204
- rows: 24,
73205
- env: buildPtyEnv(process.env, terminalEnv, session.command),
73206
- ...fallback ? { fallback: { ...fallback, env: buildPtyEnv(process.env, terminalEnv, fallback.command) } } : {}
73207
- } : void 0
73208
- });
73209
- runtimeRegistry.createRuntime(session.id, runtime);
73210
- runtimeRegistry.ensureRecorder(session.id, createTerminalRuntimeRecorder(terminalSessionManager, session.id));
73211
- return runtime;
73212
- })().catch((error2) => {
73213
- if (error2 instanceof Error && error2.message === "HOST_SESSION_GONE") {
73214
- options.terminalSessionManager.markExited(options.session.id);
73215
- }
73216
- throw error2;
73217
- }).finally(() => {
73249
+ const launch = attachOrRebuild(options).finally(() => {
73218
73250
  launches.delete(options.session.id);
73219
73251
  });
73220
73252
  launches.set(options.session.id, launch);
73221
73253
  return launch;
73222
73254
  }
73255
+ async function attachOrRebuild(options) {
73256
+ const { session, terminalSessionManager, runtimeRegistry } = options;
73257
+ const existing = runtimeRegistry.getSessionRuntime(session.id);
73258
+ if (existing) return { runtime: existing };
73259
+ let runtime;
73260
+ let warning;
73261
+ try {
73262
+ runtime = await launchHostedTerminal(options, !session.ptyHost);
73263
+ } catch (error2) {
73264
+ if (!(error2 instanceof Error) || error2.message !== "HOST_SESSION_GONE" || session.status !== "running") throw error2;
73265
+ ({ runtime, warning } = await rebuildHostedTerminal(options));
73266
+ }
73267
+ runtimeRegistry.createRuntime(session.id, runtime);
73268
+ runtimeRegistry.ensureRecorder(session.id, createTerminalRuntimeRecorder(terminalSessionManager, session.id));
73269
+ return { runtime, warning };
73270
+ }
73271
+ async function launchHostedTerminal(options, creating) {
73272
+ const { session, terminalSessionManager } = options;
73273
+ let binding = session.ptyHost;
73274
+ if (creating) {
73275
+ binding = { hostInstanceId: (await getPtyHost()).descriptor.hostInstanceId, sessionInstanceId: (0, import_node_crypto16.randomUUID)() };
73276
+ await terminalSessionManager.updateRuntimeMetadata(session.id, { runtimeKind: "pty", ptyHost: binding, recoverable: true });
73277
+ }
73278
+ if (!binding) throw new Error("HOST_SESSION_GONE");
73279
+ const fallback = creating ? resolveTerminalFallbackLaunchConfig(session) : null;
73280
+ const terminalEnv = buildTerminalRuntimeEnvironment({ terminalSessionId: session.id, projectId: session.projectId, source: session.source });
73281
+ return createHostedPty({
73282
+ terminalSessionId: session.id,
73283
+ binding,
73284
+ onLaunch: async (launch) => {
73285
+ if (launch.fallbackReason) logger.warn("terminal.pty-host.fallback.activated", { terminalSessionId: session.id, command: launch.command, reason: launch.fallbackReason });
73286
+ await terminalSessionManager.updateSessionLaunch(session.id, { command: launch.command, args: launch.args });
73287
+ },
73288
+ create: creating ? {
73289
+ sessionInstanceId: binding.sessionInstanceId,
73290
+ terminalSessionId: session.id,
73291
+ projectId: session.projectId,
73292
+ command: session.command,
73293
+ args: session.args,
73294
+ cwd: session.cwd,
73295
+ cols: 80,
73296
+ rows: 24,
73297
+ env: buildPtyEnv(process.env, terminalEnv, session.command),
73298
+ ...fallback ? { fallback: { ...fallback, env: buildPtyEnv(process.env, terminalEnv, fallback.command) } } : {}
73299
+ } : void 0
73300
+ });
73301
+ }
73302
+ async function rebuildHostedTerminal(options) {
73303
+ const { session, terminalSessionManager } = options;
73304
+ const interactive = isInteractiveShellLaunch(session);
73305
+ const thread = interactive ? resolveAgentThreadToResume(session) : null;
73306
+ let warning = "Command exited; returned to a shell.";
73307
+ if (interactive) {
73308
+ try {
73309
+ const attempt = recordTerminalRebuildAttempt(rebuildAttempts, session.id);
73310
+ warning = thread ? `Original PTY Host session was lost; starting ${thread.provider} resume from saved threadId (${attempt.count}/${attempt.maxAttempts}).` : `Original PTY Host session was lost; created a fresh terminal session (${attempt.count}/${attempt.maxAttempts}).`;
73311
+ } catch (error2) {
73312
+ if (error2 instanceof TerminalRebuildLimitError) {
73313
+ await terminalSessionManager.updateRuntimeMetadata(session.id, { runtimeKind: "pty", ptyHost: session.ptyHost, recoverable: false });
73314
+ terminalSessionManager.markExited(session.id, 1);
73315
+ }
73316
+ throw error2;
73317
+ }
73318
+ } else {
73319
+ await terminalSessionManager.updateSessionLaunch(session.id, resolveDefaultTerminalLaunchConfig());
73320
+ await terminalSessionManager.updateSessionMetadata(session.id, { cwd: session.cwd, activeCommand: null });
73321
+ terminalSessionManager.markRunning(session.id);
73322
+ }
73323
+ const sessionFile = thread ? await getAgentAdapter(thread.provider).resolveResumeTarget(thread.threadId, session.pi) : void 0;
73324
+ const runtime = await launchHostedTerminal(options, true);
73325
+ try {
73326
+ if (thread) {
73327
+ const command = buildAgentResumeCommand({ ...thread, sessionFile, shell: session.command });
73328
+ await runtime.hosted.writeSteps([{ type: "text", data: command.replace(/\n$/, "") }, { type: "delay", ms: 200 }, { type: "text", data: "\r" }], `recovery:${session.ptyHost.sessionInstanceId}`);
73329
+ await terminalSessionManager.updateSessionThreadId(session.id, thread.threadId, thread.provider);
73330
+ }
73331
+ } catch (error2) {
73332
+ runtime.dispose();
73333
+ throw error2;
73334
+ }
73335
+ logger.warn("terminal.pty-host.session-missing.rebuild", { terminalSessionId: session.id, warning });
73336
+ return { runtime, warning };
73337
+ }
73223
73338
  async function reconcileHostedTerminals(manager, registry2) {
73224
73339
  if (!usesPtyHost()) return;
73225
73340
  for (const session of manager.listSessions()) {
73226
73341
  if (session.runtimeKind !== "pty" || session.status !== "running") continue;
73227
73342
  if (!session.ptyHost) {
73228
- if (session.status === "running") manager.markExited(session.id);
73343
+ manager.markExited(session.id);
73229
73344
  continue;
73230
73345
  }
73231
73346
  try {
@@ -73279,8 +73394,6 @@ var import_node_fs16 = require("node:fs");
73279
73394
  var import_node_path25 = __toESM(require("node:path"), 1);
73280
73395
  var RUNWEAVE_TMUX_PREFIX = "runweave-";
73281
73396
  var TmuxCommandTimeoutMs = 5e3;
73282
- var RebuildWindowMs = 6e4;
73283
- var MaxRebuildAttempts = 3;
73284
73397
  var CaptureHistoryLines = 5e3;
73285
73398
  var InteractivePaneReadyMinWaitMs = 1e3;
73286
73399
  var InteractivePaneReadyStableMs = 200;
@@ -74033,28 +74146,13 @@ var TmuxSessionService = class extends TmuxProcess {
74033
74146
  return orphanedSessions;
74034
74147
  }
74035
74148
  recordRebuildAttempt(terminalSessionId) {
74036
- const now2 = this.now();
74037
- const windowStart = now2 - RebuildWindowMs;
74038
- const attempts = (this.rebuildAttempts.get(terminalSessionId) ?? []).filter(
74039
- (timestamp3) => timestamp3 >= windowStart
74040
- );
74041
- if (attempts.length >= MaxRebuildAttempts) {
74042
- this.rebuildAttempts.set(terminalSessionId, attempts);
74043
- throw new TmuxRebuildLimitError(
74044
- terminalSessionId,
74045
- attempts.length + 1,
74046
- RebuildWindowMs,
74047
- MaxRebuildAttempts
74048
- );
74149
+ try {
74150
+ return recordTerminalRebuildAttempt(this.rebuildAttempts, terminalSessionId, this.now());
74151
+ } catch (error2) {
74152
+ if (error2 instanceof TerminalRebuildLimitError)
74153
+ throw new TmuxRebuildLimitError(error2.terminalSessionId, error2.count, error2.windowMs, error2.maxAttempts);
74154
+ throw error2;
74049
74155
  }
74050
- attempts.push(now2);
74051
- this.rebuildAttempts.set(terminalSessionId, attempts);
74052
- return {
74053
- allowed: true,
74054
- count: attempts.length,
74055
- windowMs: RebuildWindowMs,
74056
- maxAttempts: MaxRebuildAttempts
74057
- };
74058
74156
  }
74059
74157
  };
74060
74158
 
@@ -74795,7 +74893,7 @@ async function ensureTerminalRuntime(options) {
74795
74893
  };
74796
74894
  });
74797
74895
  }
74798
- if (options.session.ptyHost || usesPtyHost()) return { runtime: await ensureHostedTerminal(options) };
74896
+ if (options.session.ptyHost || usesPtyHost()) return ensureHostedTerminal(options);
74799
74897
  const runtime = options.ptyService.spawnSession({
74800
74898
  command: options.session.command,
74801
74899
  args: options.session.args,
@@ -74835,19 +74933,6 @@ async function ensureTerminalRuntime(options) {
74835
74933
  function isInteractiveShellLaunch2(command, args) {
74836
74934
  return isInteractiveShellLaunch({ command, args });
74837
74935
  }
74838
- function resolveAgentThreadToResume(session) {
74839
- const activeProvider = getAgentForCommand(session.activeCommand);
74840
- if (!activeProvider) {
74841
- return null;
74842
- }
74843
- const currentThreadId = session.threadId?.trim();
74844
- const currentProvider = session.threadProvider ?? (currentThreadId ? "codex" : void 0);
74845
- if (currentThreadId && currentProvider === activeProvider) {
74846
- return { provider: activeProvider, threadId: currentThreadId };
74847
- }
74848
- const recentThreadId = session.lastThreadId?.trim();
74849
- return recentThreadId && session.lastThreadProvider === activeProvider ? { provider: activeProvider, threadId: recentThreadId } : null;
74850
- }
74851
74936
  function createTmuxInputPacedRuntime(runtime) {
74852
74937
  const queuedInput = [];
74853
74938
  let holdInputUntil = 0;
@@ -112240,9 +112325,6 @@ function registerDesktopNetworkRoutes(app, requireAuth) {
112240
112325
  });
112241
112326
  }
112242
112327
 
112243
- // backend/src/remote/routes.ts
112244
- var import_node_crypto57 = require("node:crypto");
112245
-
112246
112328
  // packages/shared/src/remote/index.ts
112247
112329
  function isRemoteBrowserProfileState(value) {
112248
112330
  if (!value || typeof value !== "object") return false;
@@ -112569,13 +112651,63 @@ function registerRemoteCapabilitiesRoute(app, requireTunnelAuth, requireAuth, br
112569
112651
  });
112570
112652
  }
112571
112653
 
112572
- // backend/src/remote/routes.ts
112573
- function matchesHookToken(value, expected) {
112574
- if (typeof value !== "string" || !expected) return false;
112654
+ // backend/src/remote/terminal-hook-auth.ts
112655
+ var import_node_crypto57 = require("node:crypto");
112656
+ function matchesToken2(value, expected) {
112657
+ if (!expected) return false;
112575
112658
  const supplied = Buffer.from(value);
112576
112659
  const actual = Buffer.from(expected);
112577
112660
  return supplied.length === actual.length && (0, import_node_crypto57.timingSafeEqual)(supplied, actual);
112578
112661
  }
112662
+ async function authenticatesTerminal(value, terminalId, sessions, signal) {
112663
+ signal.throwIfAborted();
112664
+ if (typeof value !== "string" || !value) return false;
112665
+ if (matchesToken2(value, process.env.RUNWEAVE_HOOK_TOKEN)) return true;
112666
+ if (!usesPtyHost()) return false;
112667
+ const session = sessions.getSession(terminalId);
112668
+ const binding = session?.ptyHost;
112669
+ if (!binding || session.status !== "running") return false;
112670
+ const host = await getPtyHost();
112671
+ signal.throwIfAborted();
112672
+ if (host.descriptor.hostInstanceId !== binding.hostInstanceId) return false;
112673
+ const hosted = (await host.request("listSessions", {}, signal)).find(
112674
+ (item) => item.terminalSessionId === terminalId && item.hostInstanceId === binding.hostInstanceId && item.sessionInstanceId === binding.sessionInstanceId
112675
+ );
112676
+ return hosted?.status === "running" && matchesToken2(value, hosted.gatewayToken);
112677
+ }
112678
+ function createRequireTerminalHook(sessions) {
112679
+ return async (req, res, next) => {
112680
+ if (res.destroyed) return;
112681
+ const controller = new AbortController();
112682
+ const cancel = () => controller.abort();
112683
+ const aborted2 = new Promise((_resolve, reject) => {
112684
+ controller.signal.onabort = () => reject(new Error("Terminal authentication aborted"));
112685
+ });
112686
+ const timeout = setTimeout(cancel, 4e3);
112687
+ res.once("close", cancel);
112688
+ try {
112689
+ const authenticated = await Promise.race([
112690
+ authenticatesTerminal(req.headers["x-runweave-hook-token"], String(req.params.id), sessions, controller.signal),
112691
+ aborted2
112692
+ ]);
112693
+ controller.signal.throwIfAborted();
112694
+ if (res.destroyed) return;
112695
+ if (!authenticated) {
112696
+ res.sendStatus(401);
112697
+ return;
112698
+ }
112699
+ next();
112700
+ } catch {
112701
+ if (!res.destroyed) res.status(503).json({ code: "DESKTOP_UNAVAILABLE", message: "Terminal runtime authentication is unavailable" });
112702
+ } finally {
112703
+ clearTimeout(timeout);
112704
+ res.off("close", cancel);
112705
+ controller.signal.onabort = null;
112706
+ }
112707
+ };
112708
+ }
112709
+
112710
+ // backend/src/remote/routes.ts
112579
112711
  function sendError(res, error2) {
112580
112712
  if (error2 instanceof RemoteBrowserError) {
112581
112713
  res.status(error2.status).json({ code: error2.code, message: error2.message });
@@ -112586,14 +112718,11 @@ function sendError(res, error2) {
112586
112718
  function registerRemoteBrowserRoutes(app, auth, sessions, requireTunnelAuth, browserProfileDir, backendId) {
112587
112719
  const bindings = new DesktopBrowserBindings(sessions);
112588
112720
  const requireAuth = createRequireAuth(auth);
112721
+ const requireTerminalHook = createRequireTerminalHook(sessions);
112589
112722
  registerDesktopNetworkRoutes(app, requireAuth);
112590
112723
  registerRemoteCapabilitiesRoute(app, requireTunnelAuth, requireAuth, browserProfileDir, backendId);
112591
- app.get("/api/terminal/session/:id/runtime-identity", (req, res) => {
112592
- if (!matchesHookToken(req.headers["x-runweave-hook-token"], process.env.RUNWEAVE_HOOK_TOKEN)) {
112593
- res.sendStatus(401);
112594
- return;
112595
- }
112596
- const session = sessions.getSession(req.params.id);
112724
+ app.get("/api/terminal/session/:id/runtime-identity", requireTerminalHook, (req, res) => {
112725
+ const session = sessions.getSession(String(req.params.id));
112597
112726
  if (!session) {
112598
112727
  res.status(404).json({ code: "terminal_session_not_found" });
112599
112728
  return;
@@ -112650,18 +112779,14 @@ function registerRemoteBrowserRoutes(app, auth, sessions, requireTunnelAuth, bro
112650
112779
  sendError(res, error2);
112651
112780
  }
112652
112781
  });
112653
- app.post("/api/terminal/session/:id/browser/capability", (req, res) => {
112654
- if (!matchesHookToken(req.headers["x-runweave-hook-token"], process.env.RUNWEAVE_HOOK_TOKEN)) {
112655
- res.sendStatus(401);
112656
- return;
112657
- }
112782
+ app.post("/api/terminal/session/:id/browser/capability", requireTerminalHook, (req, res) => {
112658
112783
  const projectId = req.body?.projectId;
112659
112784
  if (typeof projectId !== "string") {
112660
112785
  res.sendStatus(400);
112661
112786
  return;
112662
112787
  }
112663
112788
  try {
112664
- res.json({ capability: bindings.issueCapability(req.params.id, projectId), expiresIn: 3600 });
112789
+ res.json({ capability: bindings.issueCapability(String(req.params.id), projectId), expiresIn: 3600 });
112665
112790
  } catch (error2) {
112666
112791
  sendError(res, error2);
112667
112792
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "buildId": "20261109T095850254Z-7578c078",
4
- "sourceRevision": "7578c0782181a4db7b089ce1955082ecf8b53fcb",
3
+ "buildId": "20261109T123036078Z-da67d523",
4
+ "sourceRevision": "da67d523de925abb4f2b958f949fd50a3ac2b5bb",
5
5
  "sourceDirty": false,
6
- "builtAt": "2026-11-09T09:58:50.254Z",
6
+ "builtAt": "2026-11-09T12:30:36.078Z",
7
7
  "contentSha256": "d88edba1ac622f799a43f96f04cacdfb2052f2b959dae733f3feb164b615427b"
8
8
  }
@@ -1,6 +1,6 @@
1
1
  <!doctype html>
2
2
  <html lang="zh-CN">
3
- <head><script data-runweave-build-info>var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T095850254Z-7578c078","sourceRevision":"7578c0782181a4db7b089ce1955082ecf8b53fcb","sourceDirty":false,"builtAt":"2026-11-09T09:58:50.254Z","contentSha256":"d88edba1ac622f799a43f96f04cacdfb2052f2b959dae733f3feb164b615427b"};</script>
3
+ <head><script data-runweave-build-info>var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T123036078Z-da67d523","sourceRevision":"da67d523de925abb4f2b958f949fd50a3ac2b5bb","sourceDirty":false,"builtAt":"2026-11-09T12:30:36.078Z","contentSha256":"d88edba1ac622f799a43f96f04cacdfb2052f2b959dae733f3feb164b615427b"};</script>
4
4
 
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -1,6 +1,6 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head><script data-runweave-build-info>var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T095850254Z-7578c078","sourceRevision":"7578c0782181a4db7b089ce1955082ecf8b53fcb","sourceDirty":false,"builtAt":"2026-11-09T09:58:50.254Z","contentSha256":"d88edba1ac622f799a43f96f04cacdfb2052f2b959dae733f3feb164b615427b"};</script>
3
+ <head><script data-runweave-build-info>var __RUNWEAVE_BUILD_INFO__={"version":"0.1.0","buildId":"20261109T123036078Z-da67d523","sourceRevision":"da67d523de925abb4f2b958f949fd50a3ac2b5bb","sourceDirty":false,"builtAt":"2026-11-09T12:30:36.078Z","contentSha256":"d88edba1ac622f799a43f96f04cacdfb2052f2b959dae733f3feb164b615427b"};</script>
4
4
 
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -18953,6 +18953,7 @@ var NEVER = INVALID;
18953
18953
  // packages/pty-host/src/discovery.ts
18954
18954
  var import_node_crypto = require("node:crypto");
18955
18955
  var import_node_fs4 = require("node:fs");
18956
+ var import_promises = require("node:fs/promises");
18956
18957
  var import_node_path2 = __toESM(require("node:path"), 1);
18957
18958
  var import_node_os = __toESM(require("node:os"), 1);
18958
18959
  function hostPaths(scope) {
@@ -18982,10 +18983,27 @@ function prepareHostDirectory(scope) {
18982
18983
  readPrivateFile(paths.token, 256);
18983
18984
  return paths;
18984
18985
  }
18985
- function writeHostJson(file, data) {
18986
+ async function writeHostJson(file, data) {
18986
18987
  const temporary = `${file}.${(0, import_node_crypto.randomUUID)()}.tmp`;
18987
18988
  (0, import_node_fs4.writeFileSync)(temporary, JSON.stringify(data) + "\n", { mode: 384, flag: "wx" });
18988
- (0, import_node_fs4.renameSync)(temporary, file);
18989
+ const deadline = Date.now() + 5e3;
18990
+ try {
18991
+ for (; ; ) {
18992
+ try {
18993
+ await (0, import_promises.rename)(temporary, file);
18994
+ return;
18995
+ } catch (error) {
18996
+ if (process.platform !== "win32" || !["EPERM", "EACCES", "EBUSY"].includes(error.code ?? "") || Date.now() >= deadline) throw error;
18997
+ await new Promise((resolve) => setTimeout(resolve, 50));
18998
+ }
18999
+ }
19000
+ } catch (error) {
19001
+ try {
19002
+ (0, import_node_fs4.rmSync)(temporary, { force: true });
19003
+ } catch {
19004
+ }
19005
+ throw error;
19006
+ }
18989
19007
  }
18990
19008
 
18991
19009
  // packages/pty-host/src/gateway.ts
@@ -20181,7 +20199,7 @@ async function startHost(scope, release) {
20181
20199
  gatewayUrl: gateway.url,
20182
20200
  release
20183
20201
  };
20184
- writeHostJson(paths.descriptor, descriptor);
20202
+ await writeHostJson(paths.descriptor, descriptor);
20185
20203
  process.once("SIGTERM", () => {
20186
20204
  void shutdown();
20187
20205
  });