@openscout/scout 0.2.57 → 0.2.60

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.
@@ -956,19 +956,58 @@ function findStoredCerts() {
956
956
  return null;
957
957
  }
958
958
  }
959
- function getTailscaleHostname() {
959
+ function readTailscaleStatus() {
960
960
  try {
961
961
  const output = execSync("tailscale status --self=true --peers=false --json", {
962
962
  stdio: ["pipe", "pipe", "pipe"],
963
963
  timeout: 5000
964
964
  }).toString();
965
965
  const data = JSON.parse(output);
966
- const dnsName = data?.Self?.DNSName ?? "";
967
- return dnsName.replace(/\.$/, "") || null;
966
+ const dnsName = typeof data.Self?.DNSName === "string" ? data.Self.DNSName.replace(/\.$/, "") : "";
967
+ return {
968
+ backendState: typeof data.BackendState === "string" ? data.BackendState : null,
969
+ dnsName: dnsName || null,
970
+ online: data.Self?.Online !== false,
971
+ health: Array.isArray(data.Health) ? data.Health.filter((entry) => typeof entry === "string") : []
972
+ };
968
973
  } catch {
969
974
  return null;
970
975
  }
971
976
  }
977
+ function resolveRelayEndpointForTailscaleStatus(port, tailscale) {
978
+ const backendState = tailscale?.backendState?.trim().toLowerCase() ?? "";
979
+ const hostname = tailscale?.dnsName ?? null;
980
+ const tailscaleRunning = backendState === "running" && tailscale?.online !== false && Boolean(hostname);
981
+ const tls = resolveTls(tailscaleRunning ? hostname : null);
982
+ if (tailscaleRunning && hostname && tls) {
983
+ return {
984
+ relayUrl: `wss://${hostname}:${port}`,
985
+ options: { tls }
986
+ };
987
+ }
988
+ if (tailscaleRunning && hostname) {
989
+ pairingLog.warn("relay", "tailscale is running without TLS; falling back to insecure websocket relay", {
990
+ hostname,
991
+ port
992
+ });
993
+ return {
994
+ relayUrl: `ws://${hostname}:${port}`,
995
+ options: {}
996
+ };
997
+ }
998
+ if (hostname && backendState && backendState !== "running") {
999
+ pairingLog.warn("relay", "tailscale hostname detected but tailscale is not running; using local-only relay endpoint", {
1000
+ hostname,
1001
+ backendState,
1002
+ health: tailscale?.health ?? [],
1003
+ port
1004
+ });
1005
+ }
1006
+ return {
1007
+ relayUrl: `ws://127.0.0.1:${port}`,
1008
+ options: {}
1009
+ };
1010
+ }
972
1011
  function generateTailscaleCerts(hostname) {
973
1012
  mkdirSync3(PAIRING_DIR, { recursive: true });
974
1013
  const certPath = join(PAIRING_DIR, `${hostname}.crt`);
@@ -1014,28 +1053,7 @@ function resolveTls(hostname) {
1014
1053
  return generateTailscaleCerts(hostname);
1015
1054
  }
1016
1055
  function resolveRelayEndpoint(port) {
1017
- const hostname = getTailscaleHostname();
1018
- const tls = resolveTls(hostname);
1019
- if (hostname && tls) {
1020
- return {
1021
- relayUrl: `wss://${hostname}:${port}`,
1022
- options: { tls }
1023
- };
1024
- }
1025
- if (hostname) {
1026
- pairingLog.warn("relay", "tailscale hostname detected without TLS; falling back to insecure websocket relay", {
1027
- hostname,
1028
- port
1029
- });
1030
- return {
1031
- relayUrl: `ws://${hostname}:${port}`,
1032
- options: {}
1033
- };
1034
- }
1035
- return {
1036
- relayUrl: `ws://127.0.0.1:${port}`,
1037
- options: {}
1038
- };
1056
+ return resolveRelayEndpointForTailscaleStatus(port, readTailscaleStatus());
1039
1057
  }
1040
1058
  function startManagedRelay(port = 7889) {
1041
1059
  const endpoint = resolveRelayEndpoint(port);
@@ -7869,7 +7887,73 @@ function resolveConfig() {
7869
7887
  // ../../apps/desktop/src/core/pairing/runtime/bridge/fileserver.ts
7870
7888
  import { isAbsolute } from "path";
7871
7889
  import { homedir as homedir9 } from "os";
7890
+
7891
+ // ../../apps/desktop/src/core/pairing/runtime/bridge/web-handoff.ts
7892
+ import { randomBytes as randomBytes2 } from "crypto";
7893
+ var SCOUT_WEB_HANDOFF_COOKIE = "scout_handoff";
7894
+ var WEB_HANDOFF_TTL_MS = 5 * 60 * 1000;
7895
+ var activeWebHandoffs = new Map;
7896
+ function pruneExpiredWebHandoffs(now = Date.now()) {
7897
+ for (const [token, record] of activeWebHandoffs) {
7898
+ if (record.expiresAt <= now) {
7899
+ activeWebHandoffs.delete(token);
7900
+ }
7901
+ }
7902
+ }
7903
+ function scopesMatch(left, right) {
7904
+ if (left.kind !== right.kind) {
7905
+ return false;
7906
+ }
7907
+ if (left.sessionId !== right.sessionId) {
7908
+ return false;
7909
+ }
7910
+ if (left.kind === "session") {
7911
+ return true;
7912
+ }
7913
+ return left.turnId === right.turnId && left.blockId === right.blockId;
7914
+ }
7915
+ function pathForWebHandoffScope(scope) {
7916
+ switch (scope.kind) {
7917
+ case "session":
7918
+ return `/handoff/session/${encodeURIComponent(scope.sessionId)}`;
7919
+ case "file_change":
7920
+ return `/handoff/file-change/${encodeURIComponent(scope.sessionId)}/${encodeURIComponent(scope.turnId)}/${encodeURIComponent(scope.blockId)}`;
7921
+ }
7922
+ }
7923
+ function issueWebHandoff(scope, deviceId) {
7924
+ pruneExpiredWebHandoffs();
7925
+ const token = randomBytes2(24).toString("base64url");
7926
+ const expiresAt = Date.now() + WEB_HANDOFF_TTL_MS;
7927
+ activeWebHandoffs.set(token, {
7928
+ token,
7929
+ deviceId: deviceId?.trim() || null,
7930
+ expiresAt,
7931
+ scope
7932
+ });
7933
+ return { token, expiresAt, scope };
7934
+ }
7935
+ function readAuthorizedWebHandoff(token, scope) {
7936
+ pruneExpiredWebHandoffs();
7937
+ if (!token) {
7938
+ return null;
7939
+ }
7940
+ const record = activeWebHandoffs.get(token);
7941
+ if (!record) {
7942
+ return null;
7943
+ }
7944
+ if (!scopesMatch(record.scope, scope)) {
7945
+ return null;
7946
+ }
7947
+ return record;
7948
+ }
7949
+
7950
+ // ../../apps/desktop/src/core/pairing/runtime/bridge/fileserver.ts
7872
7951
  var ALLOWED_ROOTS = [homedir9(), "/tmp"];
7952
+ var LOOPBACK_IPV4_HOST_PATTERN = /^127(?:\.\d{1,3}){3}$/;
7953
+ function isTrustedLoopbackHostname(hostname) {
7954
+ const normalized = hostname.trim().toLowerCase();
7955
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || LOOPBACK_IPV4_HOST_PATTERN.test(normalized);
7956
+ }
7873
7957
  function isAllowedPath(filePath) {
7874
7958
  if (!isAbsolute(filePath))
7875
7959
  return false;
@@ -7880,7 +7964,7 @@ function isAllowedPath(filePath) {
7880
7964
  return ALLOWED_ROOTS.some((root) => filePath.startsWith(root));
7881
7965
  }
7882
7966
  function startFileServer(options) {
7883
- const { port } = options;
7967
+ const { port, bridge } = options;
7884
7968
  let server = null;
7885
7969
  function start() {
7886
7970
  try {
@@ -7888,7 +7972,7 @@ function startFileServer(options) {
7888
7972
  port,
7889
7973
  fetch(req) {
7890
7974
  try {
7891
- return route(new URL(req.url));
7975
+ return route(req, new URL(req.url), bridge);
7892
7976
  } catch (err) {
7893
7977
  console.error(`[fileserver] ${req.url} \u2192`, err.message);
7894
7978
  return new Response("Internal error", { status: 500 });
@@ -7911,12 +7995,19 @@ function startFileServer(options) {
7911
7995
  start();
7912
7996
  return { port, stop, restart };
7913
7997
  }
7914
- function route(url) {
7998
+ function route(req, url, bridge) {
7915
7999
  const path2 = url.pathname;
7916
- if (path2 === "/file")
8000
+ if (path2 === "/file") {
8001
+ if (!isTrustedLoopbackHostname(url.hostname)) {
8002
+ return new Response("Forbidden", { status: 403 });
8003
+ }
7917
8004
  return serveFile(url);
8005
+ }
7918
8006
  if (path2 === "/health")
7919
8007
  return Response.json({ ok: true });
8008
+ if (path2.startsWith("/handoff/")) {
8009
+ return serveHandoffPage(req, url, bridge);
8010
+ }
7920
8011
  return new Response("Pairing file server", { status: 200 });
7921
8012
  }
7922
8013
  function serveFile(url) {
@@ -7930,9 +8021,523 @@ function serveFile(url) {
7930
8021
  return new Response("Not found", { status: 404 });
7931
8022
  return new Response(file);
7932
8023
  }
8024
+ function serveHandoffPage(req, url, bridge) {
8025
+ if (!bridge) {
8026
+ return new Response("Secure handoff unavailable", { status: 503 });
8027
+ }
8028
+ const scope = parseHandoffScope(url);
8029
+ if (!scope) {
8030
+ return new Response("Not found", { status: 404 });
8031
+ }
8032
+ const token = readHandoffToken(req);
8033
+ const authorized = readAuthorizedWebHandoff(token, scope);
8034
+ if (!authorized) {
8035
+ return unauthorizedHandoffResponse();
8036
+ }
8037
+ let html;
8038
+ switch (scope.kind) {
8039
+ case "session": {
8040
+ const snapshot = bridge.getSessionSnapshot(scope.sessionId);
8041
+ if (!snapshot) {
8042
+ return new Response("Session handoff expired", { status: 404 });
8043
+ }
8044
+ html = renderSessionHandoffPage(snapshot);
8045
+ break;
8046
+ }
8047
+ case "file_change": {
8048
+ const snapshot = bridge.getSessionSnapshot(scope.sessionId);
8049
+ if (!snapshot) {
8050
+ return new Response("Session handoff expired", { status: 404 });
8051
+ }
8052
+ const target = findFileChangeBlock(snapshot, scope.turnId, scope.blockId);
8053
+ if (!target) {
8054
+ return new Response("File change handoff expired", { status: 404 });
8055
+ }
8056
+ html = renderFileChangeHandoffPage(snapshot, target.turn, target.block);
8057
+ break;
8058
+ }
8059
+ }
8060
+ const headers = new Headers({
8061
+ "content-type": "text/html; charset=utf-8",
8062
+ "cache-control": "no-store",
8063
+ "x-content-type-options": "nosniff"
8064
+ });
8065
+ headers.set("set-cookie", `${SCOUT_WEB_HANDOFF_COOKIE}=${authorized.token}; Max-Age=300; Path=/handoff; HttpOnly; SameSite=Strict`);
8066
+ return new Response(html, { status: 200, headers });
8067
+ }
8068
+ function unauthorizedHandoffResponse() {
8069
+ return new Response("Secure handoff required", {
8070
+ status: 401,
8071
+ headers: {
8072
+ "cache-control": "no-store",
8073
+ "set-cookie": `${SCOUT_WEB_HANDOFF_COOKIE}=; Max-Age=0; Path=/handoff; HttpOnly; SameSite=Strict`
8074
+ }
8075
+ });
8076
+ }
8077
+ function parseHandoffScope(url) {
8078
+ const parts = url.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
8079
+ if (parts[0] !== "handoff") {
8080
+ return null;
8081
+ }
8082
+ if (parts[1] === "session" && parts[2]) {
8083
+ return { kind: "session", sessionId: parts[2] };
8084
+ }
8085
+ if (parts[1] === "file-change" && parts[2] && parts[3] && parts[4]) {
8086
+ return {
8087
+ kind: "file_change",
8088
+ sessionId: parts[2],
8089
+ turnId: parts[3],
8090
+ blockId: parts[4]
8091
+ };
8092
+ }
8093
+ return null;
8094
+ }
8095
+ function readHandoffToken(req) {
8096
+ const headerToken = req.headers.get("x-scout-handoff-token")?.trim();
8097
+ if (headerToken) {
8098
+ return headerToken;
8099
+ }
8100
+ const cookieHeader = req.headers.get("cookie");
8101
+ if (!cookieHeader) {
8102
+ return null;
8103
+ }
8104
+ for (const part of cookieHeader.split(";")) {
8105
+ const [name, ...rest] = part.trim().split("=");
8106
+ if (name === SCOUT_WEB_HANDOFF_COOKIE) {
8107
+ const value = rest.join("=").trim();
8108
+ return value || null;
8109
+ }
8110
+ }
8111
+ return null;
8112
+ }
8113
+ function findFileChangeBlock(snapshot, turnId, blockId) {
8114
+ const turn = snapshot.turns.find((candidate) => candidate.id === turnId);
8115
+ if (!turn) {
8116
+ return null;
8117
+ }
8118
+ const blockState = turn.blocks.find((candidate) => candidate.block.id === blockId);
8119
+ const block = blockState?.block;
8120
+ if (!block || block.type !== "action" || block.action.kind !== "file_change") {
8121
+ return null;
8122
+ }
8123
+ return { turn, block };
8124
+ }
8125
+ function renderPage(title, body) {
8126
+ return `<!doctype html>
8127
+ <html lang="en">
8128
+ <head>
8129
+ <meta charset="utf-8" />
8130
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8131
+ <title>${escapeHtml(title)}</title>
8132
+ <style>
8133
+ :root {
8134
+ color-scheme: dark;
8135
+ --bg: #0b0f14;
8136
+ --panel: rgba(19, 27, 39, 0.92);
8137
+ --panel-strong: rgba(12, 18, 28, 0.96);
8138
+ --line: rgba(148, 163, 184, 0.16);
8139
+ --line-strong: rgba(148, 163, 184, 0.24);
8140
+ --text: #e5ecf5;
8141
+ --muted: #90a0b4;
8142
+ --accent: #63d0ff;
8143
+ --green: #54d38a;
8144
+ --amber: #f8c36a;
8145
+ --red: #ff7b72;
8146
+ --surface-add: rgba(84, 211, 138, 0.08);
8147
+ --surface-del: rgba(255, 123, 114, 0.08);
8148
+ }
8149
+ * { box-sizing: border-box; }
8150
+ body {
8151
+ margin: 0;
8152
+ background:
8153
+ radial-gradient(circle at top left, rgba(99, 208, 255, 0.12), transparent 32%),
8154
+ linear-gradient(180deg, #090d12 0%, #0b0f14 55%, #111723 100%);
8155
+ color: var(--text);
8156
+ font: 14px/1.5 ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
8157
+ }
8158
+ .wrap {
8159
+ width: min(980px, calc(100vw - 24px));
8160
+ margin: 0 auto;
8161
+ padding: 20px 0 40px;
8162
+ }
8163
+ .hero, .panel {
8164
+ background: var(--panel);
8165
+ border: 1px solid var(--line);
8166
+ border-radius: 18px;
8167
+ backdrop-filter: blur(18px);
8168
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
8169
+ }
8170
+ .hero {
8171
+ padding: 18px 18px 16px;
8172
+ margin-bottom: 14px;
8173
+ }
8174
+ .eyebrow {
8175
+ display: inline-flex;
8176
+ align-items: center;
8177
+ gap: 8px;
8178
+ color: var(--muted);
8179
+ font-size: 11px;
8180
+ letter-spacing: 0.14em;
8181
+ text-transform: uppercase;
8182
+ }
8183
+ .dot {
8184
+ width: 8px;
8185
+ height: 8px;
8186
+ border-radius: 999px;
8187
+ background: var(--accent);
8188
+ box-shadow: 0 0 14px rgba(99, 208, 255, 0.65);
8189
+ }
8190
+ h1 {
8191
+ margin: 12px 0 10px;
8192
+ font-size: 24px;
8193
+ line-height: 1.15;
8194
+ }
8195
+ .meta, .stack {
8196
+ display: flex;
8197
+ flex-wrap: wrap;
8198
+ gap: 8px;
8199
+ }
8200
+ .pill {
8201
+ display: inline-flex;
8202
+ align-items: center;
8203
+ gap: 6px;
8204
+ padding: 5px 10px;
8205
+ border-radius: 999px;
8206
+ border: 1px solid var(--line-strong);
8207
+ background: rgba(255, 255, 255, 0.03);
8208
+ color: var(--muted);
8209
+ font-size: 12px;
8210
+ }
8211
+ .pill strong {
8212
+ color: var(--text);
8213
+ font-weight: 600;
8214
+ }
8215
+ .stack { flex-direction: column; }
8216
+ .panel {
8217
+ margin-top: 12px;
8218
+ overflow: hidden;
8219
+ }
8220
+ .panel-inner {
8221
+ padding: 16px;
8222
+ }
8223
+ .turn {
8224
+ border-top: 1px solid var(--line);
8225
+ }
8226
+ .turn:first-child {
8227
+ border-top: none;
8228
+ }
8229
+ .turn-head {
8230
+ padding: 14px 16px;
8231
+ display: flex;
8232
+ justify-content: space-between;
8233
+ gap: 12px;
8234
+ align-items: center;
8235
+ background: rgba(255, 255, 255, 0.02);
8236
+ }
8237
+ .turn-title {
8238
+ font-size: 13px;
8239
+ font-weight: 600;
8240
+ letter-spacing: 0.02em;
8241
+ }
8242
+ .turn-subtitle {
8243
+ color: var(--muted);
8244
+ font-size: 12px;
8245
+ margin-top: 2px;
8246
+ }
8247
+ .blocks {
8248
+ padding: 8px;
8249
+ }
8250
+ .block {
8251
+ padding: 12px;
8252
+ border-radius: 14px;
8253
+ border: 1px solid var(--line);
8254
+ background: var(--panel-strong);
8255
+ margin: 8px;
8256
+ }
8257
+ .block-head {
8258
+ display: flex;
8259
+ justify-content: space-between;
8260
+ align-items: center;
8261
+ gap: 12px;
8262
+ margin-bottom: 10px;
8263
+ }
8264
+ .block-type {
8265
+ color: var(--muted);
8266
+ text-transform: uppercase;
8267
+ letter-spacing: 0.12em;
8268
+ font-size: 11px;
8269
+ }
8270
+ .label {
8271
+ color: var(--muted);
8272
+ font-size: 12px;
8273
+ }
8274
+ .label strong {
8275
+ color: var(--text);
8276
+ }
8277
+ pre {
8278
+ margin: 0;
8279
+ white-space: pre-wrap;
8280
+ word-break: break-word;
8281
+ overflow-wrap: anywhere;
8282
+ font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
8283
+ }
8284
+ .code {
8285
+ padding: 12px;
8286
+ border-radius: 12px;
8287
+ border: 1px solid var(--line);
8288
+ background: rgba(255, 255, 255, 0.02);
8289
+ }
8290
+ .diff-line-add { background: var(--surface-add); color: #8ef3b7; }
8291
+ .diff-line-del { background: var(--surface-del); color: #ff9d96; }
8292
+ .details {
8293
+ margin-top: 10px;
8294
+ border: 1px solid var(--line);
8295
+ border-radius: 12px;
8296
+ overflow: hidden;
8297
+ }
8298
+ details > summary {
8299
+ list-style: none;
8300
+ cursor: pointer;
8301
+ padding: 10px 12px;
8302
+ background: rgba(255, 255, 255, 0.03);
8303
+ color: var(--muted);
8304
+ font-size: 12px;
8305
+ text-transform: uppercase;
8306
+ letter-spacing: 0.1em;
8307
+ }
8308
+ details[open] > summary {
8309
+ border-bottom: 1px solid var(--line);
8310
+ }
8311
+ .empty {
8312
+ padding: 28px 18px;
8313
+ color: var(--muted);
8314
+ }
8315
+ .grid {
8316
+ display: grid;
8317
+ gap: 10px;
8318
+ }
8319
+ .grid.two {
8320
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
8321
+ }
8322
+ a.inline {
8323
+ color: var(--accent);
8324
+ text-decoration: none;
8325
+ }
8326
+ .status-streaming { color: var(--amber); }
8327
+ .status-completed { color: var(--green); }
8328
+ .status-failed, .status-error { color: var(--red); }
8329
+ </style>
8330
+ </head>
8331
+ <body>
8332
+ <main class="wrap">
8333
+ ${body}
8334
+ </main>
8335
+ </body>
8336
+ </html>`;
8337
+ }
8338
+ function renderSessionHandoffPage(snapshot) {
8339
+ const title = snapshot.session.name || snapshot.session.id;
8340
+ const hero = `
8341
+ <section class="hero">
8342
+ <div class="eyebrow"><span class="dot"></span> Secure Proxy Session Handoff</div>
8343
+ <h1>${escapeHtml(title)}</h1>
8344
+ <div class="meta">
8345
+ <span class="pill"><strong>ID</strong> ${escapeHtml(snapshot.session.id)}</span>
8346
+ <span class="pill"><strong>Adapter</strong> ${escapeHtml(snapshot.session.adapterType)}</span>
8347
+ <span class="pill"><strong>Status</strong> ${escapeHtml(snapshot.session.status)}</span>
8348
+ ${snapshot.session.model ? `<span class="pill"><strong>Model</strong> ${escapeHtml(snapshot.session.model)}</span>` : ""}
8349
+ ${snapshot.session.cwd ? `<span class="pill"><strong>Workspace</strong> ${escapeHtml(snapshot.session.cwd)}</span>` : ""}
8350
+ </div>
8351
+ </section>
8352
+ `;
8353
+ const turns = snapshot.turns.length > 0 ? snapshot.turns.map((turn) => {
8354
+ const turnBody = turn.blocks.length > 0 ? turn.blocks.map(({ block }) => renderBlock(block)).join("") : `<div class="empty">No blocks were captured for this turn.</div>`;
8355
+ return `
8356
+ <section class="turn">
8357
+ <div class="turn-head">
8358
+ <div>
8359
+ <div class="turn-title">${escapeHtml(turn.id)}</div>
8360
+ <div class="turn-subtitle">${formatTimestamp(turn.startedAt)}${turn.endedAt ? ` to ${formatTimestamp(turn.endedAt)}` : ""}</div>
8361
+ </div>
8362
+ <span class="pill"><strong>Turn</strong> <span class="status-${escapeHtml(turn.status)}">${escapeHtml(turn.status)}</span></span>
8363
+ </div>
8364
+ <div class="blocks">${turnBody}</div>
8365
+ </section>
8366
+ `;
8367
+ }).join("") : `<div class="empty">This session has not produced any turns yet.</div>`;
8368
+ return renderPage(title, `${hero}<section class="panel">${turns}</section>`);
8369
+ }
8370
+ function renderFileChangeHandoffPage(snapshot, turn, block) {
8371
+ const action = block.action;
8372
+ const title = action.path || snapshot.session.name || snapshot.session.id;
8373
+ const hero = `
8374
+ <section class="hero">
8375
+ <div class="eyebrow"><span class="dot"></span> Secure Proxy File Change</div>
8376
+ <h1>${escapeHtml(title)}</h1>
8377
+ <div class="meta">
8378
+ <span class="pill"><strong>Session</strong> ${escapeHtml(snapshot.session.name || snapshot.session.id)}</span>
8379
+ <span class="pill"><strong>Turn</strong> ${escapeHtml(turn.id)}</span>
8380
+ <span class="pill"><strong>Status</strong> ${escapeHtml(action.status)}</span>
8381
+ </div>
8382
+ </section>
8383
+ `;
8384
+ const body = `
8385
+ <section class="panel">
8386
+ <div class="panel-inner stack">
8387
+ <div class="grid two">
8388
+ <div class="pill"><strong>Path</strong> ${escapeHtml(action.path)}</div>
8389
+ <div class="pill"><strong>Block</strong> ${escapeHtml(block.id)}</div>
8390
+ </div>
8391
+ ${action.diff ? renderDetailsSection("Diff", renderDiff(action.diff), true) : `<div class="empty">No diff was recorded for this action.</div>`}
8392
+ ${action.output ? renderDetailsSection("Output", `<div class="code"><pre>${escapeHtml(action.output)}</pre></div>`) : ""}
8393
+ </div>
8394
+ </section>
8395
+ `;
8396
+ return renderPage(title, `${hero}${body}`);
8397
+ }
8398
+ function renderBlock(block) {
8399
+ switch (block.type) {
8400
+ case "text":
8401
+ return renderTextLikeBlock("Text", block.text);
8402
+ case "reasoning":
8403
+ return renderTextLikeBlock("Reasoning", block.text);
8404
+ case "error":
8405
+ return `
8406
+ <article class="block">
8407
+ <div class="block-head">
8408
+ <div class="block-type">Error</div>
8409
+ <span class="pill status-error">${escapeHtml(block.status)}</span>
8410
+ </div>
8411
+ <div class="code"><pre>${escapeHtml(block.message)}</pre></div>
8412
+ </article>
8413
+ `;
8414
+ case "file":
8415
+ return `
8416
+ <article class="block">
8417
+ <div class="block-head">
8418
+ <div class="block-type">File</div>
8419
+ <span class="pill">${escapeHtml(block.status)}</span>
8420
+ </div>
8421
+ <div class="grid">
8422
+ ${block.name ? `<div class="label"><strong>Name</strong> ${escapeHtml(block.name)}</div>` : ""}
8423
+ <div class="label"><strong>MIME</strong> ${escapeHtml(block.mimeType)}</div>
8424
+ </div>
8425
+ </article>
8426
+ `;
8427
+ case "question":
8428
+ return renderQuestionBlock(block);
8429
+ case "action":
8430
+ return renderActionBlock(block);
8431
+ }
8432
+ }
8433
+ function renderTextLikeBlock(label, text) {
8434
+ return `
8435
+ <article class="block">
8436
+ <div class="block-head">
8437
+ <div class="block-type">${escapeHtml(label)}</div>
8438
+ </div>
8439
+ <div class="code"><pre>${escapeHtml(text)}</pre></div>
8440
+ </article>
8441
+ `;
8442
+ }
8443
+ function renderQuestionBlock(block) {
8444
+ const answer = block.answer?.length ? block.answer.join(", ") : "Awaiting answer";
8445
+ const options = block.options?.length ? `<div class="label"><strong>Options</strong> ${escapeHtml(block.options.map((option) => option.label).join(", "))}</div>` : "";
8446
+ return `
8447
+ <article class="block">
8448
+ <div class="block-head">
8449
+ <div class="block-type">Question</div>
8450
+ <span class="pill">${escapeHtml(block.questionStatus)}</span>
8451
+ </div>
8452
+ ${block.header ? `<div class="label"><strong>${escapeHtml(block.header)}</strong></div>` : ""}
8453
+ <div class="code"><pre>${escapeHtml(block.question)}</pre></div>
8454
+ <div class="stack" style="margin-top: 10px;">
8455
+ ${options}
8456
+ <div class="label"><strong>Answer</strong> ${escapeHtml(answer)}</div>
8457
+ </div>
8458
+ </article>
8459
+ `;
8460
+ }
8461
+ function renderActionBlock(block) {
8462
+ const action = block.action;
8463
+ const parts = [];
8464
+ switch (action.kind) {
8465
+ case "file_change":
8466
+ parts.push(`<div class="label"><strong>Path</strong> ${escapeHtml(action.path)}</div>`);
8467
+ if (action.diff) {
8468
+ parts.push(renderDetailsSection("Diff", renderDiff(action.diff)));
8469
+ }
8470
+ break;
8471
+ case "command":
8472
+ parts.push(`<div class="label"><strong>Command</strong></div><div class="code"><pre>${escapeHtml(action.command)}</pre></div>`);
8473
+ if (typeof action.exitCode === "number") {
8474
+ parts.push(`<div class="label"><strong>Exit Code</strong> ${escapeHtml(String(action.exitCode))}</div>`);
8475
+ }
8476
+ break;
8477
+ case "tool_call":
8478
+ parts.push(`<div class="label"><strong>Tool</strong> ${escapeHtml(action.toolName)}</div>`);
8479
+ if (action.toolCallId) {
8480
+ parts.push(`<div class="label"><strong>Call ID</strong> ${escapeHtml(action.toolCallId)}</div>`);
8481
+ }
8482
+ break;
8483
+ case "subagent":
8484
+ parts.push(`<div class="label"><strong>Agent</strong> ${escapeHtml(action.agentName || action.agentId)}</div>`);
8485
+ if (action.prompt) {
8486
+ parts.push(`<div class="code"><pre>${escapeHtml(action.prompt)}</pre></div>`);
8487
+ }
8488
+ break;
8489
+ }
8490
+ if (action.approval) {
8491
+ parts.push(`<div class="label"><strong>Approval</strong> ${escapeHtml(action.approval.risk || "medium")} risk${action.approval.description ? ` - ${escapeHtml(action.approval.description)}` : ""}</div>`);
8492
+ }
8493
+ if (action.output) {
8494
+ parts.push(renderDetailsSection("Output", `<div class="code"><pre>${escapeHtml(action.output)}</pre></div>`));
8495
+ }
8496
+ return `
8497
+ <article class="block">
8498
+ <div class="block-head">
8499
+ <div class="block-type">${escapeHtml(action.kind.replace("_", " "))}</div>
8500
+ <span class="pill">${escapeHtml(action.status)}</span>
8501
+ </div>
8502
+ <div class="stack">${parts.join("")}</div>
8503
+ </article>
8504
+ `;
8505
+ }
8506
+ function renderDetailsSection(title, innerHtml, open = false) {
8507
+ return `
8508
+ <details class="details"${open ? " open" : ""}>
8509
+ <summary>${escapeHtml(title)}</summary>
8510
+ <div class="panel-inner">${innerHtml}</div>
8511
+ </details>
8512
+ `;
8513
+ }
8514
+ function renderDiff(diff) {
8515
+ const lines = diff.split(`
8516
+ `).map((line) => {
8517
+ const className = line.startsWith("+") ? "diff-line-add" : line.startsWith("-") ? "diff-line-del" : "";
8518
+ return `<div class="${className}"><pre>${escapeHtml(line || " ")}</pre></div>`;
8519
+ }).join("");
8520
+ return `<div class="code">${lines}</div>`;
8521
+ }
8522
+ function escapeHtml(value) {
8523
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
8524
+ }
8525
+ function formatTimestamp(value) {
8526
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8527
+ return "Unknown time";
8528
+ }
8529
+ try {
8530
+ return new Intl.DateTimeFormat("en-US", {
8531
+ dateStyle: "medium",
8532
+ timeStyle: "short"
8533
+ }).format(value);
8534
+ } catch {
8535
+ return new Date(value).toISOString();
8536
+ }
8537
+ }
7933
8538
 
7934
8539
  // ../../apps/desktop/src/core/pairing/runtime/bridge/server.ts
7935
- import { readdirSync as readdirSync3, readFileSync as readFileSync8, realpathSync, statSync as statSync3 } from "fs";
8540
+ import { readdirSync as readdirSync3, readFileSync as readFileSync9, realpathSync, statSync as statSync3 } from "fs";
7936
8541
  import { execSync as execSync3 } from "child_process";
7937
8542
  import { basename as basename6, isAbsolute as isAbsolute3, join as join17, relative as relative2 } from "path";
7938
8543
  import { homedir as homedir15 } from "os";
@@ -8036,6 +8641,11 @@ var BUILT_IN_HARNESS_CATALOG = [
8036
8641
  loginCommand: "claude login",
8037
8642
  notReadyMessage: "Claude is installed but not authenticated yet."
8038
8643
  },
8644
+ resume: {
8645
+ command: "claude",
8646
+ sessionFlag: "--resume",
8647
+ cwdFlag: "--cwd"
8648
+ },
8039
8649
  capabilities: ["chat", "invoke", "deliver", "summarize", "review"]
8040
8650
  },
8041
8651
  {
@@ -8067,6 +8677,11 @@ var BUILT_IN_HARNESS_CATALOG = [
8067
8677
  loginCommand: "codex login",
8068
8678
  notReadyMessage: "Codex is installed but not authenticated yet."
8069
8679
  },
8680
+ resume: {
8681
+ command: "codex",
8682
+ sessionFlag: "--thread",
8683
+ cwdFlag: "--cwd"
8684
+ },
8070
8685
  capabilities: ["chat", "invoke", "deliver", "review", "execute"]
8071
8686
  }
8072
8687
  ];
@@ -8179,6 +8794,7 @@ function createBuiltInHarnessCatalog() {
8179
8794
  anyOf: entry.readiness.anyOf ? [...entry.readiness.anyOf] : undefined
8180
8795
  } : undefined,
8181
8796
  launch: entry.launch ? { ...entry.launch, args: [...entry.launch.args] } : undefined,
8797
+ resume: entry.resume ? { ...entry.resume } : undefined,
8182
8798
  resolveEnv: entry.resolveEnv ? [...entry.resolveEnv] : undefined,
8183
8799
  capabilities: [...entry.capabilities],
8184
8800
  metadata: entry.metadata ? { ...entry.metadata } : undefined
@@ -8196,6 +8812,7 @@ function mergeHarnessCatalogEntries(baseEntries, overrides = {}) {
8196
8812
  install: mergeInstall(entry.install, override.install),
8197
8813
  readiness: mergeReadiness(entry.readiness, override.readiness),
8198
8814
  launch: mergeLaunch(entry.launch, override.launch),
8815
+ resume: override.resume && entry.resume ? { ...entry.resume, ...override.resume } : entry.resume,
8199
8816
  tags: override.tags ? [...override.tags] : entry.tags,
8200
8817
  capabilities: override.capabilities ? [...override.capabilities] : entry.capabilities,
8201
8818
  resolveEnv: override.resolveEnv ? [...override.resolveEnv] : entry.resolveEnv,
@@ -8224,6 +8841,7 @@ function mergeHarnessCatalogEntries(baseEntries, overrides = {}) {
8224
8841
  install: mergeInstall(undefined, override.install),
8225
8842
  readiness: mergeReadiness(undefined, override.readiness),
8226
8843
  launch: mergeLaunch(undefined, override.launch),
8844
+ resume: override.resume?.command && override.resume?.sessionFlag ? { command: override.resume.command, sessionFlag: override.resume.sessionFlag, cwdFlag: override.resume.cwdFlag } : undefined,
8227
8845
  resolveEnv: override.resolveEnv ? [...override.resolveEnv] : undefined,
8228
8846
  capabilities: [...override.capabilities],
8229
8847
  metadata: override.metadata ? { ...override.metadata } : undefined
@@ -11729,7 +12347,7 @@ async function ensureRelayAgentConfigured(value, options = {}) {
11729
12347
 
11730
12348
  // ../runtime/src/local-agents.ts
11731
12349
  import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
11732
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
12350
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
11733
12351
  import { mkdir as mkdir6, rm as rm5, stat as stat3, writeFile as writeFile6 } from "fs/promises";
11734
12352
  import { basename as basename4, dirname as dirname6, join as join15, resolve as resolve5 } from "path";
11735
12353
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -11737,7 +12355,8 @@ import { fileURLToPath as fileURLToPath5 } from "url";
11737
12355
  // ../runtime/src/claude-stream-json.ts
11738
12356
  import { randomUUID } from "crypto";
11739
12357
  import { spawn as spawn2 } from "child_process";
11740
- import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "fs/promises";
12358
+ import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
12359
+ import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
11741
12360
  import { join as join12 } from "path";
11742
12361
 
11743
12362
  // ../runtime/src/managed-agent-environment.ts
@@ -11778,18 +12397,58 @@ function sessionKey(options) {
11778
12397
  function errorMessage2(error) {
11779
12398
  return error instanceof Error ? error.message : String(error);
11780
12399
  }
11781
- async function readOptionalFile2(filePath) {
12400
+ var SESSION_CATALOG_FILENAME = "session-catalog.json";
12401
+ var SESSION_CATALOG_MAX_ENTRIES = 64;
12402
+ function readSessionCatalogSync(runtimeDirectory) {
12403
+ const catalogPath = join12(runtimeDirectory, SESSION_CATALOG_FILENAME);
11782
12404
  try {
11783
- const raw = await readFile5(filePath, "utf8");
11784
- const trimmed = raw.trim();
11785
- return trimmed || null;
12405
+ if (!existsSync11(catalogPath)) {
12406
+ const legacyPath = join12(runtimeDirectory, "claude-session-id.txt");
12407
+ if (existsSync11(legacyPath)) {
12408
+ const legacyId = readFileSync6(legacyPath, "utf8").trim();
12409
+ if (legacyId) {
12410
+ return {
12411
+ activeSessionId: legacyId,
12412
+ sessions: [{ id: legacyId, startedAt: Date.now(), cwd: "" }]
12413
+ };
12414
+ }
12415
+ }
12416
+ return { activeSessionId: null, sessions: [] };
12417
+ }
12418
+ const raw = readFileSync6(catalogPath, "utf8").trim();
12419
+ if (!raw)
12420
+ return { activeSessionId: null, sessions: [] };
12421
+ const parsed = JSON.parse(raw);
12422
+ return {
12423
+ activeSessionId: parsed.activeSessionId ?? null,
12424
+ sessions: Array.isArray(parsed.sessions) ? parsed.sessions : []
12425
+ };
11786
12426
  } catch {
11787
- return null;
12427
+ return { activeSessionId: null, sessions: [] };
12428
+ }
12429
+ }
12430
+ async function readSessionCatalog(runtimeDirectory) {
12431
+ return readSessionCatalogSync(runtimeDirectory);
12432
+ }
12433
+ async function writeSessionCatalog(runtimeDirectory, catalog) {
12434
+ const catalogPath = join12(runtimeDirectory, SESSION_CATALOG_FILENAME);
12435
+ await writeFile4(catalogPath, JSON.stringify(catalog, null, 2) + `
12436
+ `);
12437
+ }
12438
+ function catalogRecordSession(catalog, sessionId, cwd) {
12439
+ const now = Date.now();
12440
+ const sessions = catalog.sessions.map((s) => s.id === catalog.activeSessionId && !s.endedAt ? { ...s, endedAt: now } : s);
12441
+ if (!sessions.some((s) => s.id === sessionId)) {
12442
+ sessions.push({ id: sessionId, startedAt: now, cwd });
12443
+ }
12444
+ while (sessions.length > SESSION_CATALOG_MAX_ENTRIES) {
12445
+ sessions.shift();
11788
12446
  }
12447
+ return { activeSessionId: sessionId, sessions };
11789
12448
  }
11790
12449
  class ClaudeStreamJsonSession {
11791
12450
  options;
11792
- sessionStatePath;
12451
+ catalogDirectory;
11793
12452
  stdoutLogPath;
11794
12453
  stderrLogPath;
11795
12454
  process = null;
@@ -11800,7 +12459,7 @@ class ClaudeStreamJsonSession {
11800
12459
  lastConfigSignature;
11801
12460
  constructor(options) {
11802
12461
  this.options = options;
11803
- this.sessionStatePath = join12(options.runtimeDirectory, "claude-session-id.txt");
12462
+ this.catalogDirectory = options.runtimeDirectory;
11804
12463
  this.stdoutLogPath = join12(options.logsDirectory, "stdout.log");
11805
12464
  this.stderrLogPath = join12(options.logsDirectory, "stderr.log");
11806
12465
  this.lastConfigSignature = this.configSignature(options);
@@ -11906,7 +12565,9 @@ class ClaudeStreamJsonSession {
11906
12565
  }
11907
12566
  if (options.resetSession) {
11908
12567
  this.claudeSessionId = null;
11909
- await rm3(this.sessionStatePath, { force: true });
12568
+ const catalog = await readSessionCatalog(this.catalogDirectory);
12569
+ catalog.activeSessionId = null;
12570
+ await writeSessionCatalog(this.catalogDirectory, catalog);
11910
12571
  }
11911
12572
  }
11912
12573
  configSignature(options) {
@@ -11935,7 +12596,8 @@ class ClaudeStreamJsonSession {
11935
12596
  await mkdir4(this.options.runtimeDirectory, { recursive: true });
11936
12597
  await mkdir4(this.options.logsDirectory, { recursive: true });
11937
12598
  await writeFile4(join12(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
11938
- this.claudeSessionId = await readOptionalFile2(this.sessionStatePath);
12599
+ const catalog = await readSessionCatalog(this.catalogDirectory);
12600
+ this.claudeSessionId = catalog.activeSessionId;
11939
12601
  const args = [
11940
12602
  "--verbose",
11941
12603
  "--print",
@@ -12014,8 +12676,11 @@ class ClaudeStreamJsonSession {
12014
12676
  const nextSessionId = event.session_id ?? event.sessionId ?? null;
12015
12677
  if (nextSessionId && nextSessionId !== this.claudeSessionId) {
12016
12678
  this.claudeSessionId = nextSessionId;
12017
- writeFile4(this.sessionStatePath, `${nextSessionId}
12018
- `);
12679
+ (async () => {
12680
+ const catalog = await readSessionCatalog(this.catalogDirectory);
12681
+ const updated = catalogRecordSession(catalog, nextSessionId, this.options.cwd);
12682
+ await writeSessionCatalog(this.catalogDirectory, updated);
12683
+ })();
12019
12684
  }
12020
12685
  return;
12021
12686
  }
@@ -12079,7 +12744,9 @@ async function shutdownClaudeStreamJsonAgent(options, shutdownOptions = {}) {
12079
12744
  const session = sessions.get(key);
12080
12745
  if (!session) {
12081
12746
  if (shutdownOptions.resetSession) {
12082
- await rm3(join12(options.runtimeDirectory, "claude-session-id.txt"), { force: true });
12747
+ const catalog = await readSessionCatalog(options.runtimeDirectory);
12748
+ catalog.activeSessionId = null;
12749
+ await writeSessionCatalog(options.runtimeDirectory, catalog);
12083
12750
  }
12084
12751
  return;
12085
12752
  }
@@ -12089,8 +12756,107 @@ async function shutdownClaudeStreamJsonAgent(options, shutdownOptions = {}) {
12089
12756
 
12090
12757
  // ../runtime/src/codex-app-server.ts
12091
12758
  import { spawn as spawn3 } from "child_process";
12092
- import { access as access3, appendFile as appendFile3, constants as constants3, mkdir as mkdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "fs/promises";
12759
+ import { constants as constants3 } from "fs";
12760
+ import { access as access3, appendFile as appendFile3, mkdir as mkdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "fs/promises";
12093
12761
  import { delimiter as delimiter3, join as join13 } from "path";
12762
+ function normalizeCodexModelValue(value) {
12763
+ const trimmed = value?.trim();
12764
+ return trimmed ? trimmed : null;
12765
+ }
12766
+ function encodeCodexModelConfig(model) {
12767
+ return `model=${JSON.stringify(model)}`;
12768
+ }
12769
+ function parseCodexModelConfig(value) {
12770
+ const trimmed = value?.trim();
12771
+ if (!trimmed) {
12772
+ return null;
12773
+ }
12774
+ const separatorIndex = trimmed.indexOf("=");
12775
+ if (separatorIndex <= 0) {
12776
+ return null;
12777
+ }
12778
+ const key = trimmed.slice(0, separatorIndex).trim();
12779
+ if (key !== "model") {
12780
+ return null;
12781
+ }
12782
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
12783
+ if (!rawValue) {
12784
+ return null;
12785
+ }
12786
+ if (rawValue.startsWith('"') && rawValue.endsWith('"') || rawValue.startsWith("'") && rawValue.endsWith("'")) {
12787
+ return rawValue.slice(1, -1) || null;
12788
+ }
12789
+ return rawValue;
12790
+ }
12791
+ function normalizeCodexAppServerLaunchArgs(launchArgs) {
12792
+ const args = Array.isArray(launchArgs) ? launchArgs.map((entry) => String(entry).trim()).filter(Boolean) : [];
12793
+ const normalized = [];
12794
+ for (let index = 0;index < args.length; index += 1) {
12795
+ const current = args[index] ?? "";
12796
+ if (current === "--model" || current === "-m") {
12797
+ const model = normalizeCodexModelValue(args[index + 1]);
12798
+ if (model) {
12799
+ normalized.push("-c", encodeCodexModelConfig(model));
12800
+ index += 1;
12801
+ continue;
12802
+ }
12803
+ normalized.push(current);
12804
+ continue;
12805
+ }
12806
+ if (current.startsWith("--model=")) {
12807
+ const model = normalizeCodexModelValue(current.slice("--model=".length));
12808
+ if (model) {
12809
+ normalized.push("-c", encodeCodexModelConfig(model));
12810
+ continue;
12811
+ }
12812
+ }
12813
+ if (current.startsWith("-m=")) {
12814
+ const model = normalizeCodexModelValue(current.slice(3));
12815
+ if (model) {
12816
+ normalized.push("-c", encodeCodexModelConfig(model));
12817
+ continue;
12818
+ }
12819
+ }
12820
+ if (current === "-c" || current === "--config") {
12821
+ const next = args[index + 1];
12822
+ if (typeof next === "string") {
12823
+ const model = parseCodexModelConfig(next);
12824
+ normalized.push(current === "--config" ? "--config" : "-c", model ? encodeCodexModelConfig(model) : next);
12825
+ index += 1;
12826
+ continue;
12827
+ }
12828
+ }
12829
+ if (current.startsWith("--config=")) {
12830
+ const value = current.slice("--config=".length);
12831
+ const model = parseCodexModelConfig(value);
12832
+ normalized.push(model ? `--config=${encodeCodexModelConfig(model)}` : current);
12833
+ continue;
12834
+ }
12835
+ normalized.push(current);
12836
+ }
12837
+ return normalized;
12838
+ }
12839
+ function readCodexAppServerModelFromLaunchArgs(launchArgs) {
12840
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
12841
+ for (let index = 0;index < normalized.length; index += 1) {
12842
+ const current = normalized[index] ?? "";
12843
+ if (current === "-c" || current === "--config") {
12844
+ const model = parseCodexModelConfig(normalized[index + 1]);
12845
+ if (model) {
12846
+ return model;
12847
+ }
12848
+ index += 1;
12849
+ continue;
12850
+ }
12851
+ if (current.startsWith("--config=")) {
12852
+ const model = parseCodexModelConfig(current.slice("--config=".length));
12853
+ if (model) {
12854
+ return model;
12855
+ }
12856
+ }
12857
+ }
12858
+ return null;
12859
+ }
12094
12860
  function sessionKey2(options) {
12095
12861
  return `${options.agentName}:${options.sessionId}`;
12096
12862
  }
@@ -12186,7 +12952,7 @@ function isServerRequest2(message) {
12186
12952
  function isNotification2(message) {
12187
12953
  return Boolean(message && typeof message === "object" && "method" in message && !("id" in message));
12188
12954
  }
12189
- async function readOptionalFile3(filePath) {
12955
+ async function readOptionalFile2(filePath) {
12190
12956
  try {
12191
12957
  const raw = await readFile6(filePath, "utf8");
12192
12958
  const trimmed = raw.trim();
@@ -12199,6 +12965,13 @@ function isMissingCodexRolloutError2(error) {
12199
12965
  const message = errorMessage3(error).toLowerCase();
12200
12966
  return message.includes("no rollout found for thread id");
12201
12967
  }
12968
+ function resolveCodexCompletionGraceMs() {
12969
+ const parsed = Number.parseInt(process.env.OPENSCOUT_CODEX_COMPLETION_GRACE_MS ?? "", 10);
12970
+ if (Number.isFinite(parsed) && parsed >= 0) {
12971
+ return parsed;
12972
+ }
12973
+ return 60000;
12974
+ }
12202
12975
 
12203
12976
  class CodexAppServerSession {
12204
12977
  options;
@@ -12388,7 +13161,7 @@ class CodexAppServerSession {
12388
13161
  systemPrompt: options.systemPrompt,
12389
13162
  threadId: options.threadId ?? null,
12390
13163
  requireExistingThread: options.requireExistingThread === true,
12391
- launchArgs: Array.isArray(options.launchArgs) ? options.launchArgs : []
13164
+ launchArgs: normalizeCodexAppServerLaunchArgs(options.launchArgs)
12392
13165
  });
12393
13166
  }
12394
13167
  async ensureStarted() {
@@ -12410,6 +13183,7 @@ class CodexAppServerSession {
12410
13183
  await mkdir5(this.options.logsDirectory, { recursive: true });
12411
13184
  await writeFile5(join13(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
12412
13185
  const codexExecutable = await resolveCodexExecutable2();
13186
+ const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
12413
13187
  const env = buildManagedAgentEnvironment({
12414
13188
  agentName: this.options.agentName,
12415
13189
  currentDirectory: this.options.cwd,
@@ -12420,7 +13194,8 @@ class CodexAppServerSession {
12420
13194
  ...buildScoutMcpCodexLaunchArgs({
12421
13195
  currentDirectory: this.options.cwd,
12422
13196
  env
12423
- })
13197
+ }),
13198
+ ...launchArgs
12424
13199
  ], {
12425
13200
  cwd: this.options.cwd,
12426
13201
  env,
@@ -12463,7 +13238,7 @@ class CodexAppServerSession {
12463
13238
  }
12464
13239
  async resumeOrStartThread() {
12465
13240
  const requestedThreadId = this.options.threadId?.trim() || null;
12466
- const storedThreadId = requestedThreadId ?? await readOptionalFile3(this.threadIdPath);
13241
+ const storedThreadId = requestedThreadId ?? await readOptionalFile2(this.threadIdPath);
12467
13242
  if (storedThreadId) {
12468
13243
  try {
12469
13244
  const resumed = await this.request("thread/resume", {
@@ -12518,6 +13293,8 @@ class CodexAppServerSession {
12518
13293
  turnId: "",
12519
13294
  startedAt: Date.now(),
12520
13295
  timer: null,
13296
+ graceTimer: null,
13297
+ timedOutAt: null,
12521
13298
  messageOrder: [],
12522
13299
  messageByItemId: new Map,
12523
13300
  resolve: resolve4,
@@ -12525,20 +13302,43 @@ class CodexAppServerSession {
12525
13302
  watchers: []
12526
13303
  };
12527
13304
  turn.timer = setTimeout(() => {
12528
- this.interrupt().catch(() => {
12529
- return;
12530
- });
12531
- if (this.activeTurn === turn) {
12532
- this.activeTurn = null;
12533
- }
12534
- for (const watcher of this.drainTurnWatchers(turn)) {
12535
- watcher.reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
12536
- }
12537
- reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
13305
+ this.scheduleTurnTimeout(turn, timeoutMs);
12538
13306
  }, timeoutMs);
12539
13307
  this.activeTurn = turn;
12540
13308
  return turn;
12541
13309
  }
13310
+ buildTurnTimeoutError(timeoutMs) {
13311
+ return new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`);
13312
+ }
13313
+ scheduleTurnTimeout(turn, timeoutMs) {
13314
+ if (turn.timedOutAt !== null) {
13315
+ return;
13316
+ }
13317
+ turn.timedOutAt = Date.now();
13318
+ this.interrupt().catch(() => {
13319
+ return;
13320
+ });
13321
+ const graceMs = resolveCodexCompletionGraceMs();
13322
+ if (graceMs <= 0) {
13323
+ this.rejectTimedOutTurn(turn, timeoutMs);
13324
+ return;
13325
+ }
13326
+ turn.graceTimer = setTimeout(() => {
13327
+ this.rejectTimedOutTurn(turn, timeoutMs);
13328
+ }, graceMs);
13329
+ }
13330
+ rejectTimedOutTurn(turn, timeoutMs) {
13331
+ if (this.activeTurn !== turn) {
13332
+ this.clearActiveTurn(turn);
13333
+ return;
13334
+ }
13335
+ this.clearActiveTurn(turn);
13336
+ const error = this.buildTurnTimeoutError(timeoutMs);
13337
+ for (const watcher of this.drainTurnWatchers(turn)) {
13338
+ watcher.reject(error);
13339
+ }
13340
+ turn.reject(error);
13341
+ }
12542
13342
  addTurnWatcher(turn, resolve4, reject, timeoutMs) {
12543
13343
  const watcher = {
12544
13344
  resolve: resolve4,
@@ -12572,6 +13372,9 @@ class CodexAppServerSession {
12572
13372
  if (turn.timer) {
12573
13373
  clearTimeout(turn.timer);
12574
13374
  }
13375
+ if (turn.graceTimer) {
13376
+ clearTimeout(turn.graceTimer);
13377
+ }
12575
13378
  if (this.activeTurn === turn) {
12576
13379
  this.activeTurn = null;
12577
13380
  }
@@ -12890,7 +13693,7 @@ function buildCollaborationContractPrompt(agentId) {
12890
13693
 
12891
13694
  // ../runtime/src/broker-service.ts
12892
13695
  import { spawnSync } from "child_process";
12893
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
13696
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
12894
13697
  import { homedir as homedir13 } from "os";
12895
13698
  import { basename as basename3, dirname as dirname5, join as join14, resolve as resolve4 } from "path";
12896
13699
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -12936,7 +13739,7 @@ function runtimePackageDir() {
12936
13739
  return resolve4(moduleDir, "..");
12937
13740
  }
12938
13741
  function isInstalledRuntimePackageDir(candidate) {
12939
- return existsSync11(join14(candidate, "package.json")) && existsSync11(join14(candidate, "bin", "openscout-runtime.mjs"));
13742
+ return existsSync12(join14(candidate, "package.json")) && existsSync12(join14(candidate, "bin", "openscout-runtime.mjs"));
12940
13743
  }
12941
13744
  function findGlobalRuntimeDir() {
12942
13745
  const candidates = [
@@ -12967,7 +13770,7 @@ function findWorkspaceRuntimeDir(startDir) {
12967
13770
  let current = resolve4(startDir);
12968
13771
  while (true) {
12969
13772
  const candidate = join14(current, "packages", "runtime");
12970
- if (existsSync11(join14(candidate, "package.json")) && existsSync11(join14(candidate, "src"))) {
13773
+ if (existsSync12(join14(candidate, "package.json")) && existsSync12(join14(candidate, "src"))) {
12971
13774
  return candidate;
12972
13775
  }
12973
13776
  const parent = dirname5(current);
@@ -12981,18 +13784,18 @@ function resolveBunExecutable2() {
12981
13784
  if (explicit && explicit.trim().length > 0) {
12982
13785
  return explicit;
12983
13786
  }
12984
- if (basename3(process.execPath).startsWith("bun") && existsSync11(process.execPath)) {
13787
+ if (basename3(process.execPath).startsWith("bun") && existsSync12(process.execPath)) {
12985
13788
  return process.execPath;
12986
13789
  }
12987
13790
  const pathEntries = (process.env.PATH ?? "").split(":").filter(Boolean);
12988
13791
  for (const entry of pathEntries) {
12989
13792
  const candidate = join14(entry, "bun");
12990
- if (existsSync11(candidate)) {
13793
+ if (existsSync12(candidate)) {
12991
13794
  return candidate;
12992
13795
  }
12993
13796
  }
12994
13797
  const homeBun = join14(homedir13(), ".bun", "bin", "bun");
12995
- if (existsSync11(homeBun)) {
13798
+ if (existsSync12(homeBun)) {
12996
13799
  return homeBun;
12997
13800
  }
12998
13801
  return "bun";
@@ -13182,10 +13985,10 @@ function launchctlPath() {
13182
13985
  return "/bin/launchctl";
13183
13986
  }
13184
13987
  function readLogLines(path2) {
13185
- if (!existsSync11(path2)) {
13988
+ if (!existsSync12(path2)) {
13186
13989
  return [];
13187
13990
  }
13188
- return readFileSync6(path2, "utf8").split(`
13991
+ return readFileSync7(path2, "utf8").split(`
13189
13992
  `).map((line) => line.trim()).filter(Boolean);
13190
13993
  }
13191
13994
  function isPackageScriptBanner(line) {
@@ -13282,7 +14085,7 @@ async function brokerServiceStatus(config = resolveBrokerServiceConfig()) {
13282
14085
  ensureServiceDirectories(config);
13283
14086
  const launchctl = inspectLaunchctl(config);
13284
14087
  const health = await fetchHealthSnapshot(config);
13285
- const installed = existsSync11(config.launchAgentPath);
14088
+ const installed = existsSync12(config.launchAgentPath);
13286
14089
  const lastLogLine = health.reachable ? readLastLogLine([config.stdoutLogPath, config.stderrLogPath]) : readLastLogLine([config.stderrLogPath, config.stdoutLogPath]);
13287
14090
  return {
13288
14091
  label: config.label,
@@ -13344,7 +14147,7 @@ async function restartBrokerService(config = resolveBrokerServiceConfig()) {
13344
14147
  }
13345
14148
  async function uninstallBrokerService(config = resolveBrokerServiceConfig()) {
13346
14149
  await stopBrokerService(config);
13347
- if (existsSync11(config.launchAgentPath)) {
14150
+ if (existsSync12(config.launchAgentPath)) {
13348
14151
  rmSync2(config.launchAgentPath, { force: true });
13349
14152
  }
13350
14153
  return brokerServiceStatus(config);
@@ -13427,10 +14230,10 @@ function resolveProjectsRoot(projectPath) {
13427
14230
  }
13428
14231
  try {
13429
14232
  const supportPaths = resolveOpenScoutSupportPaths();
13430
- if (!existsSync12(supportPaths.settingsPath)) {
14233
+ if (!existsSync13(supportPaths.settingsPath)) {
13431
14234
  return dirname6(projectPath);
13432
14235
  }
13433
- const raw = JSON.parse(readFileSync7(supportPaths.settingsPath, "utf8"));
14236
+ const raw = JSON.parse(readFileSync8(supportPaths.settingsPath, "utf8"));
13434
14237
  const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
13435
14238
  return workspaceRoot ? resolve5(workspaceRoot) : dirname6(projectPath);
13436
14239
  } catch {
@@ -13443,8 +14246,14 @@ function resolveBrokerUrl() {
13443
14246
  function nowSeconds2() {
13444
14247
  return Math.floor(Date.now() / 1000);
13445
14248
  }
14249
+ function scoutCliPath() {
14250
+ return join15(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
14251
+ }
14252
+ function legacyNodeBrokerRelayCommand() {
14253
+ return `node ${JSON.stringify(scoutCliPath())}`;
14254
+ }
13446
14255
  function brokerRelayCommand() {
13447
- return `node ${JSON.stringify(join15(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs"))}`;
14256
+ return `bun ${JSON.stringify(scoutCliPath())}`;
13448
14257
  }
13449
14258
  function titleCaseLocalAgentName(value) {
13450
14259
  return value.split(/[-_.\s]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
@@ -13457,7 +14266,7 @@ function resolveScoutSkillPath() {
13457
14266
  join15(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
13458
14267
  ];
13459
14268
  for (const path2 of candidatePaths) {
13460
- if (existsSync12(path2)) {
14269
+ if (existsSync13(path2)) {
13461
14270
  return path2;
13462
14271
  }
13463
14272
  }
@@ -13653,7 +14462,7 @@ function buildLegacyBrokerBackedRelayPrompt(agentId, projectName, projectPath, r
13653
14462
  function legacyLocalAgentSystemPromptCandidates(agentId, projectName, projectPath) {
13654
14463
  const relayHub = resolveRelayHub();
13655
14464
  const brokerUrl = resolveBrokerUrl();
13656
- const relayCommandBases = ["openscout relay", brokerRelayCommand()];
14465
+ const relayCommandBases = ["openscout relay", brokerRelayCommand(), legacyNodeBrokerRelayCommand()];
13657
14466
  const projectPathCandidates = projectPath.endsWith("/") ? [projectPath, projectPath.slice(0, -1)] : [projectPath, `${projectPath}/`];
13658
14467
  const candidates = new Set;
13659
14468
  for (const pathCandidate of projectPathCandidates) {
@@ -13664,12 +14473,28 @@ function legacyLocalAgentSystemPromptCandidates(agentId, projectName, projectPat
13664
14473
  }
13665
14474
  return Array.from(candidates);
13666
14475
  }
14476
+ function generatedLocalAgentSystemPromptCandidates(agentId, projectName, projectPath) {
14477
+ const baseContext = buildLocalAgentTemplateContext(agentId, projectName, projectPath);
14478
+ const relayCommands = [brokerRelayCommand(), legacyNodeBrokerRelayCommand()];
14479
+ const transportModes = [undefined, "codex_app_server", "claude_stream_json"];
14480
+ const candidates = new Set;
14481
+ for (const relayCommand of relayCommands) {
14482
+ const context = {
14483
+ ...baseContext,
14484
+ relayCommand
14485
+ };
14486
+ for (const transport of transportModes) {
14487
+ candidates.add(renderLocalAgentSystemPromptTemplate(buildLocalAgentSystemPromptTemplate(), context, transport ? { transport } : {}));
14488
+ }
14489
+ }
14490
+ return Array.from(candidates);
14491
+ }
13667
14492
  function normalizeLocalAgentSystemPrompt(agentId, projectName, projectPath, systemPrompt) {
13668
14493
  const trimmed = systemPrompt?.trim();
13669
14494
  if (!trimmed) {
13670
14495
  return;
13671
14496
  }
13672
- if (legacyLocalAgentSystemPromptCandidates(agentId, projectName, projectPath).includes(trimmed)) {
14497
+ if (legacyLocalAgentSystemPromptCandidates(agentId, projectName, projectPath).includes(trimmed) || generatedLocalAgentSystemPromptCandidates(agentId, projectName, projectPath).includes(trimmed)) {
13673
14498
  return;
13674
14499
  }
13675
14500
  return trimmed;
@@ -13749,6 +14574,128 @@ function normalizeLocalAgentCapabilities(value) {
13749
14574
  function normalizeLocalAgentLaunchArgs(value) {
13750
14575
  return Array.isArray(value) ? value.map((entry) => String(entry).trim()).filter(Boolean) : [];
13751
14576
  }
14577
+ function normalizeRequestedModel(value) {
14578
+ const trimmed = value?.trim();
14579
+ return trimmed ? trimmed : undefined;
14580
+ }
14581
+ function readClaudeLaunchModel(launchArgs) {
14582
+ for (let index = 0;index < launchArgs.length; index += 1) {
14583
+ const current = launchArgs[index] ?? "";
14584
+ if (current === "--model") {
14585
+ const next = launchArgs[index + 1]?.trim();
14586
+ return next || undefined;
14587
+ }
14588
+ if (current.startsWith("--model=")) {
14589
+ const next = current.slice("--model=".length).trim();
14590
+ return next || undefined;
14591
+ }
14592
+ }
14593
+ return;
14594
+ }
14595
+ function normalizeLaunchArgsForHarness(harness, value) {
14596
+ const normalized = normalizeLocalAgentLaunchArgs(value);
14597
+ if (harness === "codex") {
14598
+ return normalizeCodexAppServerLaunchArgs(normalized);
14599
+ }
14600
+ return normalized;
14601
+ }
14602
+ function readLaunchModelForHarness(harness, launchArgs) {
14603
+ if (harness === "codex") {
14604
+ return readCodexAppServerModelFromLaunchArgs(launchArgs) ?? undefined;
14605
+ }
14606
+ if (harness === "claude") {
14607
+ return readClaudeLaunchModel(launchArgs ?? []);
14608
+ }
14609
+ return;
14610
+ }
14611
+ function stripLaunchModelForHarness(harness, launchArgs) {
14612
+ if (harness === "codex") {
14613
+ const next = [];
14614
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
14615
+ for (let index = 0;index < normalized.length; index += 1) {
14616
+ const current = normalized[index] ?? "";
14617
+ if (current === "-c" || current === "--config") {
14618
+ const value = normalized[index + 1];
14619
+ if (readCodexAppServerModelFromLaunchArgs(value ? [current, value] : [current])) {
14620
+ index += 1;
14621
+ continue;
14622
+ }
14623
+ next.push(current);
14624
+ if (value) {
14625
+ next.push(value);
14626
+ index += 1;
14627
+ }
14628
+ continue;
14629
+ }
14630
+ if (current.startsWith("--config=")) {
14631
+ if (readCodexAppServerModelFromLaunchArgs([current])) {
14632
+ continue;
14633
+ }
14634
+ }
14635
+ next.push(current);
14636
+ }
14637
+ return next;
14638
+ }
14639
+ if (harness === "claude") {
14640
+ const next = [];
14641
+ const normalized = normalizeLocalAgentLaunchArgs(launchArgs);
14642
+ for (let index = 0;index < normalized.length; index += 1) {
14643
+ const current = normalized[index] ?? "";
14644
+ if (current === "--model") {
14645
+ index += 1;
14646
+ continue;
14647
+ }
14648
+ if (current.startsWith("--model=")) {
14649
+ continue;
14650
+ }
14651
+ next.push(current);
14652
+ }
14653
+ return next;
14654
+ }
14655
+ return normalizeLocalAgentLaunchArgs(launchArgs);
14656
+ }
14657
+ function buildLaunchArgsForRequestedModel(harness, model) {
14658
+ if (harness === "codex") {
14659
+ return normalizeCodexAppServerLaunchArgs(["--model", model]);
14660
+ }
14661
+ if (harness === "claude") {
14662
+ return ["--model", model];
14663
+ }
14664
+ return [];
14665
+ }
14666
+ function applyRequestedModelToLaunchArgs(harness, launchArgs, model) {
14667
+ const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
14668
+ const requestedModel = normalizeRequestedModel(model);
14669
+ if (!requestedModel) {
14670
+ return normalized;
14671
+ }
14672
+ return [
14673
+ ...stripLaunchModelForHarness(harness, normalized),
14674
+ ...buildLaunchArgsForRequestedModel(harness, requestedModel)
14675
+ ];
14676
+ }
14677
+ function defaultHarnessForOverride(override, fallback = "claude") {
14678
+ return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
14679
+ }
14680
+ function launchArgsForOverrideHarness(override, harness) {
14681
+ const profileLaunchArgs = override.harnessProfiles?.[harness]?.launchArgs;
14682
+ if (profileLaunchArgs) {
14683
+ return normalizeLaunchArgsForHarness(harness, profileLaunchArgs);
14684
+ }
14685
+ if (harness === defaultHarnessForOverride(override, harness)) {
14686
+ return normalizeLaunchArgsForHarness(harness, override.launchArgs);
14687
+ }
14688
+ return [];
14689
+ }
14690
+ function overrideHarnessProfile(override, harness) {
14691
+ const profile = override.harnessProfiles?.[harness];
14692
+ return {
14693
+ cwd: normalizeProjectPath(profile?.cwd || override.runtime?.cwd || override.projectRoot || process.cwd()),
14694
+ transport: normalizeLocalAgentTransport(profile?.transport ?? override.runtime?.transport, harness),
14695
+ sessionId: normalizeTmuxSessionName2(profile?.sessionId, `${override.agentId}-${harness}`),
14696
+ launchArgs: launchArgsForOverrideHarness(override, harness)
14697
+ };
14698
+ }
13752
14699
  function normalizeManagedHarness2(value, fallback) {
13753
14700
  return value === "codex" ? "codex" : value === "claude" ? "claude" : fallback;
13754
14701
  }
@@ -13764,7 +14711,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
13764
14711
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
13765
14712
  transport: normalizeLocalAgentTransport(profile.transport, harness),
13766
14713
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
13767
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
14714
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
13768
14715
  };
13769
14716
  }
13770
14717
  const runtimeHarness = normalizeManagedHarness2(record.harness, defaultHarness);
@@ -13773,7 +14720,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
13773
14720
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
13774
14721
  transport: normalizeLocalAgentTransport(record.transport, runtimeHarness),
13775
14722
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${runtimeHarness}`),
13776
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
14723
+ launchArgs: normalizeLaunchArgsForHarness(runtimeHarness, record.launchArgs)
13777
14724
  };
13778
14725
  }
13779
14726
  if (!nextProfiles[defaultHarness]) {
@@ -13781,7 +14728,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
13781
14728
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
13782
14729
  transport: normalizeLocalAgentTransport(record.transport, defaultHarness),
13783
14730
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${defaultHarness}`),
13784
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
14731
+ launchArgs: normalizeLaunchArgsForHarness(defaultHarness, record.launchArgs)
13785
14732
  };
13786
14733
  }
13787
14734
  for (const harness of ["claude", "codex"]) {
@@ -13793,7 +14740,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
13793
14740
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
13794
14741
  transport: normalizeLocalAgentTransport(profile.transport, harness),
13795
14742
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
13796
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
14743
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
13797
14744
  };
13798
14745
  }
13799
14746
  return nextProfiles;
@@ -13817,14 +14764,14 @@ function recordForHarness(record, harnessOverride) {
13817
14764
  tmuxSession: fallbackSessionId,
13818
14765
  cwd: fallbackCwd,
13819
14766
  transport: fallbackTransport,
13820
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs),
14767
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs),
13821
14768
  harnessProfiles: {
13822
14769
  ...normalized.harnessProfiles,
13823
14770
  [selectedHarness]: {
13824
14771
  cwd: fallbackCwd,
13825
14772
  transport: fallbackTransport,
13826
14773
  sessionId: fallbackSessionId,
13827
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs)
14774
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs)
13828
14775
  }
13829
14776
  }
13830
14777
  };
@@ -13866,7 +14813,7 @@ function normalizeLocalAgentRecord(agentId, record) {
13866
14813
  harnessProfiles,
13867
14814
  transport: activeProfile?.transport ?? normalizeLocalAgentTransport(record.transport, harness),
13868
14815
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
13869
- launchArgs: activeProfile?.launchArgs ?? normalizeLocalAgentLaunchArgs(record.launchArgs)
14816
+ launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs)
13870
14817
  };
13871
14818
  }
13872
14819
  function localAgentStatusFromRecord(agentId, record, source) {
@@ -13935,7 +14882,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
13935
14882
  source: existing?.source && existing.source !== "inferred" ? existing.source : "manual",
13936
14883
  startedAt: normalizedRecord.startedAt,
13937
14884
  systemPrompt: normalizedRecord.systemPrompt,
13938
- launchArgs: normalizeLocalAgentLaunchArgs(normalizedRecord.launchArgs),
14885
+ launchArgs: normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs),
13939
14886
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
13940
14887
  defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
13941
14888
  harnessProfiles: normalizedRecord.harnessProfiles,
@@ -13956,7 +14903,7 @@ function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
13956
14903
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "codex_app_server" }),
13957
14904
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
13958
14905
  logsDirectory: relayAgentLogsDirectory(agentName),
13959
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
14906
+ launchArgs: normalizeLaunchArgsForHarness("codex", record.launchArgs)
13960
14907
  };
13961
14908
  }
13962
14909
  function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
@@ -13967,7 +14914,7 @@ function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
13967
14914
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "claude_stream_json" }),
13968
14915
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
13969
14916
  logsDirectory: relayAgentLogsDirectory(agentName),
13970
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
14917
+ launchArgs: normalizeLaunchArgsForHarness("claude", record.launchArgs)
13971
14918
  };
13972
14919
  }
13973
14920
  function isLocalAgentRecordOnline(agentName, record) {
@@ -14001,7 +14948,7 @@ function buildLocalAgentBootstrapPrompt(_harness, _systemPrompt, initialMessage)
14001
14948
  return initialMessage;
14002
14949
  }
14003
14950
  function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile, workerScript) {
14004
- const extraArgs = shellQuoteArguments(normalizeLocalAgentLaunchArgs(record.launchArgs));
14951
+ const extraArgs = shellQuoteArguments(normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(record.harness), record.launchArgs));
14005
14952
  if (normalizeLocalAgentHarness(record.harness) === "codex") {
14006
14953
  return `exec bash ${JSON.stringify(workerScript ?? join15(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
14007
14954
  }
@@ -14157,7 +15104,7 @@ async function ensureLocalAgentOnline(agentName, record) {
14157
15104
  await new Promise((resolve6) => setTimeout(resolve6, 100));
14158
15105
  }
14159
15106
  if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
14160
- const stderrTail = existsSync12(stderrLogFile) ? readFileSync7(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
15107
+ const stderrTail = existsSync13(stderrLogFile) ? readFileSync8(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
14161
15108
  `).trim() : "";
14162
15109
  throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
14163
15110
  ${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
@@ -14267,6 +15214,7 @@ async function startLocalAgent(input) {
14267
15214
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
14268
15215
  const transport = normalizeLocalAgentTransport(undefined, effectiveHarness);
14269
15216
  const sessionId = normalizeTmuxSessionName2(undefined, `${instance.id}-${effectiveHarness}`);
15217
+ const launchArgs = applyRequestedModelToLaunchArgs(effectiveHarness, [], input.model);
14270
15218
  const existingForInstance = overrides[instance.id];
14271
15219
  if (existingForInstance && normalizeProjectPath(existingForInstance.projectRoot) !== projectRoot) {
14272
15220
  throw new Error(`Another agent is already registered as ${instance.id} at ${existingForInstance.projectRoot}. ` + `Two clones of the same project on the same branch cannot both register here; ` + `rename one of the checkouts or switch its branch before running scout up.`);
@@ -14280,13 +15228,14 @@ async function startLocalAgent(input) {
14280
15228
  projectConfigPath: coldProjectConfigPath,
14281
15229
  source: "manual",
14282
15230
  startedAt: nowSeconds2(),
15231
+ launchArgs,
14283
15232
  defaultHarness: effectiveHarness,
14284
15233
  harnessProfiles: {
14285
15234
  [effectiveHarness]: {
14286
15235
  cwd: effectiveCwd ?? projectRoot,
14287
15236
  transport,
14288
15237
  sessionId,
14289
- launchArgs: []
15238
+ launchArgs
14290
15239
  }
14291
15240
  },
14292
15241
  runtime: {
@@ -14307,6 +15256,9 @@ async function startLocalAgent(input) {
14307
15256
  throw new Error(`Another agent is already registered as ${instance.id} at ${existingForInstance.projectRoot}. ` + `Two clones of the same project on the same branch cannot both register here; ` + `rename one of the checkouts or switch its branch before running scout up.`);
14308
15257
  }
14309
15258
  const resolvedHarness = preferredHarness ?? matchingOverride.runtime?.harness;
15259
+ const nextHarness = normalizeManagedHarness2(resolvedHarness, "claude");
15260
+ const nextDefaultHarness = preferredHarness ? normalizeManagedHarness2(preferredHarness, "claude") : defaultHarnessForOverride(matchingOverride, "claude");
15261
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(nextHarness, launchArgsForOverrideHarness(matchingOverride, nextHarness), input.model);
14310
15262
  overrides[instance.id] = {
14311
15263
  agentId: instance.id,
14312
15264
  definitionId: requestedDefinitionId,
@@ -14317,10 +15269,16 @@ async function startLocalAgent(input) {
14317
15269
  source: "manual",
14318
15270
  startedAt: matchingOverride.startedAt ?? nowSeconds2(),
14319
15271
  systemPrompt: matchingOverride.systemPrompt,
14320
- launchArgs: matchingOverride.launchArgs,
15272
+ launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
14321
15273
  capabilities: matchingOverride.capabilities,
14322
- defaultHarness: normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness, "claude"),
14323
- harnessProfiles: matchingOverride.harnessProfiles,
15274
+ defaultHarness: nextDefaultHarness,
15275
+ harnessProfiles: {
15276
+ ...matchingOverride.harnessProfiles ?? {},
15277
+ [nextHarness]: {
15278
+ ...overrideHarnessProfile(matchingOverride, nextHarness),
15279
+ launchArgs: nextLaunchArgs
15280
+ }
15281
+ },
14324
15282
  runtime: {
14325
15283
  cwd: effectiveCwd ?? matchingOverride.runtime?.cwd ?? matchingProjectRoot,
14326
15284
  harness: resolvedHarness,
@@ -14332,6 +15290,22 @@ async function startLocalAgent(input) {
14332
15290
  await writeRelayAgentOverrides(overrides);
14333
15291
  targetAgentId = instance.id;
14334
15292
  } else {
15293
+ if (input.model) {
15294
+ const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
15295
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), input.model);
15296
+ overrides[matchingAgentId] = {
15297
+ ...matchingOverride,
15298
+ launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
15299
+ harnessProfiles: {
15300
+ ...matchingOverride.harnessProfiles ?? {},
15301
+ [existingHarness]: {
15302
+ ...overrideHarnessProfile(matchingOverride, existingHarness),
15303
+ launchArgs: nextLaunchArgs
15304
+ }
15305
+ }
15306
+ };
15307
+ await writeRelayAgentOverrides(overrides);
15308
+ }
14335
15309
  targetAgentId = matchingAgentId;
14336
15310
  }
14337
15311
  await ensureLocalAgentBindingOnline(targetAgentId, process.env.OPENSCOUT_NODE_ID ?? "local", {
@@ -14402,13 +15376,38 @@ async function interruptLocalAgent(agentId) {
14402
15376
  return { ok: false, agentId };
14403
15377
  }
14404
15378
  }
15379
+ function readPersistedClaudeSessionId(agentName) {
15380
+ try {
15381
+ const runtimeDir = relayAgentRuntimeDirectory(agentName);
15382
+ const catalogPath = join15(runtimeDir, "session-catalog.json");
15383
+ if (existsSync13(catalogPath)) {
15384
+ const raw = readFileSync8(catalogPath, "utf8").trim();
15385
+ if (raw) {
15386
+ const catalog = JSON.parse(raw);
15387
+ if (typeof catalog.activeSessionId === "string" && catalog.activeSessionId.trim()) {
15388
+ return catalog.activeSessionId.trim();
15389
+ }
15390
+ }
15391
+ }
15392
+ const legacyPath = join15(runtimeDir, "claude-session-id.txt");
15393
+ if (existsSync13(legacyPath)) {
15394
+ const value = readFileSync8(legacyPath, "utf8").trim();
15395
+ return value || null;
15396
+ }
15397
+ return null;
15398
+ } catch {
15399
+ return null;
15400
+ }
15401
+ }
14405
15402
  function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14406
15403
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
15404
+ const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
14407
15405
  const definitionId = normalizedRecord.definitionId ?? agentId;
14408
15406
  const displayName = titleCaseLocalAgentName(definitionId);
14409
15407
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
14410
15408
  const instance = buildRelayAgentInstance(definitionId, projectRoot);
14411
15409
  const actorId = instance.id;
15410
+ const externalSessionId = normalizedRecord.transport === "claude_stream_json" ? readPersistedClaudeSessionId(definitionId) : null;
14412
15411
  return {
14413
15412
  actor: {
14414
15413
  id: actorId,
@@ -14427,7 +15426,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14427
15426
  defaultSelector: instance.defaultSelector,
14428
15427
  nodeQualifier: instance.nodeQualifier,
14429
15428
  workspaceQualifier: instance.workspaceQualifier,
14430
- branch: instance.branch
15429
+ branch: instance.branch,
15430
+ ...configuredModel ? { model: configuredModel } : {}
14431
15431
  }
14432
15432
  },
14433
15433
  agent: {
@@ -14454,7 +15454,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14454
15454
  defaultSelector: instance.defaultSelector,
14455
15455
  nodeQualifier: instance.nodeQualifier,
14456
15456
  workspaceQualifier: instance.workspaceQualifier,
14457
- branch: instance.branch
15457
+ branch: instance.branch,
15458
+ ...configuredModel ? { model: configuredModel } : {}
14458
15459
  },
14459
15460
  agentClass: "general",
14460
15461
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
@@ -14486,7 +15487,9 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14486
15487
  selector: instance.selector,
14487
15488
  nodeQualifier: instance.nodeQualifier,
14488
15489
  workspaceQualifier: instance.workspaceQualifier,
14489
- branch: instance.branch
15490
+ branch: instance.branch,
15491
+ ...configuredModel ? { model: configuredModel } : {},
15492
+ ...externalSessionId ? { externalSessionId } : {}
14490
15493
  }
14491
15494
  }
14492
15495
  };
@@ -15811,7 +16814,7 @@ async function deriveNewAgentName(projectName, branch, harness) {
15811
16814
  async function createGitWorktree(projectRoot, agentName) {
15812
16815
  const { execSync: execSync3 } = await import("child_process");
15813
16816
  const { join: join17 } = await import("path");
15814
- const { mkdirSync: mkdirSync9, existsSync: existsSync13 } = await import("fs");
16817
+ const { mkdirSync: mkdirSync9, existsSync: existsSync14 } = await import("fs");
15815
16818
  try {
15816
16819
  execSync3("git rev-parse --git-dir", { cwd: projectRoot, stdio: "pipe" });
15817
16820
  } catch {
@@ -15820,7 +16823,7 @@ async function createGitWorktree(projectRoot, agentName) {
15820
16823
  const branchName = `scout/${agentName}`;
15821
16824
  const worktreeDir = join17(projectRoot, ".scout-worktrees");
15822
16825
  const worktreePath = join17(worktreeDir, agentName);
15823
- if (existsSync13(worktreePath)) {
16826
+ if (existsSync14(worktreePath)) {
15824
16827
  return { path: worktreePath, branch: branchName };
15825
16828
  }
15826
16829
  mkdirSync9(worktreeDir, { recursive: true });
@@ -15862,6 +16865,20 @@ function readSyncStatus(bridge, sessionId) {
15862
16865
  oldestBufferedSeq: bridge.oldestBufferedSeq(sessionId)
15863
16866
  };
15864
16867
  }
16868
+ function resolveSyncSessionId(bridge, preferredSessionId) {
16869
+ if (preferredSessionId) {
16870
+ return preferredSessionId;
16871
+ }
16872
+ let latestSessionId = null;
16873
+ let latestActivityAt = Number.NEGATIVE_INFINITY;
16874
+ for (const session of bridge.getSessionSummaries()) {
16875
+ if (session.lastActivityAt > latestActivityAt) {
16876
+ latestActivityAt = session.lastActivityAt;
16877
+ latestSessionId = session.sessionId;
16878
+ }
16879
+ }
16880
+ return latestSessionId;
16881
+ }
15865
16882
  function sessionRegistryErrorResponse(reqId, error) {
15866
16883
  if (!isSessionRegistryError(error)) {
15867
16884
  return null;
@@ -15931,23 +16948,36 @@ async function handleRPCInner(bridge, req, deviceId) {
15931
16948
  return { id: req.id, result: { ok: true } };
15932
16949
  }
15933
16950
  case "sync/replay": {
15934
- const p = req.params;
15935
- if (!p.sessionId) {
15936
- return { id: req.id, error: { code: -32602, message: "sessionId is required" } };
16951
+ const p = req.params ?? {};
16952
+ if (typeof p.lastSeq !== "number") {
16953
+ return { id: req.id, error: { code: -32602, message: "lastSeq is required" } };
15937
16954
  }
15938
- const events2 = replaySyncEvents(bridge, p.sessionId, p.lastSeq);
16955
+ const sessionId = resolveSyncSessionId(bridge, p.sessionId);
16956
+ if (!sessionId) {
16957
+ return { id: req.id, result: { events: [] } };
16958
+ }
16959
+ const events2 = replaySyncEvents(bridge, sessionId, p.lastSeq);
15939
16960
  return { id: req.id, result: { events: events2 } };
15940
16961
  }
15941
16962
  case "sync/status": {
15942
16963
  const p = req.params ?? {};
15943
- if (!p.sessionId) {
15944
- return { id: req.id, error: { code: -32602, message: "sessionId is required" } };
16964
+ const sessionCount = bridge.listSessions().length;
16965
+ const sessionId = resolveSyncSessionId(bridge, p.sessionId);
16966
+ if (!sessionId) {
16967
+ return {
16968
+ id: req.id,
16969
+ result: {
16970
+ currentSeq: 0,
16971
+ oldestBufferedSeq: 0,
16972
+ sessionCount
16973
+ }
16974
+ };
15945
16975
  }
15946
16976
  return {
15947
16977
  id: req.id,
15948
16978
  result: {
15949
- ...readSyncStatus(bridge, p.sessionId),
15950
- sessionCount: bridge.listSessions().length
16979
+ ...readSyncStatus(bridge, sessionId),
16980
+ sessionCount
15951
16981
  }
15952
16982
  };
15953
16983
  }
@@ -16188,7 +17218,7 @@ async function handleRPCInner(bridge, req, deviceId) {
16188
17218
  return { id: req.id, error: { code: -32000, message: "Only .jsonl files can be read" } };
16189
17219
  }
16190
17220
  try {
16191
- const content = readFileSync8(p.path, "utf-8");
17221
+ const content = readFileSync9(p.path, "utf-8");
16192
17222
  const lines = content.split(`
16193
17223
  `).filter((l) => l.trim().length > 0);
16194
17224
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -33081,7 +34111,7 @@ class ThreadEventPlane {
33081
34111
  }
33082
34112
  // ../runtime/src/mobile-push.ts
33083
34113
  import { Database as Database2 } from "bun:sqlite";
33084
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync9 } from "fs";
34114
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync10 } from "fs";
33085
34115
  import { connect as connectHttp2 } from "http2";
33086
34116
  import { createPrivateKey, sign as signWithKey } from "crypto";
33087
34117
  import { dirname as dirname7, join as join18 } from "path";
@@ -33278,7 +34308,7 @@ function loadApnsCredentials() {
33278
34308
  privateKeyPem = Buffer.from(inlineBase64, "base64").toString("utf8");
33279
34309
  }
33280
34310
  if (!privateKeyPem && path2) {
33281
- privateKeyPem = readFileSync9(path2, "utf8");
34311
+ privateKeyPem = readFileSync10(path2, "utf8");
33282
34312
  }
33283
34313
  if (!teamId || !keyId || !privateKeyPem) {
33284
34314
  return null;
@@ -33452,7 +34482,7 @@ async function broadcastApnsAlertToActiveMobileDevices(alert) {
33452
34482
  };
33453
34483
  }
33454
34484
  // ../../apps/desktop/src/core/pairing/runtime/bridge/router.ts
33455
- import { readFileSync as readFileSync10, readdirSync as readdirSync4, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
34485
+ import { readFileSync as readFileSync11, readdirSync as readdirSync4, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
33456
34486
  import { execSync as execSync4 } from "child_process";
33457
34487
  import { basename as basename7, isAbsolute as isAbsolute4, join as join19, relative as relative3 } from "path";
33458
34488
  import { homedir as homedir16 } from "os";
@@ -33935,6 +34965,64 @@ var mobileRouter = t.router({
33935
34965
  limit: typeof input.limit === "number" ? input.limit : null
33936
34966
  }, resolveMobileCurrentDirectory2());
33937
34967
  }),
34968
+ webHandoff: procedure.input(exports_external.object({
34969
+ kind: exports_external.enum(["session", "file_change"]),
34970
+ sessionId: exports_external.string(),
34971
+ turnId: exports_external.string().optional(),
34972
+ blockId: exports_external.string().optional()
34973
+ })).mutation(({ input, ctx }) => {
34974
+ if (!ctx.deviceId) {
34975
+ throw new TRPCError({
34976
+ code: "UNAUTHORIZED",
34977
+ message: "Secure web handoff requires a paired mobile device"
34978
+ });
34979
+ }
34980
+ const snapshot = ctx.bridge.getSessionSnapshot(input.sessionId);
34981
+ if (!snapshot) {
34982
+ throw new TRPCError({
34983
+ code: "NOT_FOUND",
34984
+ message: `No session: ${input.sessionId}`
34985
+ });
34986
+ }
34987
+ let scope;
34988
+ let title = snapshot.session.name || snapshot.session.id;
34989
+ if (input.kind === "file_change") {
34990
+ if (!input.turnId || !input.blockId) {
34991
+ throw new TRPCError({
34992
+ code: "BAD_REQUEST",
34993
+ message: "turnId and blockId are required for file_change handoffs"
34994
+ });
34995
+ }
34996
+ const turn = snapshot.turns.find((candidate) => candidate.id === input.turnId);
34997
+ const block = turn?.blocks.find((candidate) => candidate.block.id === input.blockId)?.block;
34998
+ if (!turn || !block || block.type !== "action" || block.action.kind !== "file_change") {
34999
+ throw new TRPCError({
35000
+ code: "NOT_FOUND",
35001
+ message: "File change block not found"
35002
+ });
35003
+ }
35004
+ scope = {
35005
+ kind: "file_change",
35006
+ sessionId: input.sessionId,
35007
+ turnId: input.turnId,
35008
+ blockId: input.blockId
35009
+ };
35010
+ title = block.action.path || title;
35011
+ } else {
35012
+ scope = {
35013
+ kind: "session",
35014
+ sessionId: input.sessionId
35015
+ };
35016
+ }
35017
+ const issued = issueWebHandoff(scope, ctx.deviceId);
35018
+ return {
35019
+ kind: input.kind,
35020
+ path: pathForWebHandoffScope(scope),
35021
+ token: issued.token,
35022
+ expiresAt: issued.expiresAt,
35023
+ title
35024
+ };
35025
+ }),
33938
35026
  createSession: procedure.input(exports_external.object({
33939
35027
  workspaceId: exports_external.string(),
33940
35028
  harness: exports_external.string().optional(),
@@ -34110,7 +35198,7 @@ var historyRouter = t.router({
34110
35198
  });
34111
35199
  }
34112
35200
  try {
34113
- const content = readFileSync10(input.path, "utf-8");
35201
+ const content = readFileSync11(input.path, "utf-8");
34114
35202
  const lines = content.split(`
34115
35203
  `).filter((l) => l.trim().length > 0);
34116
35204
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -34148,15 +35236,42 @@ function readSyncStatus2(bridge, sessionId) {
34148
35236
  oldestBufferedSeq: bridge.oldestBufferedSeq(sessionId)
34149
35237
  };
34150
35238
  }
35239
+ function resolveSyncSessionId2(bridge, preferredSessionId) {
35240
+ if (preferredSessionId) {
35241
+ return preferredSessionId;
35242
+ }
35243
+ let latestSessionId = null;
35244
+ let latestActivityAt = Number.NEGATIVE_INFINITY;
35245
+ for (const session of bridge.getSessionSummaries()) {
35246
+ if (session.lastActivityAt > latestActivityAt) {
35247
+ latestActivityAt = session.lastActivityAt;
35248
+ latestSessionId = session.sessionId;
35249
+ }
35250
+ }
35251
+ return latestSessionId;
35252
+ }
34151
35253
  var syncRouter = t.router({
34152
- replay: procedure.input(exports_external.object({ lastSeq: exports_external.number(), sessionId: exports_external.string() })).query(({ input, ctx }) => {
34153
- const events2 = replaySyncEvents2(ctx.bridge, input.sessionId, input.lastSeq);
35254
+ replay: procedure.input(exports_external.object({ lastSeq: exports_external.number(), sessionId: exports_external.string().optional() })).query(({ input, ctx }) => {
35255
+ const sessionId = resolveSyncSessionId2(ctx.bridge, input.sessionId);
35256
+ if (!sessionId) {
35257
+ return { events: [] };
35258
+ }
35259
+ const events2 = replaySyncEvents2(ctx.bridge, sessionId, input.lastSeq);
34154
35260
  return { events: events2 };
34155
35261
  }),
34156
- status: procedure.input(exports_external.object({ sessionId: exports_external.string() })).query(({ input, ctx }) => {
35262
+ status: procedure.input(exports_external.object({ sessionId: exports_external.string().optional() }).optional()).query(({ input, ctx }) => {
35263
+ const sessionCount = ctx.bridge.listSessions().length;
35264
+ const sessionId = resolveSyncSessionId2(ctx.bridge, input?.sessionId);
35265
+ if (!sessionId) {
35266
+ return {
35267
+ currentSeq: 0,
35268
+ oldestBufferedSeq: 0,
35269
+ sessionCount
35270
+ };
35271
+ }
34157
35272
  return {
34158
- ...readSyncStatus2(ctx.bridge, input.sessionId),
34159
- sessionCount: ctx.bridge.listSessions().length
35273
+ ...readSyncStatus2(ctx.bridge, sessionId),
35274
+ sessionCount
34160
35275
  };
34161
35276
  })
34162
35277
  });
@@ -38658,7 +39773,7 @@ async function startPairingRuntime(options) {
38658
39773
  secure: config2.secure,
38659
39774
  identity: config2.secure ? identity2 : undefined
38660
39775
  });
38661
- const fileServer = startFileServer({ port: config2.port + 2 });
39776
+ const fileServer = startFileServer({ port: config2.port + 2, bridge });
38662
39777
  const relayUrl = options?.relayUrl?.trim() || config2.relay || null;
38663
39778
  if (!relayUrl) {
38664
39779
  fileServer.stop();