@openscout/scout 0.2.58 → 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.
package/dist/main.mjs CHANGED
@@ -464,6 +464,7 @@ function parseCardCreateCommandOptions(args, defaultCurrentDirectory) {
464
464
  let agentName;
465
465
  let displayName;
466
466
  let harness;
467
+ let model;
467
468
  let requesterId = null;
468
469
  let noInput = false;
469
470
  for (let index = 0;index < parsed.args.length; index += 1) {
@@ -486,6 +487,12 @@ function parseCardCreateCommandOptions(args, defaultCurrentDirectory) {
486
487
  index = value.nextIndex;
487
488
  continue;
488
489
  }
490
+ if (current === "--model" || current.startsWith("--model=")) {
491
+ const value = parseFlagValue(parsed.args, index, "--model");
492
+ model = value.value;
493
+ index = value.nextIndex;
494
+ continue;
495
+ }
489
496
  if (current === "--as" || current.startsWith("--as=")) {
490
497
  const value = parseFlagValue(parsed.args, index, "--as");
491
498
  requesterId = value.value;
@@ -520,6 +527,7 @@ function parseCardCreateCommandOptions(args, defaultCurrentDirectory) {
520
527
  agentName,
521
528
  displayName,
522
529
  harness,
530
+ model,
523
531
  requesterId,
524
532
  noInput
525
533
  };
@@ -9446,8 +9454,107 @@ var init_src = __esm(() => {
9446
9454
 
9447
9455
  // ../runtime/src/codex-app-server.ts
9448
9456
  import { spawn as spawn3 } from "child_process";
9449
- 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";
9457
+ import { constants as constants3 } from "fs";
9458
+ import { access as access3, appendFile as appendFile3, mkdir as mkdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "fs/promises";
9450
9459
  import { delimiter as delimiter3, join as join9 } from "path";
9460
+ function normalizeCodexModelValue(value) {
9461
+ const trimmed = value?.trim();
9462
+ return trimmed ? trimmed : null;
9463
+ }
9464
+ function encodeCodexModelConfig(model) {
9465
+ return `model=${JSON.stringify(model)}`;
9466
+ }
9467
+ function parseCodexModelConfig(value) {
9468
+ const trimmed = value?.trim();
9469
+ if (!trimmed) {
9470
+ return null;
9471
+ }
9472
+ const separatorIndex = trimmed.indexOf("=");
9473
+ if (separatorIndex <= 0) {
9474
+ return null;
9475
+ }
9476
+ const key = trimmed.slice(0, separatorIndex).trim();
9477
+ if (key !== "model") {
9478
+ return null;
9479
+ }
9480
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
9481
+ if (!rawValue) {
9482
+ return null;
9483
+ }
9484
+ if (rawValue.startsWith('"') && rawValue.endsWith('"') || rawValue.startsWith("'") && rawValue.endsWith("'")) {
9485
+ return rawValue.slice(1, -1) || null;
9486
+ }
9487
+ return rawValue;
9488
+ }
9489
+ function normalizeCodexAppServerLaunchArgs(launchArgs) {
9490
+ const args = Array.isArray(launchArgs) ? launchArgs.map((entry) => String(entry).trim()).filter(Boolean) : [];
9491
+ const normalized = [];
9492
+ for (let index = 0;index < args.length; index += 1) {
9493
+ const current = args[index] ?? "";
9494
+ if (current === "--model" || current === "-m") {
9495
+ const model = normalizeCodexModelValue(args[index + 1]);
9496
+ if (model) {
9497
+ normalized.push("-c", encodeCodexModelConfig(model));
9498
+ index += 1;
9499
+ continue;
9500
+ }
9501
+ normalized.push(current);
9502
+ continue;
9503
+ }
9504
+ if (current.startsWith("--model=")) {
9505
+ const model = normalizeCodexModelValue(current.slice("--model=".length));
9506
+ if (model) {
9507
+ normalized.push("-c", encodeCodexModelConfig(model));
9508
+ continue;
9509
+ }
9510
+ }
9511
+ if (current.startsWith("-m=")) {
9512
+ const model = normalizeCodexModelValue(current.slice(3));
9513
+ if (model) {
9514
+ normalized.push("-c", encodeCodexModelConfig(model));
9515
+ continue;
9516
+ }
9517
+ }
9518
+ if (current === "-c" || current === "--config") {
9519
+ const next = args[index + 1];
9520
+ if (typeof next === "string") {
9521
+ const model = parseCodexModelConfig(next);
9522
+ normalized.push(current === "--config" ? "--config" : "-c", model ? encodeCodexModelConfig(model) : next);
9523
+ index += 1;
9524
+ continue;
9525
+ }
9526
+ }
9527
+ if (current.startsWith("--config=")) {
9528
+ const value = current.slice("--config=".length);
9529
+ const model = parseCodexModelConfig(value);
9530
+ normalized.push(model ? `--config=${encodeCodexModelConfig(model)}` : current);
9531
+ continue;
9532
+ }
9533
+ normalized.push(current);
9534
+ }
9535
+ return normalized;
9536
+ }
9537
+ function readCodexAppServerModelFromLaunchArgs(launchArgs) {
9538
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
9539
+ for (let index = 0;index < normalized.length; index += 1) {
9540
+ const current = normalized[index] ?? "";
9541
+ if (current === "-c" || current === "--config") {
9542
+ const model = parseCodexModelConfig(normalized[index + 1]);
9543
+ if (model) {
9544
+ return model;
9545
+ }
9546
+ index += 1;
9547
+ continue;
9548
+ }
9549
+ if (current.startsWith("--config=")) {
9550
+ const model = parseCodexModelConfig(current.slice("--config=".length));
9551
+ if (model) {
9552
+ return model;
9553
+ }
9554
+ }
9555
+ }
9556
+ return null;
9557
+ }
9451
9558
  function sessionKey2(options) {
9452
9559
  return `${options.agentName}:${options.sessionId}`;
9453
9560
  }
@@ -9556,6 +9663,13 @@ function isMissingCodexRolloutError2(error) {
9556
9663
  const message = errorMessage3(error).toLowerCase();
9557
9664
  return message.includes("no rollout found for thread id");
9558
9665
  }
9666
+ function resolveCodexCompletionGraceMs() {
9667
+ const parsed = Number.parseInt(process.env.OPENSCOUT_CODEX_COMPLETION_GRACE_MS ?? "", 10);
9668
+ if (Number.isFinite(parsed) && parsed >= 0) {
9669
+ return parsed;
9670
+ }
9671
+ return 60000;
9672
+ }
9559
9673
 
9560
9674
  class CodexAppServerSession {
9561
9675
  options;
@@ -9745,7 +9859,7 @@ class CodexAppServerSession {
9745
9859
  systemPrompt: options.systemPrompt,
9746
9860
  threadId: options.threadId ?? null,
9747
9861
  requireExistingThread: options.requireExistingThread === true,
9748
- launchArgs: Array.isArray(options.launchArgs) ? options.launchArgs : []
9862
+ launchArgs: normalizeCodexAppServerLaunchArgs(options.launchArgs)
9749
9863
  });
9750
9864
  }
9751
9865
  async ensureStarted() {
@@ -9767,6 +9881,7 @@ class CodexAppServerSession {
9767
9881
  await mkdir5(this.options.logsDirectory, { recursive: true });
9768
9882
  await writeFile5(join9(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
9769
9883
  const codexExecutable = await resolveCodexExecutable2();
9884
+ const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
9770
9885
  const env = buildManagedAgentEnvironment({
9771
9886
  agentName: this.options.agentName,
9772
9887
  currentDirectory: this.options.cwd,
@@ -9777,7 +9892,8 @@ class CodexAppServerSession {
9777
9892
  ...buildScoutMcpCodexLaunchArgs({
9778
9893
  currentDirectory: this.options.cwd,
9779
9894
  env
9780
- })
9895
+ }),
9896
+ ...launchArgs
9781
9897
  ], {
9782
9898
  cwd: this.options.cwd,
9783
9899
  env,
@@ -9875,6 +9991,8 @@ class CodexAppServerSession {
9875
9991
  turnId: "",
9876
9992
  startedAt: Date.now(),
9877
9993
  timer: null,
9994
+ graceTimer: null,
9995
+ timedOutAt: null,
9878
9996
  messageOrder: [],
9879
9997
  messageByItemId: new Map,
9880
9998
  resolve: resolve6,
@@ -9882,20 +10000,43 @@ class CodexAppServerSession {
9882
10000
  watchers: []
9883
10001
  };
9884
10002
  turn.timer = setTimeout(() => {
9885
- this.interrupt().catch(() => {
9886
- return;
9887
- });
9888
- if (this.activeTurn === turn) {
9889
- this.activeTurn = null;
9890
- }
9891
- for (const watcher of this.drainTurnWatchers(turn)) {
9892
- watcher.reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
9893
- }
9894
- reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
10003
+ this.scheduleTurnTimeout(turn, timeoutMs);
9895
10004
  }, timeoutMs);
9896
10005
  this.activeTurn = turn;
9897
10006
  return turn;
9898
10007
  }
10008
+ buildTurnTimeoutError(timeoutMs) {
10009
+ return new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`);
10010
+ }
10011
+ scheduleTurnTimeout(turn, timeoutMs) {
10012
+ if (turn.timedOutAt !== null) {
10013
+ return;
10014
+ }
10015
+ turn.timedOutAt = Date.now();
10016
+ this.interrupt().catch(() => {
10017
+ return;
10018
+ });
10019
+ const graceMs = resolveCodexCompletionGraceMs();
10020
+ if (graceMs <= 0) {
10021
+ this.rejectTimedOutTurn(turn, timeoutMs);
10022
+ return;
10023
+ }
10024
+ turn.graceTimer = setTimeout(() => {
10025
+ this.rejectTimedOutTurn(turn, timeoutMs);
10026
+ }, graceMs);
10027
+ }
10028
+ rejectTimedOutTurn(turn, timeoutMs) {
10029
+ if (this.activeTurn !== turn) {
10030
+ this.clearActiveTurn(turn);
10031
+ return;
10032
+ }
10033
+ this.clearActiveTurn(turn);
10034
+ const error = this.buildTurnTimeoutError(timeoutMs);
10035
+ for (const watcher of this.drainTurnWatchers(turn)) {
10036
+ watcher.reject(error);
10037
+ }
10038
+ turn.reject(error);
10039
+ }
9899
10040
  addTurnWatcher(turn, resolve6, reject, timeoutMs) {
9900
10041
  const watcher = {
9901
10042
  resolve: resolve6,
@@ -9929,6 +10070,9 @@ class CodexAppServerSession {
9929
10070
  if (turn.timer) {
9930
10071
  clearTimeout(turn.timer);
9931
10072
  }
10073
+ if (turn.graceTimer) {
10074
+ clearTimeout(turn.graceTimer);
10075
+ }
9932
10076
  if (this.activeTurn === turn) {
9933
10077
  this.activeTurn = null;
9934
10078
  }
@@ -11134,6 +11278,128 @@ function normalizeLocalAgentCapabilities(value) {
11134
11278
  function normalizeLocalAgentLaunchArgs(value) {
11135
11279
  return Array.isArray(value) ? value.map((entry) => String(entry).trim()).filter(Boolean) : [];
11136
11280
  }
11281
+ function normalizeRequestedModel(value) {
11282
+ const trimmed = value?.trim();
11283
+ return trimmed ? trimmed : undefined;
11284
+ }
11285
+ function readClaudeLaunchModel(launchArgs) {
11286
+ for (let index = 0;index < launchArgs.length; index += 1) {
11287
+ const current = launchArgs[index] ?? "";
11288
+ if (current === "--model") {
11289
+ const next = launchArgs[index + 1]?.trim();
11290
+ return next || undefined;
11291
+ }
11292
+ if (current.startsWith("--model=")) {
11293
+ const next = current.slice("--model=".length).trim();
11294
+ return next || undefined;
11295
+ }
11296
+ }
11297
+ return;
11298
+ }
11299
+ function normalizeLaunchArgsForHarness(harness, value) {
11300
+ const normalized = normalizeLocalAgentLaunchArgs(value);
11301
+ if (harness === "codex") {
11302
+ return normalizeCodexAppServerLaunchArgs(normalized);
11303
+ }
11304
+ return normalized;
11305
+ }
11306
+ function readLaunchModelForHarness(harness, launchArgs) {
11307
+ if (harness === "codex") {
11308
+ return readCodexAppServerModelFromLaunchArgs(launchArgs) ?? undefined;
11309
+ }
11310
+ if (harness === "claude") {
11311
+ return readClaudeLaunchModel(launchArgs ?? []);
11312
+ }
11313
+ return;
11314
+ }
11315
+ function stripLaunchModelForHarness(harness, launchArgs) {
11316
+ if (harness === "codex") {
11317
+ const next = [];
11318
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
11319
+ for (let index = 0;index < normalized.length; index += 1) {
11320
+ const current = normalized[index] ?? "";
11321
+ if (current === "-c" || current === "--config") {
11322
+ const value = normalized[index + 1];
11323
+ if (readCodexAppServerModelFromLaunchArgs(value ? [current, value] : [current])) {
11324
+ index += 1;
11325
+ continue;
11326
+ }
11327
+ next.push(current);
11328
+ if (value) {
11329
+ next.push(value);
11330
+ index += 1;
11331
+ }
11332
+ continue;
11333
+ }
11334
+ if (current.startsWith("--config=")) {
11335
+ if (readCodexAppServerModelFromLaunchArgs([current])) {
11336
+ continue;
11337
+ }
11338
+ }
11339
+ next.push(current);
11340
+ }
11341
+ return next;
11342
+ }
11343
+ if (harness === "claude") {
11344
+ const next = [];
11345
+ const normalized = normalizeLocalAgentLaunchArgs(launchArgs);
11346
+ for (let index = 0;index < normalized.length; index += 1) {
11347
+ const current = normalized[index] ?? "";
11348
+ if (current === "--model") {
11349
+ index += 1;
11350
+ continue;
11351
+ }
11352
+ if (current.startsWith("--model=")) {
11353
+ continue;
11354
+ }
11355
+ next.push(current);
11356
+ }
11357
+ return next;
11358
+ }
11359
+ return normalizeLocalAgentLaunchArgs(launchArgs);
11360
+ }
11361
+ function buildLaunchArgsForRequestedModel(harness, model) {
11362
+ if (harness === "codex") {
11363
+ return normalizeCodexAppServerLaunchArgs(["--model", model]);
11364
+ }
11365
+ if (harness === "claude") {
11366
+ return ["--model", model];
11367
+ }
11368
+ return [];
11369
+ }
11370
+ function applyRequestedModelToLaunchArgs(harness, launchArgs, model) {
11371
+ const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
11372
+ const requestedModel = normalizeRequestedModel(model);
11373
+ if (!requestedModel) {
11374
+ return normalized;
11375
+ }
11376
+ return [
11377
+ ...stripLaunchModelForHarness(harness, normalized),
11378
+ ...buildLaunchArgsForRequestedModel(harness, requestedModel)
11379
+ ];
11380
+ }
11381
+ function defaultHarnessForOverride(override, fallback = "claude") {
11382
+ return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
11383
+ }
11384
+ function launchArgsForOverrideHarness(override, harness) {
11385
+ const profileLaunchArgs = override.harnessProfiles?.[harness]?.launchArgs;
11386
+ if (profileLaunchArgs) {
11387
+ return normalizeLaunchArgsForHarness(harness, profileLaunchArgs);
11388
+ }
11389
+ if (harness === defaultHarnessForOverride(override, harness)) {
11390
+ return normalizeLaunchArgsForHarness(harness, override.launchArgs);
11391
+ }
11392
+ return [];
11393
+ }
11394
+ function overrideHarnessProfile(override, harness) {
11395
+ const profile = override.harnessProfiles?.[harness];
11396
+ return {
11397
+ cwd: normalizeProjectPath(profile?.cwd || override.runtime?.cwd || override.projectRoot || process.cwd()),
11398
+ transport: normalizeLocalAgentTransport(profile?.transport ?? override.runtime?.transport, harness),
11399
+ sessionId: normalizeTmuxSessionName2(profile?.sessionId, `${override.agentId}-${harness}`),
11400
+ launchArgs: launchArgsForOverrideHarness(override, harness)
11401
+ };
11402
+ }
11137
11403
  function normalizeManagedHarness2(value, fallback) {
11138
11404
  return value === "codex" ? "codex" : value === "claude" ? "claude" : fallback;
11139
11405
  }
@@ -11149,7 +11415,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11149
11415
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
11150
11416
  transport: normalizeLocalAgentTransport(profile.transport, harness),
11151
11417
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
11152
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
11418
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
11153
11419
  };
11154
11420
  }
11155
11421
  const runtimeHarness = normalizeManagedHarness2(record.harness, defaultHarness);
@@ -11158,7 +11424,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11158
11424
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
11159
11425
  transport: normalizeLocalAgentTransport(record.transport, runtimeHarness),
11160
11426
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${runtimeHarness}`),
11161
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11427
+ launchArgs: normalizeLaunchArgsForHarness(runtimeHarness, record.launchArgs)
11162
11428
  };
11163
11429
  }
11164
11430
  if (!nextProfiles[defaultHarness]) {
@@ -11166,7 +11432,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11166
11432
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
11167
11433
  transport: normalizeLocalAgentTransport(record.transport, defaultHarness),
11168
11434
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${defaultHarness}`),
11169
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11435
+ launchArgs: normalizeLaunchArgsForHarness(defaultHarness, record.launchArgs)
11170
11436
  };
11171
11437
  }
11172
11438
  for (const harness of ["claude", "codex"]) {
@@ -11178,7 +11444,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11178
11444
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
11179
11445
  transport: normalizeLocalAgentTransport(profile.transport, harness),
11180
11446
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
11181
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
11447
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
11182
11448
  };
11183
11449
  }
11184
11450
  return nextProfiles;
@@ -11202,14 +11468,14 @@ function recordForHarness(record, harnessOverride) {
11202
11468
  tmuxSession: fallbackSessionId,
11203
11469
  cwd: fallbackCwd,
11204
11470
  transport: fallbackTransport,
11205
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs),
11471
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs),
11206
11472
  harnessProfiles: {
11207
11473
  ...normalized.harnessProfiles,
11208
11474
  [selectedHarness]: {
11209
11475
  cwd: fallbackCwd,
11210
11476
  transport: fallbackTransport,
11211
11477
  sessionId: fallbackSessionId,
11212
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs)
11478
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs)
11213
11479
  }
11214
11480
  }
11215
11481
  };
@@ -11251,7 +11517,7 @@ function normalizeLocalAgentRecord(agentId, record) {
11251
11517
  harnessProfiles,
11252
11518
  transport: activeProfile?.transport ?? normalizeLocalAgentTransport(record.transport, harness),
11253
11519
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
11254
- launchArgs: activeProfile?.launchArgs ?? normalizeLocalAgentLaunchArgs(record.launchArgs)
11520
+ launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs)
11255
11521
  };
11256
11522
  }
11257
11523
  function localAgentStatusFromRecord(agentId, record, source) {
@@ -11320,7 +11586,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
11320
11586
  source: existing?.source && existing.source !== "inferred" ? existing.source : "manual",
11321
11587
  startedAt: normalizedRecord.startedAt,
11322
11588
  systemPrompt: normalizedRecord.systemPrompt,
11323
- launchArgs: normalizeLocalAgentLaunchArgs(normalizedRecord.launchArgs),
11589
+ launchArgs: normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs),
11324
11590
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
11325
11591
  defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
11326
11592
  harnessProfiles: normalizedRecord.harnessProfiles,
@@ -11341,7 +11607,7 @@ function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
11341
11607
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "codex_app_server" }),
11342
11608
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
11343
11609
  logsDirectory: relayAgentLogsDirectory(agentName),
11344
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11610
+ launchArgs: normalizeLaunchArgsForHarness("codex", record.launchArgs)
11345
11611
  };
11346
11612
  }
11347
11613
  function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
@@ -11352,7 +11618,7 @@ function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
11352
11618
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "claude_stream_json" }),
11353
11619
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
11354
11620
  logsDirectory: relayAgentLogsDirectory(agentName),
11355
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11621
+ launchArgs: normalizeLaunchArgsForHarness("claude", record.launchArgs)
11356
11622
  };
11357
11623
  }
11358
11624
  function isLocalAgentRecordOnline(agentName, record) {
@@ -11386,7 +11652,7 @@ function buildLocalAgentBootstrapPrompt(_harness, _systemPrompt, initialMessage)
11386
11652
  return initialMessage;
11387
11653
  }
11388
11654
  function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile, workerScript) {
11389
- const extraArgs = shellQuoteArguments(normalizeLocalAgentLaunchArgs(record.launchArgs));
11655
+ const extraArgs = shellQuoteArguments(normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(record.harness), record.launchArgs));
11390
11656
  if (normalizeLocalAgentHarness(record.harness) === "codex") {
11391
11657
  return `exec bash ${JSON.stringify(workerScript ?? join11(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
11392
11658
  }
@@ -11758,6 +12024,7 @@ async function startLocalAgent(input) {
11758
12024
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
11759
12025
  const transport = normalizeLocalAgentTransport(undefined, effectiveHarness);
11760
12026
  const sessionId = normalizeTmuxSessionName2(undefined, `${instance.id}-${effectiveHarness}`);
12027
+ const launchArgs = applyRequestedModelToLaunchArgs(effectiveHarness, [], input.model);
11761
12028
  const existingForInstance = overrides[instance.id];
11762
12029
  if (existingForInstance && normalizeProjectPath(existingForInstance.projectRoot) !== projectRoot) {
11763
12030
  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.`);
@@ -11771,13 +12038,14 @@ async function startLocalAgent(input) {
11771
12038
  projectConfigPath: coldProjectConfigPath,
11772
12039
  source: "manual",
11773
12040
  startedAt: nowSeconds2(),
12041
+ launchArgs,
11774
12042
  defaultHarness: effectiveHarness,
11775
12043
  harnessProfiles: {
11776
12044
  [effectiveHarness]: {
11777
12045
  cwd: effectiveCwd ?? projectRoot,
11778
12046
  transport,
11779
12047
  sessionId,
11780
- launchArgs: []
12048
+ launchArgs
11781
12049
  }
11782
12050
  },
11783
12051
  runtime: {
@@ -11798,6 +12066,9 @@ async function startLocalAgent(input) {
11798
12066
  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.`);
11799
12067
  }
11800
12068
  const resolvedHarness = preferredHarness ?? matchingOverride.runtime?.harness;
12069
+ const nextHarness = normalizeManagedHarness2(resolvedHarness, "claude");
12070
+ const nextDefaultHarness = preferredHarness ? normalizeManagedHarness2(preferredHarness, "claude") : defaultHarnessForOverride(matchingOverride, "claude");
12071
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(nextHarness, launchArgsForOverrideHarness(matchingOverride, nextHarness), input.model);
11801
12072
  overrides[instance.id] = {
11802
12073
  agentId: instance.id,
11803
12074
  definitionId: requestedDefinitionId,
@@ -11808,10 +12079,16 @@ async function startLocalAgent(input) {
11808
12079
  source: "manual",
11809
12080
  startedAt: matchingOverride.startedAt ?? nowSeconds2(),
11810
12081
  systemPrompt: matchingOverride.systemPrompt,
11811
- launchArgs: matchingOverride.launchArgs,
12082
+ launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
11812
12083
  capabilities: matchingOverride.capabilities,
11813
- defaultHarness: normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness, "claude"),
11814
- harnessProfiles: matchingOverride.harnessProfiles,
12084
+ defaultHarness: nextDefaultHarness,
12085
+ harnessProfiles: {
12086
+ ...matchingOverride.harnessProfiles ?? {},
12087
+ [nextHarness]: {
12088
+ ...overrideHarnessProfile(matchingOverride, nextHarness),
12089
+ launchArgs: nextLaunchArgs
12090
+ }
12091
+ },
11815
12092
  runtime: {
11816
12093
  cwd: effectiveCwd ?? matchingOverride.runtime?.cwd ?? matchingProjectRoot,
11817
12094
  harness: resolvedHarness,
@@ -11823,6 +12100,22 @@ async function startLocalAgent(input) {
11823
12100
  await writeRelayAgentOverrides(overrides);
11824
12101
  targetAgentId = instance.id;
11825
12102
  } else {
12103
+ if (input.model) {
12104
+ const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
12105
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), input.model);
12106
+ overrides[matchingAgentId] = {
12107
+ ...matchingOverride,
12108
+ launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
12109
+ harnessProfiles: {
12110
+ ...matchingOverride.harnessProfiles ?? {},
12111
+ [existingHarness]: {
12112
+ ...overrideHarnessProfile(matchingOverride, existingHarness),
12113
+ launchArgs: nextLaunchArgs
12114
+ }
12115
+ }
12116
+ };
12117
+ await writeRelayAgentOverrides(overrides);
12118
+ }
11826
12119
  targetAgentId = matchingAgentId;
11827
12120
  }
11828
12121
  await ensureLocalAgentBindingOnline(targetAgentId, process.env.OPENSCOUT_NODE_ID ?? "local", {
@@ -11935,6 +12228,7 @@ function readPersistedClaudeSessionId(agentName) {
11935
12228
  }
11936
12229
  function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
11937
12230
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
12231
+ const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
11938
12232
  const definitionId = normalizedRecord.definitionId ?? agentId;
11939
12233
  const displayName = titleCaseLocalAgentName(definitionId);
11940
12234
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
@@ -11959,7 +12253,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
11959
12253
  defaultSelector: instance.defaultSelector,
11960
12254
  nodeQualifier: instance.nodeQualifier,
11961
12255
  workspaceQualifier: instance.workspaceQualifier,
11962
- branch: instance.branch
12256
+ branch: instance.branch,
12257
+ ...configuredModel ? { model: configuredModel } : {}
11963
12258
  }
11964
12259
  },
11965
12260
  agent: {
@@ -11986,7 +12281,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
11986
12281
  defaultSelector: instance.defaultSelector,
11987
12282
  nodeQualifier: instance.nodeQualifier,
11988
12283
  workspaceQualifier: instance.workspaceQualifier,
11989
- branch: instance.branch
12284
+ branch: instance.branch,
12285
+ ...configuredModel ? { model: configuredModel } : {}
11990
12286
  },
11991
12287
  agentClass: "general",
11992
12288
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
@@ -12019,6 +12315,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
12019
12315
  nodeQualifier: instance.nodeQualifier,
12020
12316
  workspaceQualifier: instance.workspaceQualifier,
12021
12317
  branch: instance.branch,
12318
+ ...configuredModel ? { model: configuredModel } : {},
12022
12319
  ...externalSessionId ? { externalSessionId } : {}
12023
12320
  }
12024
12321
  }
@@ -13130,8 +13427,73 @@ async function sendScoutMessage(input) {
13130
13427
  }
13131
13428
  const currentDirectory = input.currentDirectory ?? process.cwd();
13132
13429
  const createdAtMs = input.createdAtMs ?? Date.now();
13133
- const mentionResolution = await resolveMentionTargets(broker.snapshot, input.body, currentDirectory);
13134
13430
  const senderId = await resolveConversationActorId(broker.baseUrl, broker.snapshot, broker.node.id, input.senderId, currentDirectory);
13431
+ const requestedTargetLabel = input.targetLabel?.trim();
13432
+ if (requestedTargetLabel) {
13433
+ const targetResolution = await resolveSingleBrokerTarget(broker.snapshot, requestedTargetLabel, currentDirectory);
13434
+ if (targetResolution.kind !== "resolved") {
13435
+ return {
13436
+ usedBroker: true,
13437
+ invokedTargets: [],
13438
+ unresolvedTargets: [requestedTargetLabel]
13439
+ };
13440
+ }
13441
+ const target = targetResolution.target;
13442
+ const targetReady = await ensureTargetRelayAgentRegistered(broker.baseUrl, broker.snapshot, broker.node.id, target.agentId, currentDirectory);
13443
+ if (!targetReady || target.agentId === senderId) {
13444
+ return {
13445
+ usedBroker: true,
13446
+ invokedTargets: [],
13447
+ unresolvedTargets: [requestedTargetLabel]
13448
+ };
13449
+ }
13450
+ let conversation2;
13451
+ if (!input.channel) {
13452
+ const dm = await ensureBrokerDirectConversationBetween(broker.baseUrl, broker.snapshot, broker.node.id, senderId, target.agentId);
13453
+ conversation2 = dm.conversation;
13454
+ } else {
13455
+ conversation2 = await ensureBrokerConversation(broker.baseUrl, broker.snapshot, broker.node.id, input.channel, senderId, [target.agentId]);
13456
+ }
13457
+ const messageId2 = `m-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
13458
+ const speechText2 = input.shouldSpeak ? stripScoutAgentSelectorLabels(input.body) : "";
13459
+ const returnAddress2 = buildScoutReturnAddress2(broker.snapshot, senderId, {
13460
+ conversationId: conversation2.id,
13461
+ replyToMessageId: messageId2
13462
+ });
13463
+ await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.messages, {
13464
+ id: messageId2,
13465
+ conversationId: conversation2.id,
13466
+ actorId: senderId,
13467
+ originNodeId: broker.node.id,
13468
+ class: conversation2.kind === "system" ? "system" : "agent",
13469
+ body: input.body,
13470
+ mentions: [{ actorId: target.agentId, label: target.label }],
13471
+ speech: speechText2 ? { text: speechText2 } : undefined,
13472
+ audience: {
13473
+ notify: [target.agentId],
13474
+ reason: relayAudienceReason(conversation2)
13475
+ },
13476
+ visibility: conversation2.visibility,
13477
+ policy: "durable",
13478
+ createdAt: createdAtMs,
13479
+ metadata: {
13480
+ source: "scout-cli",
13481
+ relayChannel: relayChannelMetadata(conversation2, input.channel),
13482
+ relayTargetIds: [target.agentId],
13483
+ relayMessageId: messageId2,
13484
+ returnAddress: returnAddress2
13485
+ }
13486
+ });
13487
+ return {
13488
+ usedBroker: true,
13489
+ conversationId: conversation2.id,
13490
+ messageId: messageId2,
13491
+ invokedTargets: [target.agentId],
13492
+ unresolvedTargets: [],
13493
+ routeKind: relayRouteKind(conversation2)
13494
+ };
13495
+ }
13496
+ const mentionResolution = await resolveMentionTargets(broker.snapshot, input.body, currentDirectory);
13135
13497
  const availableTargets = (await Promise.all(mentionResolution.resolved.map(async (target) => await ensureTargetRelayAgentRegistered(broker.baseUrl, broker.snapshot, broker.node.id, target.agentId, currentDirectory) ? target : null))).filter((target) => Boolean(target));
13136
13498
  const validTargets = [
13137
13499
  ...new Set(availableTargets.map((target) => target.agentId).filter((target) => target !== senderId && Boolean(broker.snapshot.agents[target])))
@@ -14413,6 +14775,7 @@ function buildScoutAgentCard(binding, options = {}) {
14413
14775
  const selector = binding.agent.selector?.trim() || metadataString3(binding.agent.metadata, "selector");
14414
14776
  const defaultSelector = binding.agent.defaultSelector?.trim() || metadataString3(binding.agent.metadata, "defaultSelector");
14415
14777
  const branch = metadataString3(binding.agent.metadata, "branch") || metadataString3(binding.endpoint.metadata, "branch");
14778
+ const model = metadataString3(binding.endpoint.metadata, "model") || metadataString3(binding.agent.metadata, "model");
14416
14779
  const description = metadataString3(binding.agent.metadata, "description");
14417
14780
  const version = metadataString3(binding.agent.metadata, "version");
14418
14781
  const documentationUrl = metadataString3(binding.agent.metadata, "documentationUrl") || metadataString3(binding.agent.metadata, "docsUrl");
@@ -14470,7 +14833,8 @@ function buildScoutAgentCard(binding, options = {}) {
14470
14833
  metadata: {
14471
14834
  actorId: binding.actor.id,
14472
14835
  endpointId: binding.endpoint.id,
14473
- wakePolicy: binding.agent.wakePolicy
14836
+ wakePolicy: binding.agent.wakePolicy,
14837
+ ...model ? { model } : {}
14474
14838
  }
14475
14839
  };
14476
14840
  }
@@ -14504,6 +14868,7 @@ async function createScoutAgentCard(input) {
14504
14868
  agentName: input.agentName,
14505
14869
  displayName: input.displayName,
14506
14870
  harness: input.harness,
14871
+ model: input.model,
14507
14872
  currentDirectory: input.currentDirectory
14508
14873
  });
14509
14874
  const currentDirectory = input.currentDirectory ?? input.projectPath;
@@ -14588,7 +14953,7 @@ __export(exports_card, {
14588
14953
  import { createInterface } from "readline";
14589
14954
  function renderCardCommandHelp() {
14590
14955
  return [
14591
- "Usage: scout card create [path] [--name <alias>] [--display-name <name>] [--harness <claude|codex>] [--as <requester>] [--no-input] [--path <path>]",
14956
+ "Usage: scout card create [path] [--name <alias>] [--display-name <name>] [--harness <claude|codex>] [--model <model>] [--as <requester>] [--no-input] [--path <path>]",
14592
14957
  "",
14593
14958
  "Create a dedicated Scout agent card with a reply-ready return address.",
14594
14959
  "",
@@ -14597,7 +14962,7 @@ function renderCardCommandHelp() {
14597
14962
  "",
14598
14963
  "Examples:",
14599
14964
  " scout card create",
14600
- " scout card create ~/dev/openscout-worktrees/shell-fix --name shellfix --harness claude"
14965
+ " scout card create ~/dev/openscout-worktrees/shell-fix --name shellfix --harness claude --model claude-sonnet-4-6"
14601
14966
  ].join(`
14602
14967
  `);
14603
14968
  }
@@ -14661,6 +15026,7 @@ async function runCardCommand(context, args) {
14661
15026
  agentName,
14662
15027
  displayName,
14663
15028
  harness: parseScoutHarness(options.harness),
15029
+ model: options.model,
14664
15030
  currentDirectory: options.currentDirectory,
14665
15031
  createdById: resolveScoutAgentName(options.requesterId)
14666
15032
  });
@@ -45391,6 +45757,7 @@ function defaultScoutMcpDependencies(env) {
45391
45757
  agentName,
45392
45758
  displayName,
45393
45759
  harness,
45760
+ model,
45394
45761
  currentDirectory,
45395
45762
  createdById
45396
45763
  }) => createScoutAgentCard({
@@ -45398,12 +45765,21 @@ function defaultScoutMcpDependencies(env) {
45398
45765
  agentName,
45399
45766
  displayName,
45400
45767
  harness,
45768
+ model,
45401
45769
  currentDirectory,
45402
45770
  createdById
45403
45771
  }),
45404
- sendMessage: ({ senderId, body, channel, shouldSpeak, currentDirectory }) => sendScoutMessage({
45772
+ sendMessage: ({
45405
45773
  senderId,
45406
45774
  body,
45775
+ targetLabel,
45776
+ channel,
45777
+ shouldSpeak,
45778
+ currentDirectory
45779
+ }) => sendScoutMessage({
45780
+ senderId,
45781
+ body,
45782
+ targetLabel,
45407
45783
  channel,
45408
45784
  shouldSpeak,
45409
45785
  currentDirectory
@@ -45515,7 +45891,8 @@ function createScoutMcpServer(options) {
45515
45891
  senderId: string2().optional(),
45516
45892
  agentName: string2().optional(),
45517
45893
  displayName: string2().optional(),
45518
- harness: _enum2(LOCAL_AGENT_HARNESS_VALUES).optional()
45894
+ harness: _enum2(LOCAL_AGENT_HARNESS_VALUES).optional(),
45895
+ model: string2().optional()
45519
45896
  }),
45520
45897
  outputSchema: cardCreateResultSchema,
45521
45898
  annotations: {
@@ -45530,7 +45907,8 @@ function createScoutMcpServer(options) {
45530
45907
  senderId,
45531
45908
  agentName,
45532
45909
  displayName,
45533
- harness
45910
+ harness,
45911
+ model
45534
45912
  }) => {
45535
45913
  const resolvedCurrentDirectory = resolveToolCurrentDirectory(currentDirectory, options.defaultCurrentDirectory);
45536
45914
  const resolvedSenderId = await deps.resolveSenderId(senderId, resolvedCurrentDirectory, env);
@@ -45539,6 +45917,7 @@ function createScoutMcpServer(options) {
45539
45917
  agentName: agentName?.trim() || undefined,
45540
45918
  displayName: displayName?.trim() || undefined,
45541
45919
  harness,
45920
+ model: model?.trim() || undefined,
45542
45921
  currentDirectory: resolvedCurrentDirectory,
45543
45922
  createdById: resolvedSenderId
45544
45923
  });
@@ -45618,11 +45997,12 @@ function createScoutMcpServer(options) {
45618
45997
  });
45619
45998
  server.registerTool("messages_send", {
45620
45999
  title: "Send Scout Message",
45621
- description: "Post a broker-backed Scout tell/update. Use this for heads-up, replies, and status. One explicit target without a channel becomes a DM. Group delivery requires an explicit channel. Use broadcast or channel='shared' for shared updates. For owned work or a reply lifecycle, use invocations_ask instead. Prefer mentionAgentIds for first-class targeting.",
46000
+ description: "Post a broker-backed Scout tell/update. Use this for heads-up, replies, and status. One explicit target without a channel becomes a DM. Group delivery requires an explicit channel. Use broadcast or channel='shared' for shared updates. Pass targetLabel for the single-call broker-resolved path when you know who to contact but do not have an exact id. For owned work or a reply lifecycle, use invocations_ask instead. Prefer mentionAgentIds for first-class targeting when you already have exact ids.",
45622
46001
  inputSchema: object2({
45623
46002
  body: string2().min(1),
45624
46003
  currentDirectory: string2().optional(),
45625
46004
  senderId: string2().optional(),
46005
+ targetLabel: string2().optional(),
45626
46006
  channel: string2().optional(),
45627
46007
  shouldSpeak: boolean2().optional(),
45628
46008
  mentionAgentIds: array(string2()).optional()
@@ -45638,6 +46018,7 @@ function createScoutMcpServer(options) {
45638
46018
  body,
45639
46019
  currentDirectory,
45640
46020
  senderId,
46021
+ targetLabel,
45641
46022
  channel,
45642
46023
  shouldSpeak,
45643
46024
  mentionAgentIds
@@ -45674,6 +46055,32 @@ function createScoutMcpServer(options) {
45674
46055
  structuredContent: structuredContent2
45675
46056
  };
45676
46057
  }
46058
+ if (targetLabel?.trim()) {
46059
+ const result2 = await deps.sendMessage({
46060
+ senderId: resolvedSenderId,
46061
+ body,
46062
+ targetLabel: targetLabel.trim(),
46063
+ channel,
46064
+ shouldSpeak,
46065
+ currentDirectory: resolvedCurrentDirectory
46066
+ });
46067
+ const structuredContent2 = {
46068
+ currentDirectory: resolvedCurrentDirectory,
46069
+ senderId: resolvedSenderId,
46070
+ mode: "target_label",
46071
+ usedBroker: result2.usedBroker,
46072
+ conversationId: result2.conversationId ?? null,
46073
+ messageId: result2.messageId ?? null,
46074
+ invokedTargetIds: result2.invokedTargets,
46075
+ unresolvedTargetIds: result2.unresolvedTargets,
46076
+ routeKind: result2.routeKind ?? null,
46077
+ routingError: result2.routingError ?? null
46078
+ };
46079
+ return {
46080
+ content: createTextContent(structuredContent2),
46081
+ structuredContent: structuredContent2
46082
+ };
46083
+ }
45677
46084
  const result = await deps.sendMessage({
45678
46085
  senderId: resolvedSenderId,
45679
46086
  body,
@@ -46025,7 +46432,7 @@ var init_scout_mcp = __esm(async () => {
46025
46432
  sendResultSchema = object2({
46026
46433
  currentDirectory: string2(),
46027
46434
  senderId: string2(),
46028
- mode: _enum2(["body_mentions", "explicit_targets"]),
46435
+ mode: _enum2(["body_mentions", "explicit_targets", "target_label"]),
46029
46436
  usedBroker: boolean2(),
46030
46437
  conversationId: string2().nullable(),
46031
46438
  messageId: string2().nullable(),
@@ -49402,9 +49809,74 @@ var init_config3 = __esm(() => {
49402
49809
  CONFIG_FILE = join22(CONFIG_DIR, "config.json");
49403
49810
  });
49404
49811
 
49812
+ // ../../apps/desktop/src/core/pairing/runtime/bridge/web-handoff.ts
49813
+ import { randomBytes as randomBytes2 } from "crypto";
49814
+ function pruneExpiredWebHandoffs(now = Date.now()) {
49815
+ for (const [token, record3] of activeWebHandoffs) {
49816
+ if (record3.expiresAt <= now) {
49817
+ activeWebHandoffs.delete(token);
49818
+ }
49819
+ }
49820
+ }
49821
+ function scopesMatch(left, right) {
49822
+ if (left.kind !== right.kind) {
49823
+ return false;
49824
+ }
49825
+ if (left.sessionId !== right.sessionId) {
49826
+ return false;
49827
+ }
49828
+ if (left.kind === "session") {
49829
+ return true;
49830
+ }
49831
+ return left.turnId === right.turnId && left.blockId === right.blockId;
49832
+ }
49833
+ function pathForWebHandoffScope(scope) {
49834
+ switch (scope.kind) {
49835
+ case "session":
49836
+ return `/handoff/session/${encodeURIComponent(scope.sessionId)}`;
49837
+ case "file_change":
49838
+ return `/handoff/file-change/${encodeURIComponent(scope.sessionId)}/${encodeURIComponent(scope.turnId)}/${encodeURIComponent(scope.blockId)}`;
49839
+ }
49840
+ }
49841
+ function issueWebHandoff(scope, deviceId) {
49842
+ pruneExpiredWebHandoffs();
49843
+ const token = randomBytes2(24).toString("base64url");
49844
+ const expiresAt = Date.now() + WEB_HANDOFF_TTL_MS;
49845
+ activeWebHandoffs.set(token, {
49846
+ token,
49847
+ deviceId: deviceId?.trim() || null,
49848
+ expiresAt,
49849
+ scope
49850
+ });
49851
+ return { token, expiresAt, scope };
49852
+ }
49853
+ function readAuthorizedWebHandoff(token, scope) {
49854
+ pruneExpiredWebHandoffs();
49855
+ if (!token) {
49856
+ return null;
49857
+ }
49858
+ const record3 = activeWebHandoffs.get(token);
49859
+ if (!record3) {
49860
+ return null;
49861
+ }
49862
+ if (!scopesMatch(record3.scope, scope)) {
49863
+ return null;
49864
+ }
49865
+ return record3;
49866
+ }
49867
+ var SCOUT_WEB_HANDOFF_COOKIE = "scout_handoff", WEB_HANDOFF_TTL_MS, activeWebHandoffs;
49868
+ var init_web_handoff = __esm(() => {
49869
+ WEB_HANDOFF_TTL_MS = 5 * 60 * 1000;
49870
+ activeWebHandoffs = new Map;
49871
+ });
49872
+
49405
49873
  // ../../apps/desktop/src/core/pairing/runtime/bridge/fileserver.ts
49406
49874
  import { isAbsolute as isAbsolute2 } from "path";
49407
49875
  import { homedir as homedir16 } from "os";
49876
+ function isTrustedLoopbackHostname(hostname5) {
49877
+ const normalized = hostname5.trim().toLowerCase();
49878
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || LOOPBACK_IPV4_HOST_PATTERN.test(normalized);
49879
+ }
49408
49880
  function isAllowedPath(filePath) {
49409
49881
  if (!isAbsolute2(filePath))
49410
49882
  return false;
@@ -49415,7 +49887,7 @@ function isAllowedPath(filePath) {
49415
49887
  return ALLOWED_ROOTS.some((root) => filePath.startsWith(root));
49416
49888
  }
49417
49889
  function startFileServer(options) {
49418
- const { port } = options;
49890
+ const { port, bridge } = options;
49419
49891
  let server = null;
49420
49892
  function start() {
49421
49893
  try {
@@ -49423,7 +49895,7 @@ function startFileServer(options) {
49423
49895
  port,
49424
49896
  fetch(req) {
49425
49897
  try {
49426
- return route(new URL(req.url));
49898
+ return route(req, new URL(req.url), bridge);
49427
49899
  } catch (err) {
49428
49900
  console.error(`[fileserver] ${req.url} \u2192`, err.message);
49429
49901
  return new Response("Internal error", { status: 500 });
@@ -49446,12 +49918,19 @@ function startFileServer(options) {
49446
49918
  start();
49447
49919
  return { port, stop, restart };
49448
49920
  }
49449
- function route(url2) {
49921
+ function route(req, url2, bridge) {
49450
49922
  const path2 = url2.pathname;
49451
- if (path2 === "/file")
49923
+ if (path2 === "/file") {
49924
+ if (!isTrustedLoopbackHostname(url2.hostname)) {
49925
+ return new Response("Forbidden", { status: 403 });
49926
+ }
49452
49927
  return serveFile(url2);
49928
+ }
49453
49929
  if (path2 === "/health")
49454
49930
  return Response.json({ ok: true });
49931
+ if (path2.startsWith("/handoff/")) {
49932
+ return serveHandoffPage(req, url2, bridge);
49933
+ }
49455
49934
  return new Response("Pairing file server", { status: 200 });
49456
49935
  }
49457
49936
  function serveFile(url2) {
@@ -49465,9 +49944,525 @@ function serveFile(url2) {
49465
49944
  return new Response("Not found", { status: 404 });
49466
49945
  return new Response(file2);
49467
49946
  }
49468
- var ALLOWED_ROOTS;
49947
+ function serveHandoffPage(req, url2, bridge) {
49948
+ if (!bridge) {
49949
+ return new Response("Secure handoff unavailable", { status: 503 });
49950
+ }
49951
+ const scope = parseHandoffScope(url2);
49952
+ if (!scope) {
49953
+ return new Response("Not found", { status: 404 });
49954
+ }
49955
+ const token = readHandoffToken(req);
49956
+ const authorized = readAuthorizedWebHandoff(token, scope);
49957
+ if (!authorized) {
49958
+ return unauthorizedHandoffResponse();
49959
+ }
49960
+ let html;
49961
+ switch (scope.kind) {
49962
+ case "session": {
49963
+ const snapshot = bridge.getSessionSnapshot(scope.sessionId);
49964
+ if (!snapshot) {
49965
+ return new Response("Session handoff expired", { status: 404 });
49966
+ }
49967
+ html = renderSessionHandoffPage(snapshot);
49968
+ break;
49969
+ }
49970
+ case "file_change": {
49971
+ const snapshot = bridge.getSessionSnapshot(scope.sessionId);
49972
+ if (!snapshot) {
49973
+ return new Response("Session handoff expired", { status: 404 });
49974
+ }
49975
+ const target = findFileChangeBlock(snapshot, scope.turnId, scope.blockId);
49976
+ if (!target) {
49977
+ return new Response("File change handoff expired", { status: 404 });
49978
+ }
49979
+ html = renderFileChangeHandoffPage(snapshot, target.turn, target.block);
49980
+ break;
49981
+ }
49982
+ }
49983
+ const headers = new Headers({
49984
+ "content-type": "text/html; charset=utf-8",
49985
+ "cache-control": "no-store",
49986
+ "x-content-type-options": "nosniff"
49987
+ });
49988
+ headers.set("set-cookie", `${SCOUT_WEB_HANDOFF_COOKIE}=${authorized.token}; Max-Age=300; Path=/handoff; HttpOnly; SameSite=Strict`);
49989
+ return new Response(html, { status: 200, headers });
49990
+ }
49991
+ function unauthorizedHandoffResponse() {
49992
+ return new Response("Secure handoff required", {
49993
+ status: 401,
49994
+ headers: {
49995
+ "cache-control": "no-store",
49996
+ "set-cookie": `${SCOUT_WEB_HANDOFF_COOKIE}=; Max-Age=0; Path=/handoff; HttpOnly; SameSite=Strict`
49997
+ }
49998
+ });
49999
+ }
50000
+ function parseHandoffScope(url2) {
50001
+ const parts = url2.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
50002
+ if (parts[0] !== "handoff") {
50003
+ return null;
50004
+ }
50005
+ if (parts[1] === "session" && parts[2]) {
50006
+ return { kind: "session", sessionId: parts[2] };
50007
+ }
50008
+ if (parts[1] === "file-change" && parts[2] && parts[3] && parts[4]) {
50009
+ return {
50010
+ kind: "file_change",
50011
+ sessionId: parts[2],
50012
+ turnId: parts[3],
50013
+ blockId: parts[4]
50014
+ };
50015
+ }
50016
+ return null;
50017
+ }
50018
+ function readHandoffToken(req) {
50019
+ const headerToken = req.headers.get("x-scout-handoff-token")?.trim();
50020
+ if (headerToken) {
50021
+ return headerToken;
50022
+ }
50023
+ const cookieHeader = req.headers.get("cookie");
50024
+ if (!cookieHeader) {
50025
+ return null;
50026
+ }
50027
+ for (const part of cookieHeader.split(";")) {
50028
+ const [name, ...rest] = part.trim().split("=");
50029
+ if (name === SCOUT_WEB_HANDOFF_COOKIE) {
50030
+ const value = rest.join("=").trim();
50031
+ return value || null;
50032
+ }
50033
+ }
50034
+ return null;
50035
+ }
50036
+ function findFileChangeBlock(snapshot, turnId, blockId) {
50037
+ const turn = snapshot.turns.find((candidate) => candidate.id === turnId);
50038
+ if (!turn) {
50039
+ return null;
50040
+ }
50041
+ const blockState = turn.blocks.find((candidate) => candidate.block.id === blockId);
50042
+ const block = blockState?.block;
50043
+ if (!block || block.type !== "action" || block.action.kind !== "file_change") {
50044
+ return null;
50045
+ }
50046
+ return { turn, block };
50047
+ }
50048
+ function renderPage(title, body) {
50049
+ return `<!doctype html>
50050
+ <html lang="en">
50051
+ <head>
50052
+ <meta charset="utf-8" />
50053
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
50054
+ <title>${escapeHtml(title)}</title>
50055
+ <style>
50056
+ :root {
50057
+ color-scheme: dark;
50058
+ --bg: #0b0f14;
50059
+ --panel: rgba(19, 27, 39, 0.92);
50060
+ --panel-strong: rgba(12, 18, 28, 0.96);
50061
+ --line: rgba(148, 163, 184, 0.16);
50062
+ --line-strong: rgba(148, 163, 184, 0.24);
50063
+ --text: #e5ecf5;
50064
+ --muted: #90a0b4;
50065
+ --accent: #63d0ff;
50066
+ --green: #54d38a;
50067
+ --amber: #f8c36a;
50068
+ --red: #ff7b72;
50069
+ --surface-add: rgba(84, 211, 138, 0.08);
50070
+ --surface-del: rgba(255, 123, 114, 0.08);
50071
+ }
50072
+ * { box-sizing: border-box; }
50073
+ body {
50074
+ margin: 0;
50075
+ background:
50076
+ radial-gradient(circle at top left, rgba(99, 208, 255, 0.12), transparent 32%),
50077
+ linear-gradient(180deg, #090d12 0%, #0b0f14 55%, #111723 100%);
50078
+ color: var(--text);
50079
+ font: 14px/1.5 ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
50080
+ }
50081
+ .wrap {
50082
+ width: min(980px, calc(100vw - 24px));
50083
+ margin: 0 auto;
50084
+ padding: 20px 0 40px;
50085
+ }
50086
+ .hero, .panel {
50087
+ background: var(--panel);
50088
+ border: 1px solid var(--line);
50089
+ border-radius: 18px;
50090
+ backdrop-filter: blur(18px);
50091
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
50092
+ }
50093
+ .hero {
50094
+ padding: 18px 18px 16px;
50095
+ margin-bottom: 14px;
50096
+ }
50097
+ .eyebrow {
50098
+ display: inline-flex;
50099
+ align-items: center;
50100
+ gap: 8px;
50101
+ color: var(--muted);
50102
+ font-size: 11px;
50103
+ letter-spacing: 0.14em;
50104
+ text-transform: uppercase;
50105
+ }
50106
+ .dot {
50107
+ width: 8px;
50108
+ height: 8px;
50109
+ border-radius: 999px;
50110
+ background: var(--accent);
50111
+ box-shadow: 0 0 14px rgba(99, 208, 255, 0.65);
50112
+ }
50113
+ h1 {
50114
+ margin: 12px 0 10px;
50115
+ font-size: 24px;
50116
+ line-height: 1.15;
50117
+ }
50118
+ .meta, .stack {
50119
+ display: flex;
50120
+ flex-wrap: wrap;
50121
+ gap: 8px;
50122
+ }
50123
+ .pill {
50124
+ display: inline-flex;
50125
+ align-items: center;
50126
+ gap: 6px;
50127
+ padding: 5px 10px;
50128
+ border-radius: 999px;
50129
+ border: 1px solid var(--line-strong);
50130
+ background: rgba(255, 255, 255, 0.03);
50131
+ color: var(--muted);
50132
+ font-size: 12px;
50133
+ }
50134
+ .pill strong {
50135
+ color: var(--text);
50136
+ font-weight: 600;
50137
+ }
50138
+ .stack { flex-direction: column; }
50139
+ .panel {
50140
+ margin-top: 12px;
50141
+ overflow: hidden;
50142
+ }
50143
+ .panel-inner {
50144
+ padding: 16px;
50145
+ }
50146
+ .turn {
50147
+ border-top: 1px solid var(--line);
50148
+ }
50149
+ .turn:first-child {
50150
+ border-top: none;
50151
+ }
50152
+ .turn-head {
50153
+ padding: 14px 16px;
50154
+ display: flex;
50155
+ justify-content: space-between;
50156
+ gap: 12px;
50157
+ align-items: center;
50158
+ background: rgba(255, 255, 255, 0.02);
50159
+ }
50160
+ .turn-title {
50161
+ font-size: 13px;
50162
+ font-weight: 600;
50163
+ letter-spacing: 0.02em;
50164
+ }
50165
+ .turn-subtitle {
50166
+ color: var(--muted);
50167
+ font-size: 12px;
50168
+ margin-top: 2px;
50169
+ }
50170
+ .blocks {
50171
+ padding: 8px;
50172
+ }
50173
+ .block {
50174
+ padding: 12px;
50175
+ border-radius: 14px;
50176
+ border: 1px solid var(--line);
50177
+ background: var(--panel-strong);
50178
+ margin: 8px;
50179
+ }
50180
+ .block-head {
50181
+ display: flex;
50182
+ justify-content: space-between;
50183
+ align-items: center;
50184
+ gap: 12px;
50185
+ margin-bottom: 10px;
50186
+ }
50187
+ .block-type {
50188
+ color: var(--muted);
50189
+ text-transform: uppercase;
50190
+ letter-spacing: 0.12em;
50191
+ font-size: 11px;
50192
+ }
50193
+ .label {
50194
+ color: var(--muted);
50195
+ font-size: 12px;
50196
+ }
50197
+ .label strong {
50198
+ color: var(--text);
50199
+ }
50200
+ pre {
50201
+ margin: 0;
50202
+ white-space: pre-wrap;
50203
+ word-break: break-word;
50204
+ overflow-wrap: anywhere;
50205
+ font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
50206
+ }
50207
+ .code {
50208
+ padding: 12px;
50209
+ border-radius: 12px;
50210
+ border: 1px solid var(--line);
50211
+ background: rgba(255, 255, 255, 0.02);
50212
+ }
50213
+ .diff-line-add { background: var(--surface-add); color: #8ef3b7; }
50214
+ .diff-line-del { background: var(--surface-del); color: #ff9d96; }
50215
+ .details {
50216
+ margin-top: 10px;
50217
+ border: 1px solid var(--line);
50218
+ border-radius: 12px;
50219
+ overflow: hidden;
50220
+ }
50221
+ details > summary {
50222
+ list-style: none;
50223
+ cursor: pointer;
50224
+ padding: 10px 12px;
50225
+ background: rgba(255, 255, 255, 0.03);
50226
+ color: var(--muted);
50227
+ font-size: 12px;
50228
+ text-transform: uppercase;
50229
+ letter-spacing: 0.1em;
50230
+ }
50231
+ details[open] > summary {
50232
+ border-bottom: 1px solid var(--line);
50233
+ }
50234
+ .empty {
50235
+ padding: 28px 18px;
50236
+ color: var(--muted);
50237
+ }
50238
+ .grid {
50239
+ display: grid;
50240
+ gap: 10px;
50241
+ }
50242
+ .grid.two {
50243
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
50244
+ }
50245
+ a.inline {
50246
+ color: var(--accent);
50247
+ text-decoration: none;
50248
+ }
50249
+ .status-streaming { color: var(--amber); }
50250
+ .status-completed { color: var(--green); }
50251
+ .status-failed, .status-error { color: var(--red); }
50252
+ </style>
50253
+ </head>
50254
+ <body>
50255
+ <main class="wrap">
50256
+ ${body}
50257
+ </main>
50258
+ </body>
50259
+ </html>`;
50260
+ }
50261
+ function renderSessionHandoffPage(snapshot) {
50262
+ const title = snapshot.session.name || snapshot.session.id;
50263
+ const hero = `
50264
+ <section class="hero">
50265
+ <div class="eyebrow"><span class="dot"></span> Secure Proxy Session Handoff</div>
50266
+ <h1>${escapeHtml(title)}</h1>
50267
+ <div class="meta">
50268
+ <span class="pill"><strong>ID</strong> ${escapeHtml(snapshot.session.id)}</span>
50269
+ <span class="pill"><strong>Adapter</strong> ${escapeHtml(snapshot.session.adapterType)}</span>
50270
+ <span class="pill"><strong>Status</strong> ${escapeHtml(snapshot.session.status)}</span>
50271
+ ${snapshot.session.model ? `<span class="pill"><strong>Model</strong> ${escapeHtml(snapshot.session.model)}</span>` : ""}
50272
+ ${snapshot.session.cwd ? `<span class="pill"><strong>Workspace</strong> ${escapeHtml(snapshot.session.cwd)}</span>` : ""}
50273
+ </div>
50274
+ </section>
50275
+ `;
50276
+ const turns = snapshot.turns.length > 0 ? snapshot.turns.map((turn) => {
50277
+ const turnBody = turn.blocks.length > 0 ? turn.blocks.map(({ block }) => renderBlock(block)).join("") : `<div class="empty">No blocks were captured for this turn.</div>`;
50278
+ return `
50279
+ <section class="turn">
50280
+ <div class="turn-head">
50281
+ <div>
50282
+ <div class="turn-title">${escapeHtml(turn.id)}</div>
50283
+ <div class="turn-subtitle">${formatTimestamp(turn.startedAt)}${turn.endedAt ? ` to ${formatTimestamp(turn.endedAt)}` : ""}</div>
50284
+ </div>
50285
+ <span class="pill"><strong>Turn</strong> <span class="status-${escapeHtml(turn.status)}">${escapeHtml(turn.status)}</span></span>
50286
+ </div>
50287
+ <div class="blocks">${turnBody}</div>
50288
+ </section>
50289
+ `;
50290
+ }).join("") : `<div class="empty">This session has not produced any turns yet.</div>`;
50291
+ return renderPage(title, `${hero}<section class="panel">${turns}</section>`);
50292
+ }
50293
+ function renderFileChangeHandoffPage(snapshot, turn, block) {
50294
+ const action = block.action;
50295
+ const title = action.path || snapshot.session.name || snapshot.session.id;
50296
+ const hero = `
50297
+ <section class="hero">
50298
+ <div class="eyebrow"><span class="dot"></span> Secure Proxy File Change</div>
50299
+ <h1>${escapeHtml(title)}</h1>
50300
+ <div class="meta">
50301
+ <span class="pill"><strong>Session</strong> ${escapeHtml(snapshot.session.name || snapshot.session.id)}</span>
50302
+ <span class="pill"><strong>Turn</strong> ${escapeHtml(turn.id)}</span>
50303
+ <span class="pill"><strong>Status</strong> ${escapeHtml(action.status)}</span>
50304
+ </div>
50305
+ </section>
50306
+ `;
50307
+ const body = `
50308
+ <section class="panel">
50309
+ <div class="panel-inner stack">
50310
+ <div class="grid two">
50311
+ <div class="pill"><strong>Path</strong> ${escapeHtml(action.path)}</div>
50312
+ <div class="pill"><strong>Block</strong> ${escapeHtml(block.id)}</div>
50313
+ </div>
50314
+ ${action.diff ? renderDetailsSection("Diff", renderDiff(action.diff), true) : `<div class="empty">No diff was recorded for this action.</div>`}
50315
+ ${action.output ? renderDetailsSection("Output", `<div class="code"><pre>${escapeHtml(action.output)}</pre></div>`) : ""}
50316
+ </div>
50317
+ </section>
50318
+ `;
50319
+ return renderPage(title, `${hero}${body}`);
50320
+ }
50321
+ function renderBlock(block) {
50322
+ switch (block.type) {
50323
+ case "text":
50324
+ return renderTextLikeBlock("Text", block.text);
50325
+ case "reasoning":
50326
+ return renderTextLikeBlock("Reasoning", block.text);
50327
+ case "error":
50328
+ return `
50329
+ <article class="block">
50330
+ <div class="block-head">
50331
+ <div class="block-type">Error</div>
50332
+ <span class="pill status-error">${escapeHtml(block.status)}</span>
50333
+ </div>
50334
+ <div class="code"><pre>${escapeHtml(block.message)}</pre></div>
50335
+ </article>
50336
+ `;
50337
+ case "file":
50338
+ return `
50339
+ <article class="block">
50340
+ <div class="block-head">
50341
+ <div class="block-type">File</div>
50342
+ <span class="pill">${escapeHtml(block.status)}</span>
50343
+ </div>
50344
+ <div class="grid">
50345
+ ${block.name ? `<div class="label"><strong>Name</strong> ${escapeHtml(block.name)}</div>` : ""}
50346
+ <div class="label"><strong>MIME</strong> ${escapeHtml(block.mimeType)}</div>
50347
+ </div>
50348
+ </article>
50349
+ `;
50350
+ case "question":
50351
+ return renderQuestionBlock(block);
50352
+ case "action":
50353
+ return renderActionBlock(block);
50354
+ }
50355
+ }
50356
+ function renderTextLikeBlock(label, text) {
50357
+ return `
50358
+ <article class="block">
50359
+ <div class="block-head">
50360
+ <div class="block-type">${escapeHtml(label)}</div>
50361
+ </div>
50362
+ <div class="code"><pre>${escapeHtml(text)}</pre></div>
50363
+ </article>
50364
+ `;
50365
+ }
50366
+ function renderQuestionBlock(block) {
50367
+ const answer = block.answer?.length ? block.answer.join(", ") : "Awaiting answer";
50368
+ const options = block.options?.length ? `<div class="label"><strong>Options</strong> ${escapeHtml(block.options.map((option) => option.label).join(", "))}</div>` : "";
50369
+ return `
50370
+ <article class="block">
50371
+ <div class="block-head">
50372
+ <div class="block-type">Question</div>
50373
+ <span class="pill">${escapeHtml(block.questionStatus)}</span>
50374
+ </div>
50375
+ ${block.header ? `<div class="label"><strong>${escapeHtml(block.header)}</strong></div>` : ""}
50376
+ <div class="code"><pre>${escapeHtml(block.question)}</pre></div>
50377
+ <div class="stack" style="margin-top: 10px;">
50378
+ ${options}
50379
+ <div class="label"><strong>Answer</strong> ${escapeHtml(answer)}</div>
50380
+ </div>
50381
+ </article>
50382
+ `;
50383
+ }
50384
+ function renderActionBlock(block) {
50385
+ const action = block.action;
50386
+ const parts = [];
50387
+ switch (action.kind) {
50388
+ case "file_change":
50389
+ parts.push(`<div class="label"><strong>Path</strong> ${escapeHtml(action.path)}</div>`);
50390
+ if (action.diff) {
50391
+ parts.push(renderDetailsSection("Diff", renderDiff(action.diff)));
50392
+ }
50393
+ break;
50394
+ case "command":
50395
+ parts.push(`<div class="label"><strong>Command</strong></div><div class="code"><pre>${escapeHtml(action.command)}</pre></div>`);
50396
+ if (typeof action.exitCode === "number") {
50397
+ parts.push(`<div class="label"><strong>Exit Code</strong> ${escapeHtml(String(action.exitCode))}</div>`);
50398
+ }
50399
+ break;
50400
+ case "tool_call":
50401
+ parts.push(`<div class="label"><strong>Tool</strong> ${escapeHtml(action.toolName)}</div>`);
50402
+ if (action.toolCallId) {
50403
+ parts.push(`<div class="label"><strong>Call ID</strong> ${escapeHtml(action.toolCallId)}</div>`);
50404
+ }
50405
+ break;
50406
+ case "subagent":
50407
+ parts.push(`<div class="label"><strong>Agent</strong> ${escapeHtml(action.agentName || action.agentId)}</div>`);
50408
+ if (action.prompt) {
50409
+ parts.push(`<div class="code"><pre>${escapeHtml(action.prompt)}</pre></div>`);
50410
+ }
50411
+ break;
50412
+ }
50413
+ if (action.approval) {
50414
+ parts.push(`<div class="label"><strong>Approval</strong> ${escapeHtml(action.approval.risk || "medium")} risk${action.approval.description ? ` - ${escapeHtml(action.approval.description)}` : ""}</div>`);
50415
+ }
50416
+ if (action.output) {
50417
+ parts.push(renderDetailsSection("Output", `<div class="code"><pre>${escapeHtml(action.output)}</pre></div>`));
50418
+ }
50419
+ return `
50420
+ <article class="block">
50421
+ <div class="block-head">
50422
+ <div class="block-type">${escapeHtml(action.kind.replace("_", " "))}</div>
50423
+ <span class="pill">${escapeHtml(action.status)}</span>
50424
+ </div>
50425
+ <div class="stack">${parts.join("")}</div>
50426
+ </article>
50427
+ `;
50428
+ }
50429
+ function renderDetailsSection(title, innerHtml, open2 = false) {
50430
+ return `
50431
+ <details class="details"${open2 ? " open" : ""}>
50432
+ <summary>${escapeHtml(title)}</summary>
50433
+ <div class="panel-inner">${innerHtml}</div>
50434
+ </details>
50435
+ `;
50436
+ }
50437
+ function renderDiff(diff) {
50438
+ const lines = diff.split(`
50439
+ `).map((line) => {
50440
+ const className = line.startsWith("+") ? "diff-line-add" : line.startsWith("-") ? "diff-line-del" : "";
50441
+ return `<div class="${className}"><pre>${escapeHtml(line || " ")}</pre></div>`;
50442
+ }).join("");
50443
+ return `<div class="code">${lines}</div>`;
50444
+ }
50445
+ function escapeHtml(value) {
50446
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
50447
+ }
50448
+ function formatTimestamp(value) {
50449
+ if (typeof value !== "number" || !Number.isFinite(value)) {
50450
+ return "Unknown time";
50451
+ }
50452
+ try {
50453
+ return new Intl.DateTimeFormat("en-US", {
50454
+ dateStyle: "medium",
50455
+ timeStyle: "short"
50456
+ }).format(value);
50457
+ } catch {
50458
+ return new Date(value).toISOString();
50459
+ }
50460
+ }
50461
+ var ALLOWED_ROOTS, LOOPBACK_IPV4_HOST_PATTERN;
49469
50462
  var init_fileserver = __esm(() => {
50463
+ init_web_handoff();
49470
50464
  ALLOWED_ROOTS = [homedir16(), "/tmp"];
50465
+ LOOPBACK_IPV4_HOST_PATTERN = /^127(?:\.\d{1,3}){3}$/;
49471
50466
  });
49472
50467
 
49473
50468
  // ../../apps/desktop/src/server/db-queries.ts
@@ -50319,6 +51314,20 @@ function readSyncStatus(bridge, sessionId) {
50319
51314
  oldestBufferedSeq: bridge.oldestBufferedSeq(sessionId)
50320
51315
  };
50321
51316
  }
51317
+ function resolveSyncSessionId(bridge, preferredSessionId) {
51318
+ if (preferredSessionId) {
51319
+ return preferredSessionId;
51320
+ }
51321
+ let latestSessionId = null;
51322
+ let latestActivityAt = Number.NEGATIVE_INFINITY;
51323
+ for (const session of bridge.getSessionSummaries()) {
51324
+ if (session.lastActivityAt > latestActivityAt) {
51325
+ latestActivityAt = session.lastActivityAt;
51326
+ latestSessionId = session.sessionId;
51327
+ }
51328
+ }
51329
+ return latestSessionId;
51330
+ }
50322
51331
  function sessionRegistryErrorResponse(reqId, error48) {
50323
51332
  if (!isSessionRegistryError(error48)) {
50324
51333
  return null;
@@ -50388,23 +51397,36 @@ async function handleRPCInner(bridge, req, deviceId) {
50388
51397
  return { id: req.id, result: { ok: true } };
50389
51398
  }
50390
51399
  case "sync/replay": {
50391
- const p = req.params;
50392
- if (!p.sessionId) {
50393
- return { id: req.id, error: { code: -32602, message: "sessionId is required" } };
51400
+ const p = req.params ?? {};
51401
+ if (typeof p.lastSeq !== "number") {
51402
+ return { id: req.id, error: { code: -32602, message: "lastSeq is required" } };
51403
+ }
51404
+ const sessionId = resolveSyncSessionId(bridge, p.sessionId);
51405
+ if (!sessionId) {
51406
+ return { id: req.id, result: { events: [] } };
50394
51407
  }
50395
- const events2 = replaySyncEvents(bridge, p.sessionId, p.lastSeq);
51408
+ const events2 = replaySyncEvents(bridge, sessionId, p.lastSeq);
50396
51409
  return { id: req.id, result: { events: events2 } };
50397
51410
  }
50398
51411
  case "sync/status": {
50399
51412
  const p = req.params ?? {};
50400
- if (!p.sessionId) {
50401
- return { id: req.id, error: { code: -32602, message: "sessionId is required" } };
51413
+ const sessionCount = bridge.listSessions().length;
51414
+ const sessionId = resolveSyncSessionId(bridge, p.sessionId);
51415
+ if (!sessionId) {
51416
+ return {
51417
+ id: req.id,
51418
+ result: {
51419
+ currentSeq: 0,
51420
+ oldestBufferedSeq: 0,
51421
+ sessionCount
51422
+ }
51423
+ };
50402
51424
  }
50403
51425
  return {
50404
51426
  id: req.id,
50405
51427
  result: {
50406
- ...readSyncStatus(bridge, p.sessionId),
50407
- sessionCount: bridge.listSessions().length
51428
+ ...readSyncStatus(bridge, sessionId),
51429
+ sessionCount
50408
51430
  }
50409
51431
  };
50410
51432
  }
@@ -55584,6 +56606,20 @@ function readSyncStatus2(bridge, sessionId) {
55584
56606
  oldestBufferedSeq: bridge.oldestBufferedSeq(sessionId)
55585
56607
  };
55586
56608
  }
56609
+ function resolveSyncSessionId2(bridge, preferredSessionId) {
56610
+ if (preferredSessionId) {
56611
+ return preferredSessionId;
56612
+ }
56613
+ let latestSessionId = null;
56614
+ let latestActivityAt = Number.NEGATIVE_INFINITY;
56615
+ for (const session2 of bridge.getSessionSummaries()) {
56616
+ if (session2.lastActivityAt > latestActivityAt) {
56617
+ latestActivityAt = session2.lastActivityAt;
56618
+ latestSessionId = session2.sessionId;
56619
+ }
56620
+ }
56621
+ return latestSessionId;
56622
+ }
55587
56623
  var t, logged, procedure, MARKER_FILES2, sessionRouter, mobileRouter, workspaceRouter, historyRouter, promptRouter, syncRouter, bridgeRouter;
55588
56624
  var init_router = __esm(async () => {
55589
56625
  init_dist3();
@@ -55592,6 +56628,7 @@ var init_router = __esm(async () => {
55592
56628
  init_log();
55593
56629
  init_config3();
55594
56630
  init_db_queries();
56631
+ init_web_handoff();
55595
56632
  await __promiseAll([
55596
56633
  init_service5(),
55597
56634
  init_src2(),
@@ -55799,6 +56836,64 @@ var init_router = __esm(async () => {
55799
56836
  limit: typeof input.limit === "number" ? input.limit : null
55800
56837
  }, resolveMobileCurrentDirectory2());
55801
56838
  }),
56839
+ webHandoff: procedure.input(exports_external.object({
56840
+ kind: exports_external.enum(["session", "file_change"]),
56841
+ sessionId: exports_external.string(),
56842
+ turnId: exports_external.string().optional(),
56843
+ blockId: exports_external.string().optional()
56844
+ })).mutation(({ input, ctx }) => {
56845
+ if (!ctx.deviceId) {
56846
+ throw new TRPCError({
56847
+ code: "UNAUTHORIZED",
56848
+ message: "Secure web handoff requires a paired mobile device"
56849
+ });
56850
+ }
56851
+ const snapshot = ctx.bridge.getSessionSnapshot(input.sessionId);
56852
+ if (!snapshot) {
56853
+ throw new TRPCError({
56854
+ code: "NOT_FOUND",
56855
+ message: `No session: ${input.sessionId}`
56856
+ });
56857
+ }
56858
+ let scope;
56859
+ let title = snapshot.session.name || snapshot.session.id;
56860
+ if (input.kind === "file_change") {
56861
+ if (!input.turnId || !input.blockId) {
56862
+ throw new TRPCError({
56863
+ code: "BAD_REQUEST",
56864
+ message: "turnId and blockId are required for file_change handoffs"
56865
+ });
56866
+ }
56867
+ const turn = snapshot.turns.find((candidate) => candidate.id === input.turnId);
56868
+ const block = turn?.blocks.find((candidate) => candidate.block.id === input.blockId)?.block;
56869
+ if (!turn || !block || block.type !== "action" || block.action.kind !== "file_change") {
56870
+ throw new TRPCError({
56871
+ code: "NOT_FOUND",
56872
+ message: "File change block not found"
56873
+ });
56874
+ }
56875
+ scope = {
56876
+ kind: "file_change",
56877
+ sessionId: input.sessionId,
56878
+ turnId: input.turnId,
56879
+ blockId: input.blockId
56880
+ };
56881
+ title = block.action.path || title;
56882
+ } else {
56883
+ scope = {
56884
+ kind: "session",
56885
+ sessionId: input.sessionId
56886
+ };
56887
+ }
56888
+ const issued = issueWebHandoff(scope, ctx.deviceId);
56889
+ return {
56890
+ kind: input.kind,
56891
+ path: pathForWebHandoffScope(scope),
56892
+ token: issued.token,
56893
+ expiresAt: issued.expiresAt,
56894
+ title
56895
+ };
56896
+ }),
55802
56897
  createSession: procedure.input(exports_external.object({
55803
56898
  workspaceId: exports_external.string(),
55804
56899
  harness: exports_external.string().optional(),
@@ -56004,14 +57099,27 @@ var init_router = __esm(async () => {
56004
57099
  })
56005
57100
  });
56006
57101
  syncRouter = t.router({
56007
- replay: procedure.input(exports_external.object({ lastSeq: exports_external.number(), sessionId: exports_external.string() })).query(({ input, ctx }) => {
56008
- const events2 = replaySyncEvents2(ctx.bridge, input.sessionId, input.lastSeq);
57102
+ replay: procedure.input(exports_external.object({ lastSeq: exports_external.number(), sessionId: exports_external.string().optional() })).query(({ input, ctx }) => {
57103
+ const sessionId = resolveSyncSessionId2(ctx.bridge, input.sessionId);
57104
+ if (!sessionId) {
57105
+ return { events: [] };
57106
+ }
57107
+ const events2 = replaySyncEvents2(ctx.bridge, sessionId, input.lastSeq);
56009
57108
  return { events: events2 };
56010
57109
  }),
56011
- status: procedure.input(exports_external.object({ sessionId: exports_external.string() })).query(({ input, ctx }) => {
57110
+ status: procedure.input(exports_external.object({ sessionId: exports_external.string().optional() }).optional()).query(({ input, ctx }) => {
57111
+ const sessionCount = ctx.bridge.listSessions().length;
57112
+ const sessionId = resolveSyncSessionId2(ctx.bridge, input?.sessionId);
57113
+ if (!sessionId) {
57114
+ return {
57115
+ currentSeq: 0,
57116
+ oldestBufferedSeq: 0,
57117
+ sessionCount
57118
+ };
57119
+ }
56012
57120
  return {
56013
- ...readSyncStatus2(ctx.bridge, input.sessionId),
56014
- sessionCount: ctx.bridge.listSessions().length
57121
+ ...readSyncStatus2(ctx.bridge, sessionId),
57122
+ sessionCount
56015
57123
  };
56016
57124
  })
56017
57125
  });
@@ -60620,7 +61728,7 @@ async function startPairingRuntime(options) {
60620
61728
  secure: config2.secure,
60621
61729
  identity: config2.secure ? identity2 : undefined
60622
61730
  });
60623
- const fileServer = startFileServer({ port: config2.port + 2 });
61731
+ const fileServer = startFileServer({ port: config2.port + 2, bridge });
60624
61732
  const relayUrl = options?.relayUrl?.trim() || config2.relay || null;
60625
61733
  if (!relayUrl) {
60626
61734
  fileServer.stop();
@@ -60952,19 +62060,58 @@ function findStoredCerts() {
60952
62060
  return null;
60953
62061
  }
60954
62062
  }
60955
- function getTailscaleHostname() {
62063
+ function readTailscaleStatus2() {
60956
62064
  try {
60957
62065
  const output = execSync4("tailscale status --self=true --peers=false --json", {
60958
62066
  stdio: ["pipe", "pipe", "pipe"],
60959
62067
  timeout: 5000
60960
62068
  }).toString();
60961
62069
  const data = JSON.parse(output);
60962
- const dnsName = data?.Self?.DNSName ?? "";
60963
- return dnsName.replace(/\.$/, "") || null;
62070
+ const dnsName = typeof data.Self?.DNSName === "string" ? data.Self.DNSName.replace(/\.$/, "") : "";
62071
+ return {
62072
+ backendState: typeof data.BackendState === "string" ? data.BackendState : null,
62073
+ dnsName: dnsName || null,
62074
+ online: data.Self?.Online !== false,
62075
+ health: Array.isArray(data.Health) ? data.Health.filter((entry) => typeof entry === "string") : []
62076
+ };
60964
62077
  } catch {
60965
62078
  return null;
60966
62079
  }
60967
62080
  }
62081
+ function resolveRelayEndpointForTailscaleStatus(port, tailscale2) {
62082
+ const backendState = tailscale2?.backendState?.trim().toLowerCase() ?? "";
62083
+ const hostname5 = tailscale2?.dnsName ?? null;
62084
+ const tailscaleRunning = backendState === "running" && tailscale2?.online !== false && Boolean(hostname5);
62085
+ const tls = resolveTls(tailscaleRunning ? hostname5 : null);
62086
+ if (tailscaleRunning && hostname5 && tls) {
62087
+ return {
62088
+ relayUrl: `wss://${hostname5}:${port}`,
62089
+ options: { tls }
62090
+ };
62091
+ }
62092
+ if (tailscaleRunning && hostname5) {
62093
+ pairingLog.warn("relay", "tailscale is running without TLS; falling back to insecure websocket relay", {
62094
+ hostname: hostname5,
62095
+ port
62096
+ });
62097
+ return {
62098
+ relayUrl: `ws://${hostname5}:${port}`,
62099
+ options: {}
62100
+ };
62101
+ }
62102
+ if (hostname5 && backendState && backendState !== "running") {
62103
+ pairingLog.warn("relay", "tailscale hostname detected but tailscale is not running; using local-only relay endpoint", {
62104
+ hostname: hostname5,
62105
+ backendState,
62106
+ health: tailscale2?.health ?? [],
62107
+ port
62108
+ });
62109
+ }
62110
+ return {
62111
+ relayUrl: `ws://127.0.0.1:${port}`,
62112
+ options: {}
62113
+ };
62114
+ }
60968
62115
  function generateTailscaleCerts(hostname5) {
60969
62116
  mkdirSync11(PAIRING_DIR2, { recursive: true });
60970
62117
  const certPath = join27(PAIRING_DIR2, `${hostname5}.crt`);
@@ -61010,28 +62157,7 @@ function resolveTls(hostname5) {
61010
62157
  return generateTailscaleCerts(hostname5);
61011
62158
  }
61012
62159
  function resolveRelayEndpoint(port) {
61013
- const hostname5 = getTailscaleHostname();
61014
- const tls = resolveTls(hostname5);
61015
- if (hostname5 && tls) {
61016
- return {
61017
- relayUrl: `wss://${hostname5}:${port}`,
61018
- options: { tls }
61019
- };
61020
- }
61021
- if (hostname5) {
61022
- pairingLog.warn("relay", "tailscale hostname detected without TLS; falling back to insecure websocket relay", {
61023
- hostname: hostname5,
61024
- port
61025
- });
61026
- return {
61027
- relayUrl: `ws://${hostname5}:${port}`,
61028
- options: {}
61029
- };
61030
- }
61031
- return {
61032
- relayUrl: `ws://127.0.0.1:${port}`,
61033
- options: {}
61034
- };
62160
+ return resolveRelayEndpointForTailscaleStatus(port, readTailscaleStatus2());
61035
62161
  }
61036
62162
  function startManagedRelay(port = 7889) {
61037
62163
  const endpoint = resolveRelayEndpoint(port);
@@ -61983,11 +63109,13 @@ __export(exports_server, {
61983
63109
  runServerCommand: () => runServerCommand,
61984
63110
  resolveScoutWebServerEntry: () => resolveScoutWebServerEntry,
61985
63111
  resolveScoutControlPlaneWebServerEntry: () => resolveScoutControlPlaneWebServerEntry,
63112
+ resolveBunExecutable: () => resolveBunExecutable3,
61986
63113
  renderServerCommandHelp: () => renderServerCommandHelp,
61987
63114
  normalizeServerOpenPath: () => normalizeServerOpenPath
61988
63115
  });
61989
63116
  import { spawn as spawn5 } from "child_process";
61990
- import { existsSync as existsSync20 } from "fs";
63117
+ import { accessSync as accessSync2, constants as constants4, existsSync as existsSync20 } from "fs";
63118
+ import { homedir as homedir23 } from "os";
61991
63119
  import { dirname as dirname14, join as join28, resolve as resolve14 } from "path";
61992
63120
  import { fileURLToPath as fileURLToPath9 } from "url";
61993
63121
  function renderServerCommandHelp() {
@@ -62108,6 +63236,42 @@ function resolveBundledStaticClientRoot(entry, _mode) {
62108
63236
  const indexPath = join28(clientDirectory, "index.html");
62109
63237
  return existsSync20(indexPath) ? clientDirectory : null;
62110
63238
  }
63239
+ function isExecutable4(filePath) {
63240
+ if (!filePath) {
63241
+ return false;
63242
+ }
63243
+ try {
63244
+ accessSync2(filePath, constants4.X_OK);
63245
+ return true;
63246
+ } catch {
63247
+ return false;
63248
+ }
63249
+ }
63250
+ function resolveBunExecutable3(env) {
63251
+ const explicitPaths = [
63252
+ env.SCOUT_BUN_BIN,
63253
+ env.OPENSCOUT_BUN_BIN,
63254
+ env.BUN_BIN
63255
+ ].filter((candidate) => Boolean(candidate?.trim()));
63256
+ for (const candidate of explicitPaths) {
63257
+ if (isExecutable4(candidate)) {
63258
+ return candidate;
63259
+ }
63260
+ }
63261
+ const pathEntries = (env.PATH ?? "").split(":").filter(Boolean);
63262
+ const commonDirectories = [
63263
+ join28(homedir23(), ".bun", "bin"),
63264
+ "/opt/homebrew/bin",
63265
+ "/usr/local/bin"
63266
+ ];
63267
+ for (const directory of [...pathEntries, ...commonDirectories]) {
63268
+ const candidate = join28(directory.replace(/^~(?=$|\/)/, homedir23()), "bun");
63269
+ if (isExecutable4(candidate)) {
63270
+ return candidate;
63271
+ }
63272
+ }
63273
+ return "bun";
63274
+ }
62111
63275
  function buildMergedServerEnv(entry, mode, flagEnv) {
62112
63276
  const bundledStaticClientRoot = resolveBundledStaticClientRoot(entry, mode);
62113
63277
  const autoEnv = {};
@@ -62280,8 +63444,9 @@ async function openBrowser(url2) {
62280
63444
  });
62281
63445
  }
62282
63446
  async function spawnDetachedServer(entry, env) {
63447
+ const bunExecutable = resolveBunExecutable3(env);
62283
63448
  await new Promise((resolvePromise, rejectPromise) => {
62284
- const child = spawn5("bun", ["run", entry], {
63449
+ const child = spawn5(bunExecutable, ["run", entry], {
62285
63450
  detached: true,
62286
63451
  stdio: "ignore",
62287
63452
  env,
@@ -62373,8 +63538,9 @@ async function runServerCommand(context, args) {
62373
63538
  context.output.writeValue(result, renderServerOpenResult);
62374
63539
  return;
62375
63540
  }
63541
+ const bunExecutable = resolveBunExecutable3(mergedEnv);
62376
63542
  await new Promise((resolvePromise, rejectPromise) => {
62377
- const child = spawn5("bun", ["run", selection.entry], {
63543
+ const child = spawn5(bunExecutable, ["run", selection.entry], {
62378
63544
  stdio: "inherit",
62379
63545
  env: mergedEnv
62380
63546
  });
@@ -71572,7 +72738,7 @@ var __defProp3, __returnValue2 = (v) => v, __export2 = (target, all) => {
71572
72738
  configurable: true,
71573
72739
  set: __exportSetter2.bind(all, name)
71574
72740
  });
71575
- }, exports_src, loadYoga, yoga_wasm_base64_esm_default, Align, BoxSizing, Dimension, Direction, Display, Edge, Errata, ExperimentalFeature, FlexDirection, Gutter, Justify, LogLevel, MeasureMode, NodeType, Overflow, PositionType, Unit, Wrap, constants4, YGEnums_default, Yoga, src_default, VALID_BORDER_STYLES, BorderChars, BorderCharArrays, KeyHandler, InternalKeyHandler, CSS_COLOR_NAMES, block_default, shade_default, slick_default, tiny_default, huge_default, grid_default, pallet_default, fonts, parsedFonts, TextAttributes, ATTRIBUTE_BASE_BITS = 8, ATTRIBUTE_BASE_MASK = 255, DebugOverlayCorner, TargetChannel, ATTRIBUTE_BASE_MASK2 = 255, LINK_ID_SHIFT = 8, LINK_ID_PAYLOAD_MASK = 16777215, BrandedStyledText, StyledText, black = (input) => applyStyle(input, { fg: "black" }), red = (input) => applyStyle(input, { fg: "red" }), green = (input) => applyStyle(input, { fg: "green" }), yellow = (input) => applyStyle(input, { fg: "yellow" }), blue = (input) => applyStyle(input, { fg: "blue" }), magenta = (input) => applyStyle(input, { fg: "magenta" }), cyan = (input) => applyStyle(input, { fg: "cyan" }), white = (input) => applyStyle(input, { fg: "white" }), brightBlack = (input) => applyStyle(input, { fg: "brightBlack" }), brightRed = (input) => applyStyle(input, { fg: "brightRed" }), brightGreen = (input) => applyStyle(input, { fg: "brightGreen" }), brightYellow = (input) => applyStyle(input, { fg: "brightYellow" }), brightBlue = (input) => applyStyle(input, { fg: "brightBlue" }), brightMagenta = (input) => applyStyle(input, { fg: "brightMagenta" }), brightCyan = (input) => applyStyle(input, { fg: "brightCyan" }), brightWhite = (input) => applyStyle(input, { fg: "brightWhite" }), bgBlack = (input) => applyStyle(input, { bg: "black" }), bgRed = (input) => applyStyle(input, { bg: "red" }), bgGreen = (input) => applyStyle(input, { bg: "green" }), bgYellow = (input) => applyStyle(input, { bg: "yellow" }), bgBlue = (input) => applyStyle(input, { bg: "blue" }), bgMagenta = (input) => applyStyle(input, { bg: "magenta" }), bgCyan = (input) => applyStyle(input, { bg: "cyan" }), bgWhite = (input) => applyStyle(input, { bg: "white" }), bold = (input) => applyStyle(input, { bold: true }), italic = (input) => applyStyle(input, { italic: true }), underline = (input) => applyStyle(input, { underline: true }), strikethrough = (input) => applyStyle(input, { strikethrough: true }), dim = (input) => applyStyle(input, { dim: true }), reverse = (input) => applyStyle(input, { reverse: true }), blink = (input) => applyStyle(input, { blink: true }), fg = (color) => (input) => applyStyle(input, { fg: color }), bg = (color) => (input) => applyStyle(input, { bg: color }), link = (url2) => (input) => {
72741
+ }, exports_src, loadYoga, yoga_wasm_base64_esm_default, Align, BoxSizing, Dimension, Direction, Display, Edge, Errata, ExperimentalFeature, FlexDirection, Gutter, Justify, LogLevel, MeasureMode, NodeType, Overflow, PositionType, Unit, Wrap, constants5, YGEnums_default, Yoga, src_default, VALID_BORDER_STYLES, BorderChars, BorderCharArrays, KeyHandler, InternalKeyHandler, CSS_COLOR_NAMES, block_default, shade_default, slick_default, tiny_default, huge_default, grid_default, pallet_default, fonts, parsedFonts, TextAttributes, ATTRIBUTE_BASE_BITS = 8, ATTRIBUTE_BASE_MASK = 255, DebugOverlayCorner, TargetChannel, ATTRIBUTE_BASE_MASK2 = 255, LINK_ID_SHIFT = 8, LINK_ID_PAYLOAD_MASK = 16777215, BrandedStyledText, StyledText, black = (input) => applyStyle(input, { fg: "black" }), red = (input) => applyStyle(input, { fg: "red" }), green = (input) => applyStyle(input, { fg: "green" }), yellow = (input) => applyStyle(input, { fg: "yellow" }), blue = (input) => applyStyle(input, { fg: "blue" }), magenta = (input) => applyStyle(input, { fg: "magenta" }), cyan = (input) => applyStyle(input, { fg: "cyan" }), white = (input) => applyStyle(input, { fg: "white" }), brightBlack = (input) => applyStyle(input, { fg: "brightBlack" }), brightRed = (input) => applyStyle(input, { fg: "brightRed" }), brightGreen = (input) => applyStyle(input, { fg: "brightGreen" }), brightYellow = (input) => applyStyle(input, { fg: "brightYellow" }), brightBlue = (input) => applyStyle(input, { fg: "brightBlue" }), brightMagenta = (input) => applyStyle(input, { fg: "brightMagenta" }), brightCyan = (input) => applyStyle(input, { fg: "brightCyan" }), brightWhite = (input) => applyStyle(input, { fg: "brightWhite" }), bgBlack = (input) => applyStyle(input, { bg: "black" }), bgRed = (input) => applyStyle(input, { bg: "red" }), bgGreen = (input) => applyStyle(input, { bg: "green" }), bgYellow = (input) => applyStyle(input, { bg: "yellow" }), bgBlue = (input) => applyStyle(input, { bg: "blue" }), bgMagenta = (input) => applyStyle(input, { bg: "magenta" }), bgCyan = (input) => applyStyle(input, { bg: "cyan" }), bgWhite = (input) => applyStyle(input, { bg: "white" }), bold = (input) => applyStyle(input, { bold: true }), italic = (input) => applyStyle(input, { italic: true }), underline = (input) => applyStyle(input, { underline: true }), strikethrough = (input) => applyStyle(input, { strikethrough: true }), dim = (input) => applyStyle(input, { dim: true }), reverse = (input) => applyStyle(input, { reverse: true }), blink = (input) => applyStyle(input, { blink: true }), fg = (color) => (input) => applyStyle(input, { fg: color }), bg = (color) => (input) => applyStyle(input, { bg: color }), link = (url2) => (input) => {
71576
72742
  const chunk = typeof input === "object" && "__isChunk" in input ? input : {
71577
72743
  __isChunk: true,
71578
72744
  text: String(input)
@@ -73232,7 +74398,7 @@ var init_index_vy1rm1x3 = __esm(async () => {
73232
74398
  Wrap2[Wrap2["WrapReverse"] = 2] = "WrapReverse";
73233
74399
  return Wrap2;
73234
74400
  }({});
73235
- constants4 = {
74401
+ constants5 = {
73236
74402
  ALIGN_AUTO: Align.Auto,
73237
74403
  ALIGN_FLEX_START: Align.FlexStart,
73238
74404
  ALIGN_CENTER: Align.Center,
@@ -73306,7 +74472,7 @@ var init_index_vy1rm1x3 = __esm(async () => {
73306
74472
  WRAP_WRAP: Wrap.Wrap,
73307
74473
  WRAP_WRAP_REVERSE: Wrap.WrapReverse
73308
74474
  };
73309
- YGEnums_default = constants4;
74475
+ YGEnums_default = constants5;
73310
74476
  Yoga = wrapAssembly(await yoga_wasm_base64_esm_default());
73311
74477
  src_default = Yoga;
73312
74478
  VALID_BORDER_STYLES = ["single", "double", "rounded", "heavy"];
@@ -123823,6 +124989,7 @@ async function runUpCommand(context, args) {
123823
124989
  let target = null;
123824
124990
  let agentName;
123825
124991
  let harness;
124992
+ let model;
123826
124993
  for (let index2 = 0;index2 < args.length; index2 += 1) {
123827
124994
  const current = args[index2] ?? "";
123828
124995
  if (current === "--name") {
@@ -123851,6 +125018,19 @@ async function runUpCommand(context, args) {
123851
125018
  harness = current.slice("--harness=".length);
123852
125019
  continue;
123853
125020
  }
125021
+ if (current === "--model") {
125022
+ const value = args[index2 + 1];
125023
+ if (!value) {
125024
+ throw new ScoutCliError("missing value for --model");
125025
+ }
125026
+ model = value;
125027
+ index2 += 1;
125028
+ continue;
125029
+ }
125030
+ if (current.startsWith("--model=")) {
125031
+ model = current.slice("--model=".length);
125032
+ continue;
125033
+ }
123854
125034
  if (current.startsWith("--")) {
123855
125035
  throw new ScoutCliError(`unexpected argument for up: ${current}`);
123856
125036
  }
@@ -123860,7 +125040,7 @@ async function runUpCommand(context, args) {
123860
125040
  target = current;
123861
125041
  }
123862
125042
  if (!target) {
123863
- throw new ScoutCliError("usage: scout up <name|path> [--name <alias>] [--harness <claude|codex>]");
125043
+ throw new ScoutCliError("usage: scout up <name|path> [--name <alias>] [--harness <claude|codex>] [--model <model>]");
123864
125044
  }
123865
125045
  let projectPath;
123866
125046
  if (looksLikePath(target) || existsSync23(resolve17(target))) {
@@ -123876,6 +125056,7 @@ async function runUpCommand(context, args) {
123876
125056
  projectPath,
123877
125057
  agentName,
123878
125058
  harness: parseScoutHarness(harness),
125059
+ model,
123879
125060
  currentDirectory: defaultScoutContextDirectory(context)
123880
125061
  });
123881
125062
  context.output.writeValue(agent, renderScoutUpResult);
@@ -123986,7 +125167,7 @@ var init_whoami = __esm(async () => {
123986
125167
  import { existsSync as existsSync25, readFileSync as readFileSync15, writeFileSync as writeFileSync9, statSync as statSync5, mkdirSync as mkdirSync12 } from "fs";
123987
125168
  import { join as join32 } from "path";
123988
125169
  import { spawnSync as spawnSync3 } from "child_process";
123989
- import { homedir as homedir23 } from "os";
125170
+ import { homedir as homedir24 } from "os";
123990
125171
 
123991
125172
  // ../../apps/desktop/src/cli/argv.ts
123992
125173
  function parseScoutArgv(argv) {
@@ -124246,7 +125427,7 @@ var _scoutBinPath = null;
124246
125427
  function getScoutBinPath() {
124247
125428
  if (_scoutBinPath)
124248
125429
  return _scoutBinPath;
124249
- _scoutBinPath = join32(homedir23(), ".bun", "bin", "scout");
125430
+ _scoutBinPath = join32(homedir24(), ".bun", "bin", "scout");
124250
125431
  if (!existsSync25(_scoutBinPath)) {
124251
125432
  _scoutBinPath = spawnSync3("which", ["scout"], { encoding: "utf8" }).stdout.trim();
124252
125433
  }
@@ -124255,12 +125436,12 @@ function getScoutBinPath() {
124255
125436
  async function ensureBrokerUptodate() {
124256
125437
  try {
124257
125438
  const mtime = statSync5(getScoutBinPath()).mtimeMs;
124258
- const checkpointDir = join32(homedir23(), ".scout");
125439
+ const checkpointDir = join32(homedir24(), ".scout");
124259
125440
  const mtimePath = join32(checkpointDir, "cli-mtime");
124260
125441
  const lastMtime = existsSync25(mtimePath) ? Number(readFileSync15(mtimePath, "utf8").trim()) : 0;
124261
125442
  if (mtime > lastMtime) {
124262
125443
  const uid = process.getuid();
124263
- const plistPath = join32(homedir23(), "Library", "LaunchAgents", "dev.openscout.broker.plist");
125444
+ const plistPath = join32(homedir24(), "Library", "LaunchAgents", "dev.openscout.broker.plist");
124264
125445
  spawnSync3("launchctl", ["bootout", `gui/${uid}/dev.openscout.broker`], { stdio: "ignore" });
124265
125446
  await new Promise((resolve18) => setTimeout(resolve18, 1500));
124266
125447
  spawnSync3("launchctl", ["bootstrap", `gui/${uid}`, plistPath], { stdio: "ignore" });