@openscout/scout 0.2.58 → 0.2.61

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.
@@ -5501,116 +5501,6 @@ var init_mesh = () => {};
5501
5501
 
5502
5502
  // packages/protocol/dist/conversations.js
5503
5503
  var init_conversations = () => {};
5504
-
5505
- // packages/protocol/dist/collaboration.js
5506
- function isQuestionTerminalState(state) {
5507
- return state === "closed" || state === "declined";
5508
- }
5509
- function isWorkItemTerminalState(state) {
5510
- return state === "done" || state === "cancelled";
5511
- }
5512
- function collaborationRequiresNextMoveOwner(record) {
5513
- if (record.kind === "question") {
5514
- return !isQuestionTerminalState(record.state);
5515
- }
5516
- return !isWorkItemTerminalState(record.state);
5517
- }
5518
- function collaborationRequiresOwner(record) {
5519
- return record.kind === "work_item" && !isWorkItemTerminalState(record.state);
5520
- }
5521
- function collaborationRequiresWaitingOn(record) {
5522
- return record.kind === "work_item" && record.state === "waiting";
5523
- }
5524
- function collaborationRequiresAcceptance(record) {
5525
- if (record.acceptanceState === "none") {
5526
- return false;
5527
- }
5528
- if (record.kind === "question") {
5529
- return Boolean(record.askedById && record.askedOfId);
5530
- }
5531
- return Boolean(record.requestedById);
5532
- }
5533
- function validateCollaborationRecord(record) {
5534
- const errors = [];
5535
- if (!record.id.trim()) {
5536
- errors.push("collaboration record id is required");
5537
- }
5538
- if (!record.title.trim()) {
5539
- errors.push("collaboration title is required");
5540
- }
5541
- if (!record.createdById.trim()) {
5542
- errors.push("createdById is required");
5543
- }
5544
- if (record.parentId && record.parentId === record.id) {
5545
- errors.push("parentId cannot reference the record itself");
5546
- }
5547
- if (record.createdAt > record.updatedAt) {
5548
- errors.push("updatedAt must be greater than or equal to createdAt");
5549
- }
5550
- if (collaborationRequiresOwner(record) && !record.ownerId) {
5551
- errors.push("non-terminal work items require ownerId");
5552
- }
5553
- if (collaborationRequiresNextMoveOwner(record) && !record.nextMoveOwnerId) {
5554
- errors.push("non-terminal collaboration records require nextMoveOwnerId");
5555
- }
5556
- if (record.kind === "work_item" && collaborationRequiresWaitingOn(record) && !record.waitingOn) {
5557
- errors.push("waiting work items require waitingOn");
5558
- }
5559
- if (record.kind === "question") {
5560
- if (record.spawnedWorkItemId && record.spawnedWorkItemId === record.id) {
5561
- errors.push("question spawnedWorkItemId cannot reference the question itself");
5562
- }
5563
- } else if (record.waitingOn?.targetId && record.waitingOn.targetId === record.id) {
5564
- errors.push("waitingOn.targetId cannot reference the work item itself");
5565
- }
5566
- if (record.acceptanceState !== "none" && !collaborationRequiresAcceptance(record)) {
5567
- errors.push("acceptanceState requires the corresponding requester and reviewer identities");
5568
- }
5569
- return errors;
5570
- }
5571
- function assertValidCollaborationRecord(record) {
5572
- const errors = validateCollaborationRecord(record);
5573
- if (errors.length > 0) {
5574
- throw new Error(errors.join("; "));
5575
- }
5576
- }
5577
- function validateCollaborationEvent(event, record) {
5578
- const errors = [];
5579
- if (!event.id.trim()) {
5580
- errors.push("collaboration event id is required");
5581
- }
5582
- if (!event.recordId.trim()) {
5583
- errors.push("collaboration event recordId is required");
5584
- }
5585
- if (!event.actorId.trim()) {
5586
- errors.push("collaboration event actorId is required");
5587
- }
5588
- if (record) {
5589
- if (record.id !== event.recordId) {
5590
- errors.push("collaboration event recordId does not match the target record");
5591
- }
5592
- if (record.kind !== event.recordKind) {
5593
- errors.push("collaboration event recordKind does not match the target record");
5594
- }
5595
- }
5596
- if (event.kind === "answered" && event.recordKind !== "question") {
5597
- errors.push("answered events only apply to questions");
5598
- }
5599
- if (event.kind === "declined" && event.recordKind !== "question") {
5600
- errors.push("declined events only apply to questions");
5601
- }
5602
- if ((event.kind === "waiting" || event.kind === "progressed" || event.kind === "review_requested" || event.kind === "done" || event.kind === "cancelled") && event.recordKind !== "work_item") {
5603
- errors.push(`${event.kind} events only apply to work items`);
5604
- }
5605
- return errors;
5606
- }
5607
- function assertValidCollaborationEvent(event, record) {
5608
- const errors = validateCollaborationEvent(event, record);
5609
- if (errors.length > 0) {
5610
- throw new Error(errors.join("; "));
5611
- }
5612
- }
5613
-
5614
5504
  // packages/protocol/dist/messages.js
5615
5505
  var init_messages = () => {};
5616
5506
 
@@ -8902,8 +8792,107 @@ var init_claude_stream_json = __esm(() => {
8902
8792
 
8903
8793
  // packages/runtime/src/codex-app-server.ts
8904
8794
  import { spawn as spawn4 } from "child_process";
8905
- import { access as access3, appendFile as appendFile3, constants as constants4, mkdir as mkdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "fs/promises";
8795
+ import { constants as constants4 } from "fs";
8796
+ import { access as access3, appendFile as appendFile3, mkdir as mkdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "fs/promises";
8906
8797
  import { delimiter as delimiter3, join as join14 } from "path";
8798
+ function normalizeCodexModelValue(value) {
8799
+ const trimmed = value?.trim();
8800
+ return trimmed ? trimmed : null;
8801
+ }
8802
+ function encodeCodexModelConfig(model) {
8803
+ return `model=${JSON.stringify(model)}`;
8804
+ }
8805
+ function parseCodexModelConfig(value) {
8806
+ const trimmed = value?.trim();
8807
+ if (!trimmed) {
8808
+ return null;
8809
+ }
8810
+ const separatorIndex = trimmed.indexOf("=");
8811
+ if (separatorIndex <= 0) {
8812
+ return null;
8813
+ }
8814
+ const key = trimmed.slice(0, separatorIndex).trim();
8815
+ if (key !== "model") {
8816
+ return null;
8817
+ }
8818
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
8819
+ if (!rawValue) {
8820
+ return null;
8821
+ }
8822
+ if (rawValue.startsWith('"') && rawValue.endsWith('"') || rawValue.startsWith("'") && rawValue.endsWith("'")) {
8823
+ return rawValue.slice(1, -1) || null;
8824
+ }
8825
+ return rawValue;
8826
+ }
8827
+ function normalizeCodexAppServerLaunchArgs(launchArgs) {
8828
+ const args = Array.isArray(launchArgs) ? launchArgs.map((entry) => String(entry).trim()).filter(Boolean) : [];
8829
+ const normalized = [];
8830
+ for (let index = 0;index < args.length; index += 1) {
8831
+ const current = args[index] ?? "";
8832
+ if (current === "--model" || current === "-m") {
8833
+ const model = normalizeCodexModelValue(args[index + 1]);
8834
+ if (model) {
8835
+ normalized.push("-c", encodeCodexModelConfig(model));
8836
+ index += 1;
8837
+ continue;
8838
+ }
8839
+ normalized.push(current);
8840
+ continue;
8841
+ }
8842
+ if (current.startsWith("--model=")) {
8843
+ const model = normalizeCodexModelValue(current.slice("--model=".length));
8844
+ if (model) {
8845
+ normalized.push("-c", encodeCodexModelConfig(model));
8846
+ continue;
8847
+ }
8848
+ }
8849
+ if (current.startsWith("-m=")) {
8850
+ const model = normalizeCodexModelValue(current.slice(3));
8851
+ if (model) {
8852
+ normalized.push("-c", encodeCodexModelConfig(model));
8853
+ continue;
8854
+ }
8855
+ }
8856
+ if (current === "-c" || current === "--config") {
8857
+ const next = args[index + 1];
8858
+ if (typeof next === "string") {
8859
+ const model = parseCodexModelConfig(next);
8860
+ normalized.push(current === "--config" ? "--config" : "-c", model ? encodeCodexModelConfig(model) : next);
8861
+ index += 1;
8862
+ continue;
8863
+ }
8864
+ }
8865
+ if (current.startsWith("--config=")) {
8866
+ const value = current.slice("--config=".length);
8867
+ const model = parseCodexModelConfig(value);
8868
+ normalized.push(model ? `--config=${encodeCodexModelConfig(model)}` : current);
8869
+ continue;
8870
+ }
8871
+ normalized.push(current);
8872
+ }
8873
+ return normalized;
8874
+ }
8875
+ function readCodexAppServerModelFromLaunchArgs(launchArgs) {
8876
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
8877
+ for (let index = 0;index < normalized.length; index += 1) {
8878
+ const current = normalized[index] ?? "";
8879
+ if (current === "-c" || current === "--config") {
8880
+ const model = parseCodexModelConfig(normalized[index + 1]);
8881
+ if (model) {
8882
+ return model;
8883
+ }
8884
+ index += 1;
8885
+ continue;
8886
+ }
8887
+ if (current.startsWith("--config=")) {
8888
+ const model = parseCodexModelConfig(current.slice("--config=".length));
8889
+ if (model) {
8890
+ return model;
8891
+ }
8892
+ }
8893
+ }
8894
+ return null;
8895
+ }
8907
8896
  function isCodexThreadGlobalMessage(message) {
8908
8897
  const result = message.result;
8909
8898
  const thread = result?.thread;
@@ -9706,6 +9695,13 @@ function isMissingCodexRolloutError2(error) {
9706
9695
  const message = errorMessage3(error).toLowerCase();
9707
9696
  return message.includes("no rollout found for thread id");
9708
9697
  }
9698
+ function resolveCodexCompletionGraceMs() {
9699
+ const parsed = Number.parseInt(process.env.OPENSCOUT_CODEX_COMPLETION_GRACE_MS ?? "", 10);
9700
+ if (Number.isFinite(parsed) && parsed >= 0) {
9701
+ return parsed;
9702
+ }
9703
+ return 60000;
9704
+ }
9709
9705
 
9710
9706
  class CodexAppServerSession {
9711
9707
  options;
@@ -9895,7 +9891,7 @@ class CodexAppServerSession {
9895
9891
  systemPrompt: options.systemPrompt,
9896
9892
  threadId: options.threadId ?? null,
9897
9893
  requireExistingThread: options.requireExistingThread === true,
9898
- launchArgs: Array.isArray(options.launchArgs) ? options.launchArgs : []
9894
+ launchArgs: normalizeCodexAppServerLaunchArgs(options.launchArgs)
9899
9895
  });
9900
9896
  }
9901
9897
  async ensureStarted() {
@@ -9917,6 +9913,7 @@ class CodexAppServerSession {
9917
9913
  await mkdir5(this.options.logsDirectory, { recursive: true });
9918
9914
  await writeFile5(join14(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
9919
9915
  const codexExecutable = await resolveCodexExecutable2();
9916
+ const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
9920
9917
  const env = buildManagedAgentEnvironment({
9921
9918
  agentName: this.options.agentName,
9922
9919
  currentDirectory: this.options.cwd,
@@ -9927,7 +9924,8 @@ class CodexAppServerSession {
9927
9924
  ...buildScoutMcpCodexLaunchArgs({
9928
9925
  currentDirectory: this.options.cwd,
9929
9926
  env
9930
- })
9927
+ }),
9928
+ ...launchArgs
9931
9929
  ], {
9932
9930
  cwd: this.options.cwd,
9933
9931
  env,
@@ -10025,6 +10023,8 @@ class CodexAppServerSession {
10025
10023
  turnId: "",
10026
10024
  startedAt: Date.now(),
10027
10025
  timer: null,
10026
+ graceTimer: null,
10027
+ timedOutAt: null,
10028
10028
  messageOrder: [],
10029
10029
  messageByItemId: new Map,
10030
10030
  resolve: resolve5,
@@ -10032,20 +10032,43 @@ class CodexAppServerSession {
10032
10032
  watchers: []
10033
10033
  };
10034
10034
  turn.timer = setTimeout(() => {
10035
- this.interrupt().catch(() => {
10036
- return;
10037
- });
10038
- if (this.activeTurn === turn) {
10039
- this.activeTurn = null;
10040
- }
10041
- for (const watcher of this.drainTurnWatchers(turn)) {
10042
- watcher.reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
10043
- }
10044
- reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
10035
+ this.scheduleTurnTimeout(turn, timeoutMs);
10045
10036
  }, timeoutMs);
10046
10037
  this.activeTurn = turn;
10047
10038
  return turn;
10048
10039
  }
10040
+ buildTurnTimeoutError(timeoutMs) {
10041
+ return new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`);
10042
+ }
10043
+ scheduleTurnTimeout(turn, timeoutMs) {
10044
+ if (turn.timedOutAt !== null) {
10045
+ return;
10046
+ }
10047
+ turn.timedOutAt = Date.now();
10048
+ this.interrupt().catch(() => {
10049
+ return;
10050
+ });
10051
+ const graceMs = resolveCodexCompletionGraceMs();
10052
+ if (graceMs <= 0) {
10053
+ this.rejectTimedOutTurn(turn, timeoutMs);
10054
+ return;
10055
+ }
10056
+ turn.graceTimer = setTimeout(() => {
10057
+ this.rejectTimedOutTurn(turn, timeoutMs);
10058
+ }, graceMs);
10059
+ }
10060
+ rejectTimedOutTurn(turn, timeoutMs) {
10061
+ if (this.activeTurn !== turn) {
10062
+ this.clearActiveTurn(turn);
10063
+ return;
10064
+ }
10065
+ this.clearActiveTurn(turn);
10066
+ const error = this.buildTurnTimeoutError(timeoutMs);
10067
+ for (const watcher of this.drainTurnWatchers(turn)) {
10068
+ watcher.reject(error);
10069
+ }
10070
+ turn.reject(error);
10071
+ }
10049
10072
  addTurnWatcher(turn, resolve5, reject, timeoutMs) {
10050
10073
  const watcher = {
10051
10074
  resolve: resolve5,
@@ -10079,6 +10102,9 @@ class CodexAppServerSession {
10079
10102
  if (turn.timer) {
10080
10103
  clearTimeout(turn.timer);
10081
10104
  }
10105
+ if (turn.graceTimer) {
10106
+ clearTimeout(turn.graceTimer);
10107
+ }
10082
10108
  if (this.activeTurn === turn) {
10083
10109
  this.activeTurn = null;
10084
10110
  }
@@ -11454,6 +11480,128 @@ function normalizeLocalAgentCapabilities(value) {
11454
11480
  function normalizeLocalAgentLaunchArgs(value) {
11455
11481
  return Array.isArray(value) ? value.map((entry) => String(entry).trim()).filter(Boolean) : [];
11456
11482
  }
11483
+ function normalizeRequestedModel(value) {
11484
+ const trimmed = value?.trim();
11485
+ return trimmed ? trimmed : undefined;
11486
+ }
11487
+ function readClaudeLaunchModel(launchArgs) {
11488
+ for (let index = 0;index < launchArgs.length; index += 1) {
11489
+ const current = launchArgs[index] ?? "";
11490
+ if (current === "--model") {
11491
+ const next = launchArgs[index + 1]?.trim();
11492
+ return next || undefined;
11493
+ }
11494
+ if (current.startsWith("--model=")) {
11495
+ const next = current.slice("--model=".length).trim();
11496
+ return next || undefined;
11497
+ }
11498
+ }
11499
+ return;
11500
+ }
11501
+ function normalizeLaunchArgsForHarness(harness, value) {
11502
+ const normalized = normalizeLocalAgentLaunchArgs(value);
11503
+ if (harness === "codex") {
11504
+ return normalizeCodexAppServerLaunchArgs(normalized);
11505
+ }
11506
+ return normalized;
11507
+ }
11508
+ function readLaunchModelForHarness(harness, launchArgs) {
11509
+ if (harness === "codex") {
11510
+ return readCodexAppServerModelFromLaunchArgs(launchArgs) ?? undefined;
11511
+ }
11512
+ if (harness === "claude") {
11513
+ return readClaudeLaunchModel(launchArgs ?? []);
11514
+ }
11515
+ return;
11516
+ }
11517
+ function stripLaunchModelForHarness(harness, launchArgs) {
11518
+ if (harness === "codex") {
11519
+ const next = [];
11520
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
11521
+ for (let index = 0;index < normalized.length; index += 1) {
11522
+ const current = normalized[index] ?? "";
11523
+ if (current === "-c" || current === "--config") {
11524
+ const value = normalized[index + 1];
11525
+ if (readCodexAppServerModelFromLaunchArgs(value ? [current, value] : [current])) {
11526
+ index += 1;
11527
+ continue;
11528
+ }
11529
+ next.push(current);
11530
+ if (value) {
11531
+ next.push(value);
11532
+ index += 1;
11533
+ }
11534
+ continue;
11535
+ }
11536
+ if (current.startsWith("--config=")) {
11537
+ if (readCodexAppServerModelFromLaunchArgs([current])) {
11538
+ continue;
11539
+ }
11540
+ }
11541
+ next.push(current);
11542
+ }
11543
+ return next;
11544
+ }
11545
+ if (harness === "claude") {
11546
+ const next = [];
11547
+ const normalized = normalizeLocalAgentLaunchArgs(launchArgs);
11548
+ for (let index = 0;index < normalized.length; index += 1) {
11549
+ const current = normalized[index] ?? "";
11550
+ if (current === "--model") {
11551
+ index += 1;
11552
+ continue;
11553
+ }
11554
+ if (current.startsWith("--model=")) {
11555
+ continue;
11556
+ }
11557
+ next.push(current);
11558
+ }
11559
+ return next;
11560
+ }
11561
+ return normalizeLocalAgentLaunchArgs(launchArgs);
11562
+ }
11563
+ function buildLaunchArgsForRequestedModel(harness, model) {
11564
+ if (harness === "codex") {
11565
+ return normalizeCodexAppServerLaunchArgs(["--model", model]);
11566
+ }
11567
+ if (harness === "claude") {
11568
+ return ["--model", model];
11569
+ }
11570
+ return [];
11571
+ }
11572
+ function applyRequestedModelToLaunchArgs(harness, launchArgs, model) {
11573
+ const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
11574
+ const requestedModel = normalizeRequestedModel(model);
11575
+ if (!requestedModel) {
11576
+ return normalized;
11577
+ }
11578
+ return [
11579
+ ...stripLaunchModelForHarness(harness, normalized),
11580
+ ...buildLaunchArgsForRequestedModel(harness, requestedModel)
11581
+ ];
11582
+ }
11583
+ function defaultHarnessForOverride(override, fallback = "claude") {
11584
+ return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
11585
+ }
11586
+ function launchArgsForOverrideHarness(override, harness) {
11587
+ const profileLaunchArgs = override.harnessProfiles?.[harness]?.launchArgs;
11588
+ if (profileLaunchArgs) {
11589
+ return normalizeLaunchArgsForHarness(harness, profileLaunchArgs);
11590
+ }
11591
+ if (harness === defaultHarnessForOverride(override, harness)) {
11592
+ return normalizeLaunchArgsForHarness(harness, override.launchArgs);
11593
+ }
11594
+ return [];
11595
+ }
11596
+ function overrideHarnessProfile(override, harness) {
11597
+ const profile = override.harnessProfiles?.[harness];
11598
+ return {
11599
+ cwd: normalizeProjectPath(profile?.cwd || override.runtime?.cwd || override.projectRoot || process.cwd()),
11600
+ transport: normalizeLocalAgentTransport(profile?.transport ?? override.runtime?.transport, harness),
11601
+ sessionId: normalizeTmuxSessionName2(profile?.sessionId, `${override.agentId}-${harness}`),
11602
+ launchArgs: launchArgsForOverrideHarness(override, harness)
11603
+ };
11604
+ }
11457
11605
  function normalizeManagedHarness2(value, fallback) {
11458
11606
  return value === "codex" ? "codex" : value === "claude" ? "claude" : fallback;
11459
11607
  }
@@ -11469,7 +11617,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11469
11617
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
11470
11618
  transport: normalizeLocalAgentTransport(profile.transport, harness),
11471
11619
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
11472
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
11620
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
11473
11621
  };
11474
11622
  }
11475
11623
  const runtimeHarness = normalizeManagedHarness2(record.harness, defaultHarness);
@@ -11478,7 +11626,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11478
11626
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
11479
11627
  transport: normalizeLocalAgentTransport(record.transport, runtimeHarness),
11480
11628
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${runtimeHarness}`),
11481
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11629
+ launchArgs: normalizeLaunchArgsForHarness(runtimeHarness, record.launchArgs)
11482
11630
  };
11483
11631
  }
11484
11632
  if (!nextProfiles[defaultHarness]) {
@@ -11486,7 +11634,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11486
11634
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
11487
11635
  transport: normalizeLocalAgentTransport(record.transport, defaultHarness),
11488
11636
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${defaultHarness}`),
11489
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11637
+ launchArgs: normalizeLaunchArgsForHarness(defaultHarness, record.launchArgs)
11490
11638
  };
11491
11639
  }
11492
11640
  for (const harness of ["claude", "codex"]) {
@@ -11498,7 +11646,7 @@ function normalizeLocalHarnessProfiles(agentId, record) {
11498
11646
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
11499
11647
  transport: normalizeLocalAgentTransport(profile.transport, harness),
11500
11648
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
11501
- launchArgs: normalizeLocalAgentLaunchArgs(profile.launchArgs)
11649
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
11502
11650
  };
11503
11651
  }
11504
11652
  return nextProfiles;
@@ -11522,14 +11670,14 @@ function recordForHarness(record, harnessOverride) {
11522
11670
  tmuxSession: fallbackSessionId,
11523
11671
  cwd: fallbackCwd,
11524
11672
  transport: fallbackTransport,
11525
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs),
11673
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs),
11526
11674
  harnessProfiles: {
11527
11675
  ...normalized.harnessProfiles,
11528
11676
  [selectedHarness]: {
11529
11677
  cwd: fallbackCwd,
11530
11678
  transport: fallbackTransport,
11531
11679
  sessionId: fallbackSessionId,
11532
- launchArgs: normalizeLocalAgentLaunchArgs(normalized.launchArgs)
11680
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs)
11533
11681
  }
11534
11682
  }
11535
11683
  };
@@ -11571,7 +11719,7 @@ function normalizeLocalAgentRecord(agentId, record) {
11571
11719
  harnessProfiles,
11572
11720
  transport: activeProfile?.transport ?? normalizeLocalAgentTransport(record.transport, harness),
11573
11721
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
11574
- launchArgs: activeProfile?.launchArgs ?? normalizeLocalAgentLaunchArgs(record.launchArgs)
11722
+ launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs)
11575
11723
  };
11576
11724
  }
11577
11725
  function localAgentStatusFromRecord(agentId, record, source) {
@@ -11640,7 +11788,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
11640
11788
  source: existing?.source && existing.source !== "inferred" ? existing.source : "manual",
11641
11789
  startedAt: normalizedRecord.startedAt,
11642
11790
  systemPrompt: normalizedRecord.systemPrompt,
11643
- launchArgs: normalizeLocalAgentLaunchArgs(normalizedRecord.launchArgs),
11791
+ launchArgs: normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs),
11644
11792
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
11645
11793
  defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
11646
11794
  harnessProfiles: normalizedRecord.harnessProfiles,
@@ -11665,7 +11813,7 @@ function buildLocalAgentConfigState(agentId, record) {
11665
11813
  sessionId: record.tmuxSession,
11666
11814
  wakePolicy: "on_demand"
11667
11815
  },
11668
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs),
11816
+ launchArgs: normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(record.harness), record.launchArgs),
11669
11817
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
11670
11818
  applyMode: "restart",
11671
11819
  templateHint: LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT
@@ -11791,7 +11939,7 @@ function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
11791
11939
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "codex_app_server" }),
11792
11940
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
11793
11941
  logsDirectory: relayAgentLogsDirectory(agentName),
11794
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11942
+ launchArgs: normalizeLaunchArgsForHarness("codex", record.launchArgs)
11795
11943
  };
11796
11944
  }
11797
11945
  function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
@@ -11802,7 +11950,7 @@ function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
11802
11950
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "claude_stream_json" }),
11803
11951
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
11804
11952
  logsDirectory: relayAgentLogsDirectory(agentName),
11805
- launchArgs: normalizeLocalAgentLaunchArgs(record.launchArgs)
11953
+ launchArgs: normalizeLaunchArgsForHarness("claude", record.launchArgs)
11806
11954
  };
11807
11955
  }
11808
11956
  function endpointMetadataString(endpoint, key) {
@@ -12007,7 +12155,7 @@ function buildLocalAgentBootstrapPrompt(_harness, _systemPrompt, initialMessage)
12007
12155
  return initialMessage;
12008
12156
  }
12009
12157
  function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile, workerScript) {
12010
- const extraArgs = shellQuoteArguments(normalizeLocalAgentLaunchArgs(record.launchArgs));
12158
+ const extraArgs = shellQuoteArguments(normalizeLaunchArgsForHarness(normalizeLocalAgentHarness(record.harness), record.launchArgs));
12011
12159
  if (normalizeLocalAgentHarness(record.harness) === "codex") {
12012
12160
  return `exec bash ${JSON.stringify(workerScript ?? join16(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
12013
12161
  }
@@ -12379,6 +12527,7 @@ async function startLocalAgent(input) {
12379
12527
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
12380
12528
  const transport = normalizeLocalAgentTransport(undefined, effectiveHarness);
12381
12529
  const sessionId = normalizeTmuxSessionName2(undefined, `${instance.id}-${effectiveHarness}`);
12530
+ const launchArgs = applyRequestedModelToLaunchArgs(effectiveHarness, [], input.model);
12382
12531
  const existingForInstance = overrides[instance.id];
12383
12532
  if (existingForInstance && normalizeProjectPath(existingForInstance.projectRoot) !== projectRoot) {
12384
12533
  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.`);
@@ -12392,13 +12541,14 @@ async function startLocalAgent(input) {
12392
12541
  projectConfigPath: coldProjectConfigPath,
12393
12542
  source: "manual",
12394
12543
  startedAt: nowSeconds2(),
12544
+ launchArgs,
12395
12545
  defaultHarness: effectiveHarness,
12396
12546
  harnessProfiles: {
12397
12547
  [effectiveHarness]: {
12398
12548
  cwd: effectiveCwd ?? projectRoot,
12399
12549
  transport,
12400
12550
  sessionId,
12401
- launchArgs: []
12551
+ launchArgs
12402
12552
  }
12403
12553
  },
12404
12554
  runtime: {
@@ -12419,6 +12569,9 @@ async function startLocalAgent(input) {
12419
12569
  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.`);
12420
12570
  }
12421
12571
  const resolvedHarness = preferredHarness ?? matchingOverride.runtime?.harness;
12572
+ const nextHarness = normalizeManagedHarness2(resolvedHarness, "claude");
12573
+ const nextDefaultHarness = preferredHarness ? normalizeManagedHarness2(preferredHarness, "claude") : defaultHarnessForOverride(matchingOverride, "claude");
12574
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(nextHarness, launchArgsForOverrideHarness(matchingOverride, nextHarness), input.model);
12422
12575
  overrides[instance.id] = {
12423
12576
  agentId: instance.id,
12424
12577
  definitionId: requestedDefinitionId,
@@ -12429,10 +12582,16 @@ async function startLocalAgent(input) {
12429
12582
  source: "manual",
12430
12583
  startedAt: matchingOverride.startedAt ?? nowSeconds2(),
12431
12584
  systemPrompt: matchingOverride.systemPrompt,
12432
- launchArgs: matchingOverride.launchArgs,
12585
+ launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
12433
12586
  capabilities: matchingOverride.capabilities,
12434
- defaultHarness: normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness, "claude"),
12435
- harnessProfiles: matchingOverride.harnessProfiles,
12587
+ defaultHarness: nextDefaultHarness,
12588
+ harnessProfiles: {
12589
+ ...matchingOverride.harnessProfiles ?? {},
12590
+ [nextHarness]: {
12591
+ ...overrideHarnessProfile(matchingOverride, nextHarness),
12592
+ launchArgs: nextLaunchArgs
12593
+ }
12594
+ },
12436
12595
  runtime: {
12437
12596
  cwd: effectiveCwd ?? matchingOverride.runtime?.cwd ?? matchingProjectRoot,
12438
12597
  harness: resolvedHarness,
@@ -12444,6 +12603,22 @@ async function startLocalAgent(input) {
12444
12603
  await writeRelayAgentOverrides(overrides);
12445
12604
  targetAgentId = instance.id;
12446
12605
  } else {
12606
+ if (input.model) {
12607
+ const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
12608
+ const nextLaunchArgs = applyRequestedModelToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), input.model);
12609
+ overrides[matchingAgentId] = {
12610
+ ...matchingOverride,
12611
+ launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
12612
+ harnessProfiles: {
12613
+ ...matchingOverride.harnessProfiles ?? {},
12614
+ [existingHarness]: {
12615
+ ...overrideHarnessProfile(matchingOverride, existingHarness),
12616
+ launchArgs: nextLaunchArgs
12617
+ }
12618
+ }
12619
+ };
12620
+ await writeRelayAgentOverrides(overrides);
12621
+ }
12447
12622
  targetAgentId = matchingAgentId;
12448
12623
  }
12449
12624
  await ensureLocalAgentBindingOnline(targetAgentId, process.env.OPENSCOUT_NODE_ID ?? "local", {
@@ -12556,6 +12731,7 @@ function readPersistedClaudeSessionId(agentName) {
12556
12731
  }
12557
12732
  function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
12558
12733
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
12734
+ const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
12559
12735
  const definitionId = normalizedRecord.definitionId ?? agentId;
12560
12736
  const displayName = titleCaseLocalAgentName(definitionId);
12561
12737
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
@@ -12580,7 +12756,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
12580
12756
  defaultSelector: instance.defaultSelector,
12581
12757
  nodeQualifier: instance.nodeQualifier,
12582
12758
  workspaceQualifier: instance.workspaceQualifier,
12583
- branch: instance.branch
12759
+ branch: instance.branch,
12760
+ ...configuredModel ? { model: configuredModel } : {}
12584
12761
  }
12585
12762
  },
12586
12763
  agent: {
@@ -12607,7 +12784,8 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
12607
12784
  defaultSelector: instance.defaultSelector,
12608
12785
  nodeQualifier: instance.nodeQualifier,
12609
12786
  workspaceQualifier: instance.workspaceQualifier,
12610
- branch: instance.branch
12787
+ branch: instance.branch,
12788
+ ...configuredModel ? { model: configuredModel } : {}
12611
12789
  },
12612
12790
  agentClass: "general",
12613
12791
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
@@ -12640,6 +12818,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
12640
12818
  nodeQualifier: instance.nodeQualifier,
12641
12819
  workspaceQualifier: instance.workspaceQualifier,
12642
12820
  branch: instance.branch,
12821
+ ...configuredModel ? { model: configuredModel } : {},
12643
12822
  ...externalSessionId ? { externalSessionId } : {}
12644
12823
  }
12645
12824
  }
@@ -12841,11 +13020,11 @@ var init_local_agents = __esm(async () => {
12841
13020
 
12842
13021
  // packages/web/server/index.ts
12843
13022
  import { existsSync as existsSync15 } from "fs";
12844
- import { dirname as dirname13, join as join21, resolve as resolve10 } from "path";
12845
- import { fileURLToPath as fileURLToPath8 } from "url";
13023
+ import { dirname as dirname14, join as join20, resolve as resolve11 } from "path";
13024
+ import { fileURLToPath as fileURLToPath9 } from "url";
12846
13025
 
12847
13026
  // packages/web/server/create-openscout-web-server.ts
12848
- import { existsSync as existsSync14, rmSync as rmSync3 } from "fs";
13027
+ import { existsSync as existsSync13, rmSync as rmSync3 } from "fs";
12849
13028
  import { dirname as dirname12, resolve as resolve9 } from "path";
12850
13029
  import { fileURLToPath as fileURLToPath7 } from "url";
12851
13030
 
@@ -15745,6 +15924,12 @@ function transientBrokerWorkingStatusPredicate(alias) {
15745
15924
  AND ${alias}.body LIKE '% is working.'
15746
15925
  )`;
15747
15926
  }
15927
+ function staleFlightActivityPredicate(alias) {
15928
+ return `NOT (
15929
+ ${alias}.kind = 'flight_updated'
15930
+ AND COALESCE(${alias}.summary, '') LIKE 'Stale running flight reconciled:%'
15931
+ )`;
15932
+ }
15748
15933
  function workPhaseFromFlightState(state) {
15749
15934
  switch (state) {
15750
15935
  case "queued":
@@ -15908,10 +16093,7 @@ function queryActivity(limit = 60) {
15908
16093
  FROM activity_items ai
15909
16094
  LEFT JOIN actors ac ON ac.id = ai.actor_id
15910
16095
  WHERE ai.kind != 'ask_replied'
15911
- AND NOT (
15912
- ai.kind = 'flight_updated'
15913
- AND COALESCE(ai.summary, '') LIKE 'Stale running flight reconciled:%'
15914
- )
16096
+ AND ${staleFlightActivityPredicate("ai")}
15915
16097
  ORDER BY ai.ts DESC
15916
16098
  LIMIT ?`).all(limit);
15917
16099
  const items = rows.map((r) => ({
@@ -16637,6 +16819,7 @@ function queryFleetActivity(opts) {
16637
16819
  filters.push("ai.conversation_id = ?");
16638
16820
  params.push(opts.conversationId);
16639
16821
  }
16822
+ const scopedFilters = sqlJoinClauses(filters, "OR");
16640
16823
  const sql = `SELECT
16641
16824
  ai.id,
16642
16825
  ai.kind,
@@ -16655,7 +16838,10 @@ function queryFleetActivity(opts) {
16655
16838
  ai.session_id
16656
16839
  FROM activity_items ai
16657
16840
  LEFT JOIN actors ac ON ac.id = ai.actor_id
16658
- ${sqlWhereClause(filters, "OR")}
16841
+ ${sqlWhereClause([
16842
+ staleFlightActivityPredicate("ai"),
16843
+ scopedFilters ? `(${scopedFilters})` : null
16844
+ ])}
16659
16845
  ORDER BY ai.ts DESC
16660
16846
  LIMIT ?`;
16661
16847
  const rows = db().prepare(sql).all(...params, opts?.limit ?? 80);
@@ -16738,6 +16924,10 @@ function queryFleetAskRows(requesterIds, limit) {
16738
16924
  )
16739
16925
  LEFT JOIN collaboration_records cr ON cr.id = inv.collaboration_record_id
16740
16926
  WHERE inv.requester_id IN (${requesterClause})
16927
+ AND NOT (
16928
+ COALESCE(f.state, '') = 'failed'
16929
+ AND COALESCE(f.error, '') LIKE 'Stale running flight reconciled:%'
16930
+ )
16741
16931
  ORDER BY COALESCE(f.completed_at, f.started_at, inv.created_at) DESC
16742
16932
  LIMIT ?`).all(...requesterIds, limit);
16743
16933
  }
@@ -17828,2359 +18018,79 @@ function whoEntryState(endpoints, registrationKind) {
17828
18018
 
17829
18019
  // packages/web/server/core/observe/service.ts
17830
18020
  init_src();
18021
+ await init_local_agents();
17831
18022
  import { existsSync as existsSync12, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
17832
18023
  import { homedir as homedir12 } from "os";
17833
18024
  import { join as join18, resolve as resolve8 } from "path";
17834
-
17835
- // packages/runtime/src/registry.ts
17836
- function createRuntimeRegistrySnapshot(value = {}) {
17837
- return {
17838
- nodes: value.nodes ?? {},
17839
- actors: value.actors ?? {},
17840
- agents: value.agents ?? {},
17841
- endpoints: value.endpoints ?? {},
17842
- conversations: value.conversations ?? {},
17843
- bindings: value.bindings ?? {},
17844
- messages: value.messages ?? {},
17845
- flights: value.flights ?? {},
17846
- collaborationRecords: value.collaborationRecords ?? {}
18025
+ var HISTORY_SNAPSHOT_CACHE_LIMIT = 128;
18026
+ var historySnapshotCache = new Map;
18027
+ var OBSERVE_SUMMARY_TAIL_SIZE = 8;
18028
+ function activeEndpoint(snapshot, agentId) {
18029
+ const candidates = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === agentId);
18030
+ const rank = (state) => {
18031
+ switch (state) {
18032
+ case "active":
18033
+ return 0;
18034
+ case "idle":
18035
+ return 1;
18036
+ case "waiting":
18037
+ return 2;
18038
+ case "offline":
18039
+ return 5;
18040
+ default:
18041
+ return 4;
18042
+ }
17847
18043
  };
18044
+ return [...candidates].sort((left, right) => rank(left.state) - rank(right.state))[0] ?? null;
17848
18045
  }
17849
-
17850
- // packages/runtime/src/index.ts
17851
- init_harness_catalog();
17852
-
17853
- // packages/runtime/src/planner.ts
17854
- function createDeliveryId(messageId, targetId, reason, transport) {
17855
- return `del-${messageId}-${targetId}-${reason}-${transport}`;
17856
- }
17857
- function unique(values) {
17858
- return [...new Set(values)];
17859
- }
17860
- function resolveVisibilityAudience(message, conversation) {
17861
- if (message.audience?.visibleTo?.length) {
17862
- return unique(message.audience.visibleTo);
18046
+ function expandHome(value) {
18047
+ if (!value) {
18048
+ return null;
17863
18049
  }
17864
- return unique(conversation.participantIds.filter((participantId) => participantId !== message.actorId));
17865
- }
17866
- function resolveNotifyAudience(message) {
17867
- const fromMentions = (message.mentions ?? []).map((mention) => mention.actorId);
17868
- const explicit = message.audience?.notify ?? [];
17869
- return unique([...fromMentions, ...explicit].filter((actorId) => actorId !== message.actorId));
17870
- }
17871
- function resolveInvocationAudience(message) {
17872
- return unique((message.audience?.invoke ?? []).filter((actorId) => actorId !== message.actorId));
18050
+ if (value === "~") {
18051
+ return homedir12();
18052
+ }
18053
+ if (value.startsWith("~/")) {
18054
+ return join18(homedir12(), value.slice(2));
18055
+ }
18056
+ return value;
17873
18057
  }
17874
- function planTargetDelivery(message, route, reason, policy) {
17875
- return {
17876
- id: createDeliveryId(message.id, route.targetId, reason, route.transport),
17877
- messageId: message.id,
17878
- targetId: route.targetId,
17879
- targetNodeId: route.nodeId,
17880
- targetKind: route.targetKind,
17881
- transport: route.transport,
17882
- reason,
17883
- policy,
17884
- status: "pending",
17885
- bindingId: route.bindingId
17886
- };
18058
+ function encodeClaudeProjectsSlug2(absolutePath) {
18059
+ const normalized = resolve8(absolutePath);
18060
+ return `-${normalized.replace(/^\//u, "").replace(/\//gu, "-")}`;
17887
18061
  }
17888
- function planMessageDeliveries(input) {
17889
- const visibilityIds = new Set(resolveVisibilityAudience(input.message, input.conversation));
17890
- const notifyIds = new Set(resolveNotifyAudience(input.message));
17891
- const invokeIds = new Set(resolveInvocationAudience(input.message));
17892
- const deliveries2 = new Map;
17893
- for (const route of input.participantRoutes) {
17894
- if (visibilityIds.has(route.targetId)) {
17895
- const intent = planTargetDelivery(input.message, {
17896
- ...route,
17897
- transport: route.nodeId && input.localNodeId && route.nodeId !== input.localNodeId ? "peer_broker" : route.transport
17898
- }, input.conversation.kind === "direct" || input.conversation.kind === "group_direct" ? "direct_message" : "conversation_visibility", "durable");
17899
- deliveries2.set(intent.id, intent);
17900
- }
17901
- if (notifyIds.has(route.targetId)) {
17902
- const intent = planTargetDelivery(input.message, {
17903
- ...route,
17904
- transport: route.nodeId && input.localNodeId && route.nodeId !== input.localNodeId ? "peer_broker" : route.transport
17905
- }, "mention", "must_ack");
17906
- deliveries2.set(intent.id, intent);
17907
- }
17908
- if (invokeIds.has(route.targetId)) {
17909
- const intent = planTargetDelivery(input.message, {
17910
- ...route,
17911
- transport: route.nodeId && input.localNodeId && route.nodeId !== input.localNodeId ? "peer_broker" : route.transport
17912
- }, "invocation", "must_ack");
17913
- deliveries2.set(intent.id, intent);
17914
- }
17915
- if (input.message.speech?.text && route.speechEnabled) {
17916
- const speechIntent = planTargetDelivery(input.message, {
17917
- ...route,
17918
- transport: route.transport === "local_socket" ? "native_voice" : "tts",
17919
- targetKind: route.targetKind === "device" ? "voice_session" : route.targetKind
17920
- }, "speech", "best_effort");
17921
- deliveries2.set(speechIntent.id, speechIntent);
17922
- }
17923
- }
17924
- for (const route of input.bindingRoutes ?? []) {
17925
- const intent = planTargetDelivery(input.message, route, "bridge_outbound", "durable");
17926
- deliveries2.set(intent.id, intent);
17927
- }
17928
- return [...deliveries2.values()];
17929
- }
17930
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/entity.js
17931
- var entityKind = Symbol.for("drizzle:entityKind");
17932
- var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
17933
- function is(value, type) {
17934
- if (!value || typeof value !== "object") {
17935
- return false;
17936
- }
17937
- if (value instanceof type) {
17938
- return true;
18062
+ function resolveClaudeHistoryPath(cwd, sessionId) {
18063
+ const normalizedCwd = expandHome(cwd)?.trim();
18064
+ if (!normalizedCwd) {
18065
+ return null;
17939
18066
  }
17940
- if (!Object.prototype.hasOwnProperty.call(type, entityKind)) {
17941
- throw new Error(`Class "${type.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`);
18067
+ const projectDir = join18(homedir12(), ".claude", "projects", encodeClaudeProjectsSlug2(normalizedCwd));
18068
+ const normalizedSessionId = sessionId?.trim().replace(/\.jsonl$/u, "") || "";
18069
+ if (normalizedSessionId) {
18070
+ const exactPath = join18(projectDir, `${normalizedSessionId}.jsonl`);
18071
+ if (existsSync12(exactPath)) {
18072
+ return exactPath;
18073
+ }
17942
18074
  }
17943
- let cls = Object.getPrototypeOf(value).constructor;
17944
- if (cls) {
17945
- while (cls) {
17946
- if (entityKind in cls && cls[entityKind] === type[entityKind]) {
17947
- return true;
18075
+ return findMostRecentJsonl(projectDir);
18076
+ }
18077
+ function findMostRecentJsonl(dir) {
18078
+ if (!existsSync12(dir))
18079
+ return null;
18080
+ try {
18081
+ let best = null;
18082
+ for (const entry of readdirSync2(dir)) {
18083
+ if (!entry.endsWith(".jsonl"))
18084
+ continue;
18085
+ const full = join18(dir, entry);
18086
+ const st = statSync4(full);
18087
+ if (!best || st.mtimeMs > best.mtime) {
18088
+ best = { path: full, mtime: st.mtimeMs };
17948
18089
  }
17949
- cls = Object.getPrototypeOf(cls);
17950
18090
  }
17951
- }
17952
- return false;
17953
- }
17954
-
17955
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column.js
17956
- class Column {
17957
- constructor(table, config) {
17958
- this.table = table;
17959
- this.config = config;
17960
- this.name = config.name;
17961
- this.keyAsName = config.keyAsName;
17962
- this.notNull = config.notNull;
17963
- this.default = config.default;
17964
- this.defaultFn = config.defaultFn;
17965
- this.onUpdateFn = config.onUpdateFn;
17966
- this.hasDefault = config.hasDefault;
17967
- this.primary = config.primaryKey;
17968
- this.isUnique = config.isUnique;
17969
- this.uniqueName = config.uniqueName;
17970
- this.uniqueType = config.uniqueType;
17971
- this.dataType = config.dataType;
17972
- this.columnType = config.columnType;
17973
- this.generated = config.generated;
17974
- this.generatedIdentity = config.generatedIdentity;
17975
- }
17976
- static [entityKind] = "Column";
17977
- name;
17978
- keyAsName;
17979
- primary;
17980
- notNull;
17981
- default;
17982
- defaultFn;
17983
- onUpdateFn;
17984
- hasDefault;
17985
- isUnique;
17986
- uniqueName;
17987
- uniqueType;
17988
- dataType;
17989
- columnType;
17990
- enumValues = undefined;
17991
- generated = undefined;
17992
- generatedIdentity = undefined;
17993
- config;
17994
- mapFromDriverValue(value) {
17995
- return value;
17996
- }
17997
- mapToDriverValue(value) {
17998
- return value;
17999
- }
18000
- shouldDisableInsert() {
18001
- return this.config.generated !== undefined && this.config.generated.type !== "byDefault";
18002
- }
18003
- }
18004
-
18005
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column-builder.js
18006
- class ColumnBuilder {
18007
- static [entityKind] = "ColumnBuilder";
18008
- config;
18009
- constructor(name, dataType, columnType) {
18010
- this.config = {
18011
- name,
18012
- keyAsName: name === "",
18013
- notNull: false,
18014
- default: undefined,
18015
- hasDefault: false,
18016
- primaryKey: false,
18017
- isUnique: false,
18018
- uniqueName: undefined,
18019
- uniqueType: undefined,
18020
- dataType,
18021
- columnType,
18022
- generated: undefined
18023
- };
18024
- }
18025
- $type() {
18026
- return this;
18027
- }
18028
- notNull() {
18029
- this.config.notNull = true;
18030
- return this;
18031
- }
18032
- default(value) {
18033
- this.config.default = value;
18034
- this.config.hasDefault = true;
18035
- return this;
18036
- }
18037
- $defaultFn(fn) {
18038
- this.config.defaultFn = fn;
18039
- this.config.hasDefault = true;
18040
- return this;
18041
- }
18042
- $default = this.$defaultFn;
18043
- $onUpdateFn(fn) {
18044
- this.config.onUpdateFn = fn;
18045
- this.config.hasDefault = true;
18046
- return this;
18047
- }
18048
- $onUpdate = this.$onUpdateFn;
18049
- primaryKey() {
18050
- this.config.primaryKey = true;
18051
- this.config.notNull = true;
18052
- return this;
18053
- }
18054
- setName(name) {
18055
- if (this.config.name !== "")
18056
- return;
18057
- this.config.name = name;
18058
- }
18059
- }
18060
-
18061
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.utils.js
18062
- var TableName = Symbol.for("drizzle:Name");
18063
-
18064
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing-utils.js
18065
- function iife(fn, ...args) {
18066
- return fn(...args);
18067
- }
18068
-
18069
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/unique-constraint.js
18070
- function uniqueKeyName(table, columns) {
18071
- return `${table[TableName]}_${columns.join("_")}_unique`;
18072
- }
18073
-
18074
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/common.js
18075
- class PgColumn extends Column {
18076
- constructor(table, config) {
18077
- if (!config.uniqueName) {
18078
- config.uniqueName = uniqueKeyName(table, [config.name]);
18079
- }
18080
- super(table, config);
18081
- this.table = table;
18082
- }
18083
- static [entityKind] = "PgColumn";
18084
- }
18085
-
18086
- class ExtraConfigColumn extends PgColumn {
18087
- static [entityKind] = "ExtraConfigColumn";
18088
- getSQLType() {
18089
- return this.getSQLType();
18090
- }
18091
- indexConfig = {
18092
- order: this.config.order ?? "asc",
18093
- nulls: this.config.nulls ?? "last",
18094
- opClass: this.config.opClass
18095
- };
18096
- defaultConfig = {
18097
- order: "asc",
18098
- nulls: "last",
18099
- opClass: undefined
18100
- };
18101
- asc() {
18102
- this.indexConfig.order = "asc";
18103
- return this;
18104
- }
18105
- desc() {
18106
- this.indexConfig.order = "desc";
18107
- return this;
18108
- }
18109
- nullsFirst() {
18110
- this.indexConfig.nulls = "first";
18111
- return this;
18112
- }
18113
- nullsLast() {
18114
- this.indexConfig.nulls = "last";
18115
- return this;
18116
- }
18117
- op(opClass) {
18118
- this.indexConfig.opClass = opClass;
18119
- return this;
18120
- }
18121
- }
18122
-
18123
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/enum.js
18124
- class PgEnumObjectColumn extends PgColumn {
18125
- static [entityKind] = "PgEnumObjectColumn";
18126
- enum;
18127
- enumValues = this.config.enum.enumValues;
18128
- constructor(table, config) {
18129
- super(table, config);
18130
- this.enum = config.enum;
18131
- }
18132
- getSQLType() {
18133
- return this.enum.enumName;
18134
- }
18135
- }
18136
- var isPgEnumSym = Symbol.for("drizzle:isPgEnum");
18137
- function isPgEnum(obj) {
18138
- return !!obj && typeof obj === "function" && isPgEnumSym in obj && obj[isPgEnumSym] === true;
18139
- }
18140
- class PgEnumColumn extends PgColumn {
18141
- static [entityKind] = "PgEnumColumn";
18142
- enum = this.config.enum;
18143
- enumValues = this.config.enum.enumValues;
18144
- constructor(table, config) {
18145
- super(table, config);
18146
- this.enum = config.enum;
18147
- }
18148
- getSQLType() {
18149
- return this.enum.enumName;
18150
- }
18151
- }
18152
-
18153
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/subquery.js
18154
- class Subquery {
18155
- static [entityKind] = "Subquery";
18156
- constructor(sql, fields, alias, isWith = false, usedTables = []) {
18157
- this._ = {
18158
- brand: "Subquery",
18159
- sql,
18160
- selectedFields: fields,
18161
- alias,
18162
- isWith,
18163
- usedTables
18164
- };
18165
- }
18166
- }
18167
-
18168
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/version.js
18169
- var version = "0.45.2";
18170
-
18171
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing.js
18172
- var otel;
18173
- var rawTracer;
18174
- var tracer = {
18175
- startActiveSpan(name, fn) {
18176
- if (!otel) {
18177
- return fn();
18178
- }
18179
- if (!rawTracer) {
18180
- rawTracer = otel.trace.getTracer("drizzle-orm", version);
18181
- }
18182
- return iife((otel2, rawTracer2) => rawTracer2.startActiveSpan(name, (span) => {
18183
- try {
18184
- return fn(span);
18185
- } catch (e) {
18186
- span.setStatus({
18187
- code: otel2.SpanStatusCode.ERROR,
18188
- message: e instanceof Error ? e.message : "Unknown error"
18189
- });
18190
- throw e;
18191
- } finally {
18192
- span.end();
18193
- }
18194
- }), otel, rawTracer);
18195
- }
18196
- };
18197
-
18198
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/view-common.js
18199
- var ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
18200
-
18201
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.js
18202
- var Schema = Symbol.for("drizzle:Schema");
18203
- var Columns = Symbol.for("drizzle:Columns");
18204
- var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns");
18205
- var OriginalName = Symbol.for("drizzle:OriginalName");
18206
- var BaseName = Symbol.for("drizzle:BaseName");
18207
- var IsAlias = Symbol.for("drizzle:IsAlias");
18208
- var ExtraConfigBuilder = Symbol.for("drizzle:ExtraConfigBuilder");
18209
- var IsDrizzleTable = Symbol.for("drizzle:IsDrizzleTable");
18210
-
18211
- class Table {
18212
- static [entityKind] = "Table";
18213
- static Symbol = {
18214
- Name: TableName,
18215
- Schema,
18216
- OriginalName,
18217
- Columns,
18218
- ExtraConfigColumns,
18219
- BaseName,
18220
- IsAlias,
18221
- ExtraConfigBuilder
18222
- };
18223
- [TableName];
18224
- [OriginalName];
18225
- [Schema];
18226
- [Columns];
18227
- [ExtraConfigColumns];
18228
- [BaseName];
18229
- [IsAlias] = false;
18230
- [IsDrizzleTable] = true;
18231
- [ExtraConfigBuilder] = undefined;
18232
- constructor(name, schema, baseName) {
18233
- this[TableName] = this[OriginalName] = name;
18234
- this[Schema] = schema;
18235
- this[BaseName] = baseName;
18236
- }
18237
- }
18238
-
18239
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sql/sql.js
18240
- function isSQLWrapper(value) {
18241
- return value !== null && value !== undefined && typeof value.getSQL === "function";
18242
- }
18243
- function mergeQueries(queries) {
18244
- const result = { sql: "", params: [] };
18245
- for (const query of queries) {
18246
- result.sql += query.sql;
18247
- result.params.push(...query.params);
18248
- if (query.typings?.length) {
18249
- if (!result.typings) {
18250
- result.typings = [];
18251
- }
18252
- result.typings.push(...query.typings);
18253
- }
18254
- }
18255
- return result;
18256
- }
18257
-
18258
- class StringChunk {
18259
- static [entityKind] = "StringChunk";
18260
- value;
18261
- constructor(value) {
18262
- this.value = Array.isArray(value) ? value : [value];
18263
- }
18264
- getSQL() {
18265
- return new SQL([this]);
18266
- }
18267
- }
18268
-
18269
- class SQL {
18270
- constructor(queryChunks) {
18271
- this.queryChunks = queryChunks;
18272
- for (const chunk of queryChunks) {
18273
- if (is(chunk, Table)) {
18274
- const schemaName = chunk[Table.Symbol.Schema];
18275
- this.usedTables.push(schemaName === undefined ? chunk[Table.Symbol.Name] : schemaName + "." + chunk[Table.Symbol.Name]);
18276
- }
18277
- }
18278
- }
18279
- static [entityKind] = "SQL";
18280
- decoder = noopDecoder;
18281
- shouldInlineParams = false;
18282
- usedTables = [];
18283
- append(query) {
18284
- this.queryChunks.push(...query.queryChunks);
18285
- return this;
18286
- }
18287
- toQuery(config) {
18288
- return tracer.startActiveSpan("drizzle.buildSQL", (span) => {
18289
- const query = this.buildQueryFromSourceParams(this.queryChunks, config);
18290
- span?.setAttributes({
18291
- "drizzle.query.text": query.sql,
18292
- "drizzle.query.params": JSON.stringify(query.params)
18293
- });
18294
- return query;
18295
- });
18296
- }
18297
- buildQueryFromSourceParams(chunks, _config) {
18298
- const config = Object.assign({}, _config, {
18299
- inlineParams: _config.inlineParams || this.shouldInlineParams,
18300
- paramStartIndex: _config.paramStartIndex || { value: 0 }
18301
- });
18302
- const {
18303
- casing,
18304
- escapeName,
18305
- escapeParam,
18306
- prepareTyping,
18307
- inlineParams,
18308
- paramStartIndex
18309
- } = config;
18310
- return mergeQueries(chunks.map((chunk) => {
18311
- if (is(chunk, StringChunk)) {
18312
- return { sql: chunk.value.join(""), params: [] };
18313
- }
18314
- if (is(chunk, Name)) {
18315
- return { sql: escapeName(chunk.value), params: [] };
18316
- }
18317
- if (chunk === undefined) {
18318
- return { sql: "", params: [] };
18319
- }
18320
- if (Array.isArray(chunk)) {
18321
- const result = [new StringChunk("(")];
18322
- for (const [i, p] of chunk.entries()) {
18323
- result.push(p);
18324
- if (i < chunk.length - 1) {
18325
- result.push(new StringChunk(", "));
18326
- }
18327
- }
18328
- result.push(new StringChunk(")"));
18329
- return this.buildQueryFromSourceParams(result, config);
18330
- }
18331
- if (is(chunk, SQL)) {
18332
- return this.buildQueryFromSourceParams(chunk.queryChunks, {
18333
- ...config,
18334
- inlineParams: inlineParams || chunk.shouldInlineParams
18335
- });
18336
- }
18337
- if (is(chunk, Table)) {
18338
- const schemaName = chunk[Table.Symbol.Schema];
18339
- const tableName = chunk[Table.Symbol.Name];
18340
- return {
18341
- sql: schemaName === undefined || chunk[IsAlias] ? escapeName(tableName) : escapeName(schemaName) + "." + escapeName(tableName),
18342
- params: []
18343
- };
18344
- }
18345
- if (is(chunk, Column)) {
18346
- const columnName = casing.getColumnCasing(chunk);
18347
- if (_config.invokeSource === "indexes") {
18348
- return { sql: escapeName(columnName), params: [] };
18349
- }
18350
- const schemaName = chunk.table[Table.Symbol.Schema];
18351
- return {
18352
- sql: chunk.table[IsAlias] || schemaName === undefined ? escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName) : escapeName(schemaName) + "." + escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName),
18353
- params: []
18354
- };
18355
- }
18356
- if (is(chunk, View)) {
18357
- const schemaName = chunk[ViewBaseConfig].schema;
18358
- const viewName = chunk[ViewBaseConfig].name;
18359
- return {
18360
- sql: schemaName === undefined || chunk[ViewBaseConfig].isAlias ? escapeName(viewName) : escapeName(schemaName) + "." + escapeName(viewName),
18361
- params: []
18362
- };
18363
- }
18364
- if (is(chunk, Param)) {
18365
- if (is(chunk.value, Placeholder)) {
18366
- return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
18367
- }
18368
- const mappedValue = chunk.value === null ? null : chunk.encoder.mapToDriverValue(chunk.value);
18369
- if (is(mappedValue, SQL)) {
18370
- return this.buildQueryFromSourceParams([mappedValue], config);
18371
- }
18372
- if (inlineParams) {
18373
- return { sql: this.mapInlineParam(mappedValue, config), params: [] };
18374
- }
18375
- let typings = ["none"];
18376
- if (prepareTyping) {
18377
- typings = [prepareTyping(chunk.encoder)];
18378
- }
18379
- return { sql: escapeParam(paramStartIndex.value++, mappedValue), params: [mappedValue], typings };
18380
- }
18381
- if (is(chunk, Placeholder)) {
18382
- return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
18383
- }
18384
- if (is(chunk, SQL.Aliased) && chunk.fieldAlias !== undefined) {
18385
- return { sql: escapeName(chunk.fieldAlias), params: [] };
18386
- }
18387
- if (is(chunk, Subquery)) {
18388
- if (chunk._.isWith) {
18389
- return { sql: escapeName(chunk._.alias), params: [] };
18390
- }
18391
- return this.buildQueryFromSourceParams([
18392
- new StringChunk("("),
18393
- chunk._.sql,
18394
- new StringChunk(") "),
18395
- new Name(chunk._.alias)
18396
- ], config);
18397
- }
18398
- if (isPgEnum(chunk)) {
18399
- if (chunk.schema) {
18400
- return { sql: escapeName(chunk.schema) + "." + escapeName(chunk.enumName), params: [] };
18401
- }
18402
- return { sql: escapeName(chunk.enumName), params: [] };
18403
- }
18404
- if (isSQLWrapper(chunk)) {
18405
- if (chunk.shouldOmitSQLParens?.()) {
18406
- return this.buildQueryFromSourceParams([chunk.getSQL()], config);
18407
- }
18408
- return this.buildQueryFromSourceParams([
18409
- new StringChunk("("),
18410
- chunk.getSQL(),
18411
- new StringChunk(")")
18412
- ], config);
18413
- }
18414
- if (inlineParams) {
18415
- return { sql: this.mapInlineParam(chunk, config), params: [] };
18416
- }
18417
- return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
18418
- }));
18419
- }
18420
- mapInlineParam(chunk, { escapeString }) {
18421
- if (chunk === null) {
18422
- return "null";
18423
- }
18424
- if (typeof chunk === "number" || typeof chunk === "boolean") {
18425
- return chunk.toString();
18426
- }
18427
- if (typeof chunk === "string") {
18428
- return escapeString(chunk);
18429
- }
18430
- if (typeof chunk === "object") {
18431
- const mappedValueAsString = chunk.toString();
18432
- if (mappedValueAsString === "[object Object]") {
18433
- return escapeString(JSON.stringify(chunk));
18434
- }
18435
- return escapeString(mappedValueAsString);
18436
- }
18437
- throw new Error("Unexpected param value: " + chunk);
18438
- }
18439
- getSQL() {
18440
- return this;
18441
- }
18442
- as(alias) {
18443
- if (alias === undefined) {
18444
- return this;
18445
- }
18446
- return new SQL.Aliased(this, alias);
18447
- }
18448
- mapWith(decoder) {
18449
- this.decoder = typeof decoder === "function" ? { mapFromDriverValue: decoder } : decoder;
18450
- return this;
18451
- }
18452
- inlineParams() {
18453
- this.shouldInlineParams = true;
18454
- return this;
18455
- }
18456
- if(condition) {
18457
- return condition ? this : undefined;
18458
- }
18459
- }
18460
-
18461
- class Name {
18462
- constructor(value) {
18463
- this.value = value;
18464
- }
18465
- static [entityKind] = "Name";
18466
- brand;
18467
- getSQL() {
18468
- return new SQL([this]);
18469
- }
18470
- }
18471
- var noopDecoder = {
18472
- mapFromDriverValue: (value) => value
18473
- };
18474
- var noopEncoder = {
18475
- mapToDriverValue: (value) => value
18476
- };
18477
- var noopMapper = {
18478
- ...noopDecoder,
18479
- ...noopEncoder
18480
- };
18481
-
18482
- class Param {
18483
- constructor(value, encoder = noopEncoder) {
18484
- this.value = value;
18485
- this.encoder = encoder;
18486
- }
18487
- static [entityKind] = "Param";
18488
- brand;
18489
- getSQL() {
18490
- return new SQL([this]);
18491
- }
18492
- }
18493
- function sql(strings, ...params) {
18494
- const queryChunks = [];
18495
- if (params.length > 0 || strings.length > 0 && strings[0] !== "") {
18496
- queryChunks.push(new StringChunk(strings[0]));
18497
- }
18498
- for (const [paramIndex, param2] of params.entries()) {
18499
- queryChunks.push(param2, new StringChunk(strings[paramIndex + 1]));
18500
- }
18501
- return new SQL(queryChunks);
18502
- }
18503
- ((sql2) => {
18504
- function empty() {
18505
- return new SQL([]);
18506
- }
18507
- sql2.empty = empty;
18508
- function fromList(list) {
18509
- return new SQL(list);
18510
- }
18511
- sql2.fromList = fromList;
18512
- function raw2(str) {
18513
- return new SQL([new StringChunk(str)]);
18514
- }
18515
- sql2.raw = raw2;
18516
- function join18(chunks, separator) {
18517
- const result = [];
18518
- for (const [i, chunk] of chunks.entries()) {
18519
- if (i > 0 && separator !== undefined) {
18520
- result.push(separator);
18521
- }
18522
- result.push(chunk);
18523
- }
18524
- return new SQL(result);
18525
- }
18526
- sql2.join = join18;
18527
- function identifier(value) {
18528
- return new Name(value);
18529
- }
18530
- sql2.identifier = identifier;
18531
- function placeholder2(name2) {
18532
- return new Placeholder(name2);
18533
- }
18534
- sql2.placeholder = placeholder2;
18535
- function param2(value, encoder) {
18536
- return new Param(value, encoder);
18537
- }
18538
- sql2.param = param2;
18539
- })(sql || (sql = {}));
18540
- ((SQL2) => {
18541
-
18542
- class Aliased {
18543
- constructor(sql2, fieldAlias) {
18544
- this.sql = sql2;
18545
- this.fieldAlias = fieldAlias;
18546
- }
18547
- static [entityKind] = "SQL.Aliased";
18548
- isSelectionField = false;
18549
- getSQL() {
18550
- return this.sql;
18551
- }
18552
- clone() {
18553
- return new Aliased(this.sql, this.fieldAlias);
18554
- }
18555
- }
18556
- SQL2.Aliased = Aliased;
18557
- })(SQL || (SQL = {}));
18558
-
18559
- class Placeholder {
18560
- constructor(name2) {
18561
- this.name = name2;
18562
- }
18563
- static [entityKind] = "Placeholder";
18564
- getSQL() {
18565
- return new SQL([this]);
18566
- }
18567
- }
18568
- var IsDrizzleView = Symbol.for("drizzle:IsDrizzleView");
18569
-
18570
- class View {
18571
- static [entityKind] = "View";
18572
- [ViewBaseConfig];
18573
- [IsDrizzleView] = true;
18574
- constructor({ name: name2, schema, selectedFields, query }) {
18575
- this[ViewBaseConfig] = {
18576
- name: name2,
18577
- originalName: name2,
18578
- schema,
18579
- selectedFields,
18580
- query,
18581
- isExisting: !query,
18582
- isAlias: false
18583
- };
18584
- }
18585
- getSQL() {
18586
- return new SQL([this]);
18587
- }
18588
- }
18589
- Column.prototype.getSQL = function() {
18590
- return new SQL([this]);
18591
- };
18592
- Table.prototype.getSQL = function() {
18593
- return new SQL([this]);
18594
- };
18595
- Subquery.prototype.getSQL = function() {
18596
- return new SQL([this]);
18597
- };
18598
-
18599
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/utils.js
18600
- function getColumnNameAndConfig(a, b) {
18601
- return {
18602
- name: typeof a === "string" && a.length > 0 ? a : "",
18603
- config: typeof a === "object" ? a : b
18604
- };
18605
- }
18606
- var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
18607
-
18608
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
18609
- class ForeignKeyBuilder {
18610
- static [entityKind] = "SQLiteForeignKeyBuilder";
18611
- reference;
18612
- _onUpdate;
18613
- _onDelete;
18614
- constructor(config, actions) {
18615
- this.reference = () => {
18616
- const { name, columns, foreignColumns } = config();
18617
- return { name, columns, foreignTable: foreignColumns[0].table, foreignColumns };
18618
- };
18619
- if (actions) {
18620
- this._onUpdate = actions.onUpdate;
18621
- this._onDelete = actions.onDelete;
18622
- }
18623
- }
18624
- onUpdate(action) {
18625
- this._onUpdate = action;
18626
- return this;
18627
- }
18628
- onDelete(action) {
18629
- this._onDelete = action;
18630
- return this;
18631
- }
18632
- build(table) {
18633
- return new ForeignKey(table, this);
18634
- }
18635
- }
18636
-
18637
- class ForeignKey {
18638
- constructor(table, builder) {
18639
- this.table = table;
18640
- this.reference = builder.reference;
18641
- this.onUpdate = builder._onUpdate;
18642
- this.onDelete = builder._onDelete;
18643
- }
18644
- static [entityKind] = "SQLiteForeignKey";
18645
- reference;
18646
- onUpdate;
18647
- onDelete;
18648
- getName() {
18649
- const { name, columns, foreignColumns } = this.reference();
18650
- const columnNames = columns.map((column) => column.name);
18651
- const foreignColumnNames = foreignColumns.map((column) => column.name);
18652
- const chunks = [
18653
- this.table[TableName],
18654
- ...columnNames,
18655
- foreignColumns[0].table[TableName],
18656
- ...foreignColumnNames
18657
- ];
18658
- return name ?? `${chunks.join("_")}_fk`;
18659
- }
18660
- }
18661
-
18662
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
18663
- function uniqueKeyName2(table, columns) {
18664
- return `${table[TableName]}_${columns.join("_")}_unique`;
18665
- }
18666
-
18667
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/common.js
18668
- class SQLiteColumnBuilder extends ColumnBuilder {
18669
- static [entityKind] = "SQLiteColumnBuilder";
18670
- foreignKeyConfigs = [];
18671
- references(ref, actions = {}) {
18672
- this.foreignKeyConfigs.push({ ref, actions });
18673
- return this;
18674
- }
18675
- unique(name) {
18676
- this.config.isUnique = true;
18677
- this.config.uniqueName = name;
18678
- return this;
18679
- }
18680
- generatedAlwaysAs(as, config) {
18681
- this.config.generated = {
18682
- as,
18683
- type: "always",
18684
- mode: config?.mode ?? "virtual"
18685
- };
18686
- return this;
18687
- }
18688
- buildForeignKeys(column, table) {
18689
- return this.foreignKeyConfigs.map(({ ref, actions }) => {
18690
- return ((ref2, actions2) => {
18691
- const builder = new ForeignKeyBuilder(() => {
18692
- const foreignColumn = ref2();
18693
- return { columns: [column], foreignColumns: [foreignColumn] };
18694
- });
18695
- if (actions2.onUpdate) {
18696
- builder.onUpdate(actions2.onUpdate);
18697
- }
18698
- if (actions2.onDelete) {
18699
- builder.onDelete(actions2.onDelete);
18700
- }
18701
- return builder.build(table);
18702
- })(ref, actions);
18703
- });
18704
- }
18705
- }
18706
-
18707
- class SQLiteColumn extends Column {
18708
- constructor(table, config) {
18709
- if (!config.uniqueName) {
18710
- config.uniqueName = uniqueKeyName2(table, [config.name]);
18711
- }
18712
- super(table, config);
18713
- this.table = table;
18714
- }
18715
- static [entityKind] = "SQLiteColumn";
18716
- }
18717
-
18718
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/blob.js
18719
- class SQLiteBigIntBuilder extends SQLiteColumnBuilder {
18720
- static [entityKind] = "SQLiteBigIntBuilder";
18721
- constructor(name) {
18722
- super(name, "bigint", "SQLiteBigInt");
18723
- }
18724
- build(table) {
18725
- return new SQLiteBigInt(table, this.config);
18726
- }
18727
- }
18728
-
18729
- class SQLiteBigInt extends SQLiteColumn {
18730
- static [entityKind] = "SQLiteBigInt";
18731
- getSQLType() {
18732
- return "blob";
18733
- }
18734
- mapFromDriverValue(value) {
18735
- if (typeof Buffer !== "undefined" && Buffer.from) {
18736
- const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
18737
- return BigInt(buf.toString("utf8"));
18738
- }
18739
- return BigInt(textDecoder.decode(value));
18740
- }
18741
- mapToDriverValue(value) {
18742
- return Buffer.from(value.toString());
18743
- }
18744
- }
18745
-
18746
- class SQLiteBlobJsonBuilder extends SQLiteColumnBuilder {
18747
- static [entityKind] = "SQLiteBlobJsonBuilder";
18748
- constructor(name) {
18749
- super(name, "json", "SQLiteBlobJson");
18750
- }
18751
- build(table) {
18752
- return new SQLiteBlobJson(table, this.config);
18753
- }
18754
- }
18755
-
18756
- class SQLiteBlobJson extends SQLiteColumn {
18757
- static [entityKind] = "SQLiteBlobJson";
18758
- getSQLType() {
18759
- return "blob";
18760
- }
18761
- mapFromDriverValue(value) {
18762
- if (typeof Buffer !== "undefined" && Buffer.from) {
18763
- const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
18764
- return JSON.parse(buf.toString("utf8"));
18765
- }
18766
- return JSON.parse(textDecoder.decode(value));
18767
- }
18768
- mapToDriverValue(value) {
18769
- return Buffer.from(JSON.stringify(value));
18770
- }
18771
- }
18772
-
18773
- class SQLiteBlobBufferBuilder extends SQLiteColumnBuilder {
18774
- static [entityKind] = "SQLiteBlobBufferBuilder";
18775
- constructor(name) {
18776
- super(name, "buffer", "SQLiteBlobBuffer");
18777
- }
18778
- build(table) {
18779
- return new SQLiteBlobBuffer(table, this.config);
18780
- }
18781
- }
18782
-
18783
- class SQLiteBlobBuffer extends SQLiteColumn {
18784
- static [entityKind] = "SQLiteBlobBuffer";
18785
- mapFromDriverValue(value) {
18786
- if (Buffer.isBuffer(value)) {
18787
- return value;
18788
- }
18789
- return Buffer.from(value);
18790
- }
18791
- getSQLType() {
18792
- return "blob";
18793
- }
18794
- }
18795
- function blob(a, b) {
18796
- const { name, config } = getColumnNameAndConfig(a, b);
18797
- if (config?.mode === "json") {
18798
- return new SQLiteBlobJsonBuilder(name);
18799
- }
18800
- if (config?.mode === "bigint") {
18801
- return new SQLiteBigIntBuilder(name);
18802
- }
18803
- return new SQLiteBlobBufferBuilder(name);
18804
- }
18805
-
18806
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/custom.js
18807
- class SQLiteCustomColumnBuilder extends SQLiteColumnBuilder {
18808
- static [entityKind] = "SQLiteCustomColumnBuilder";
18809
- constructor(name, fieldConfig, customTypeParams) {
18810
- super(name, "custom", "SQLiteCustomColumn");
18811
- this.config.fieldConfig = fieldConfig;
18812
- this.config.customTypeParams = customTypeParams;
18813
- }
18814
- build(table) {
18815
- return new SQLiteCustomColumn(table, this.config);
18816
- }
18817
- }
18818
-
18819
- class SQLiteCustomColumn extends SQLiteColumn {
18820
- static [entityKind] = "SQLiteCustomColumn";
18821
- sqlName;
18822
- mapTo;
18823
- mapFrom;
18824
- constructor(table, config) {
18825
- super(table, config);
18826
- this.sqlName = config.customTypeParams.dataType(config.fieldConfig);
18827
- this.mapTo = config.customTypeParams.toDriver;
18828
- this.mapFrom = config.customTypeParams.fromDriver;
18829
- }
18830
- getSQLType() {
18831
- return this.sqlName;
18832
- }
18833
- mapFromDriverValue(value) {
18834
- return typeof this.mapFrom === "function" ? this.mapFrom(value) : value;
18835
- }
18836
- mapToDriverValue(value) {
18837
- return typeof this.mapTo === "function" ? this.mapTo(value) : value;
18838
- }
18839
- }
18840
- function customType(customTypeParams) {
18841
- return (a, b) => {
18842
- const { name, config } = getColumnNameAndConfig(a, b);
18843
- return new SQLiteCustomColumnBuilder(name, config, customTypeParams);
18844
- };
18845
- }
18846
-
18847
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/integer.js
18848
- class SQLiteBaseIntegerBuilder extends SQLiteColumnBuilder {
18849
- static [entityKind] = "SQLiteBaseIntegerBuilder";
18850
- constructor(name, dataType, columnType) {
18851
- super(name, dataType, columnType);
18852
- this.config.autoIncrement = false;
18853
- }
18854
- primaryKey(config) {
18855
- if (config?.autoIncrement) {
18856
- this.config.autoIncrement = true;
18857
- }
18858
- this.config.hasDefault = true;
18859
- return super.primaryKey();
18860
- }
18861
- }
18862
-
18863
- class SQLiteBaseInteger extends SQLiteColumn {
18864
- static [entityKind] = "SQLiteBaseInteger";
18865
- autoIncrement = this.config.autoIncrement;
18866
- getSQLType() {
18867
- return "integer";
18868
- }
18869
- }
18870
-
18871
- class SQLiteIntegerBuilder extends SQLiteBaseIntegerBuilder {
18872
- static [entityKind] = "SQLiteIntegerBuilder";
18873
- constructor(name) {
18874
- super(name, "number", "SQLiteInteger");
18875
- }
18876
- build(table) {
18877
- return new SQLiteInteger(table, this.config);
18878
- }
18879
- }
18880
-
18881
- class SQLiteInteger extends SQLiteBaseInteger {
18882
- static [entityKind] = "SQLiteInteger";
18883
- }
18884
-
18885
- class SQLiteTimestampBuilder extends SQLiteBaseIntegerBuilder {
18886
- static [entityKind] = "SQLiteTimestampBuilder";
18887
- constructor(name, mode) {
18888
- super(name, "date", "SQLiteTimestamp");
18889
- this.config.mode = mode;
18890
- }
18891
- defaultNow() {
18892
- return this.default(sql`(cast((julianday('now') - 2440587.5)*86400000 as integer))`);
18893
- }
18894
- build(table) {
18895
- return new SQLiteTimestamp(table, this.config);
18896
- }
18897
- }
18898
-
18899
- class SQLiteTimestamp extends SQLiteBaseInteger {
18900
- static [entityKind] = "SQLiteTimestamp";
18901
- mode = this.config.mode;
18902
- mapFromDriverValue(value) {
18903
- if (this.config.mode === "timestamp") {
18904
- return new Date(value * 1000);
18905
- }
18906
- return new Date(value);
18907
- }
18908
- mapToDriverValue(value) {
18909
- const unix = value.getTime();
18910
- if (this.config.mode === "timestamp") {
18911
- return Math.floor(unix / 1000);
18912
- }
18913
- return unix;
18914
- }
18915
- }
18916
-
18917
- class SQLiteBooleanBuilder extends SQLiteBaseIntegerBuilder {
18918
- static [entityKind] = "SQLiteBooleanBuilder";
18919
- constructor(name, mode) {
18920
- super(name, "boolean", "SQLiteBoolean");
18921
- this.config.mode = mode;
18922
- }
18923
- build(table) {
18924
- return new SQLiteBoolean(table, this.config);
18925
- }
18926
- }
18927
-
18928
- class SQLiteBoolean extends SQLiteBaseInteger {
18929
- static [entityKind] = "SQLiteBoolean";
18930
- mode = this.config.mode;
18931
- mapFromDriverValue(value) {
18932
- return Number(value) === 1;
18933
- }
18934
- mapToDriverValue(value) {
18935
- return value ? 1 : 0;
18936
- }
18937
- }
18938
- function integer(a, b) {
18939
- const { name, config } = getColumnNameAndConfig(a, b);
18940
- if (config?.mode === "timestamp" || config?.mode === "timestamp_ms") {
18941
- return new SQLiteTimestampBuilder(name, config.mode);
18942
- }
18943
- if (config?.mode === "boolean") {
18944
- return new SQLiteBooleanBuilder(name, config.mode);
18945
- }
18946
- return new SQLiteIntegerBuilder(name);
18947
- }
18948
-
18949
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
18950
- class SQLiteNumericBuilder extends SQLiteColumnBuilder {
18951
- static [entityKind] = "SQLiteNumericBuilder";
18952
- constructor(name) {
18953
- super(name, "string", "SQLiteNumeric");
18954
- }
18955
- build(table) {
18956
- return new SQLiteNumeric(table, this.config);
18957
- }
18958
- }
18959
-
18960
- class SQLiteNumeric extends SQLiteColumn {
18961
- static [entityKind] = "SQLiteNumeric";
18962
- mapFromDriverValue(value) {
18963
- if (typeof value === "string")
18964
- return value;
18965
- return String(value);
18966
- }
18967
- getSQLType() {
18968
- return "numeric";
18969
- }
18970
- }
18971
-
18972
- class SQLiteNumericNumberBuilder extends SQLiteColumnBuilder {
18973
- static [entityKind] = "SQLiteNumericNumberBuilder";
18974
- constructor(name) {
18975
- super(name, "number", "SQLiteNumericNumber");
18976
- }
18977
- build(table) {
18978
- return new SQLiteNumericNumber(table, this.config);
18979
- }
18980
- }
18981
-
18982
- class SQLiteNumericNumber extends SQLiteColumn {
18983
- static [entityKind] = "SQLiteNumericNumber";
18984
- mapFromDriverValue(value) {
18985
- if (typeof value === "number")
18986
- return value;
18987
- return Number(value);
18988
- }
18989
- mapToDriverValue = String;
18990
- getSQLType() {
18991
- return "numeric";
18992
- }
18993
- }
18994
-
18995
- class SQLiteNumericBigIntBuilder extends SQLiteColumnBuilder {
18996
- static [entityKind] = "SQLiteNumericBigIntBuilder";
18997
- constructor(name) {
18998
- super(name, "bigint", "SQLiteNumericBigInt");
18999
- }
19000
- build(table) {
19001
- return new SQLiteNumericBigInt(table, this.config);
19002
- }
19003
- }
19004
-
19005
- class SQLiteNumericBigInt extends SQLiteColumn {
19006
- static [entityKind] = "SQLiteNumericBigInt";
19007
- mapFromDriverValue = BigInt;
19008
- mapToDriverValue = String;
19009
- getSQLType() {
19010
- return "numeric";
19011
- }
19012
- }
19013
- function numeric(a, b) {
19014
- const { name, config } = getColumnNameAndConfig(a, b);
19015
- const mode = config?.mode;
19016
- return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
19017
- }
19018
-
19019
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/real.js
19020
- class SQLiteRealBuilder extends SQLiteColumnBuilder {
19021
- static [entityKind] = "SQLiteRealBuilder";
19022
- constructor(name) {
19023
- super(name, "number", "SQLiteReal");
19024
- }
19025
- build(table) {
19026
- return new SQLiteReal(table, this.config);
19027
- }
19028
- }
19029
-
19030
- class SQLiteReal extends SQLiteColumn {
19031
- static [entityKind] = "SQLiteReal";
19032
- getSQLType() {
19033
- return "real";
19034
- }
19035
- }
19036
- function real(name) {
19037
- return new SQLiteRealBuilder(name ?? "");
19038
- }
19039
-
19040
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/text.js
19041
- class SQLiteTextBuilder extends SQLiteColumnBuilder {
19042
- static [entityKind] = "SQLiteTextBuilder";
19043
- constructor(name, config) {
19044
- super(name, "string", "SQLiteText");
19045
- this.config.enumValues = config.enum;
19046
- this.config.length = config.length;
19047
- }
19048
- build(table) {
19049
- return new SQLiteText(table, this.config);
19050
- }
19051
- }
19052
-
19053
- class SQLiteText extends SQLiteColumn {
19054
- static [entityKind] = "SQLiteText";
19055
- enumValues = this.config.enumValues;
19056
- length = this.config.length;
19057
- constructor(table, config) {
19058
- super(table, config);
19059
- }
19060
- getSQLType() {
19061
- return `text${this.config.length ? `(${this.config.length})` : ""}`;
19062
- }
19063
- }
19064
-
19065
- class SQLiteTextJsonBuilder extends SQLiteColumnBuilder {
19066
- static [entityKind] = "SQLiteTextJsonBuilder";
19067
- constructor(name) {
19068
- super(name, "json", "SQLiteTextJson");
19069
- }
19070
- build(table) {
19071
- return new SQLiteTextJson(table, this.config);
19072
- }
19073
- }
19074
-
19075
- class SQLiteTextJson extends SQLiteColumn {
19076
- static [entityKind] = "SQLiteTextJson";
19077
- getSQLType() {
19078
- return "text";
19079
- }
19080
- mapFromDriverValue(value) {
19081
- return JSON.parse(value);
19082
- }
19083
- mapToDriverValue(value) {
19084
- return JSON.stringify(value);
19085
- }
19086
- }
19087
- function text(a, b = {}) {
19088
- const { name, config } = getColumnNameAndConfig(a, b);
19089
- if (config.mode === "json") {
19090
- return new SQLiteTextJsonBuilder(name);
19091
- }
19092
- return new SQLiteTextBuilder(name, config);
19093
- }
19094
-
19095
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/all.js
19096
- function getSQLiteColumnBuilders() {
19097
- return {
19098
- blob,
19099
- customType,
19100
- integer,
19101
- numeric,
19102
- real,
19103
- text
19104
- };
19105
- }
19106
-
19107
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/table.js
19108
- var InlineForeignKeys = Symbol.for("drizzle:SQLiteInlineForeignKeys");
19109
-
19110
- class SQLiteTable extends Table {
19111
- static [entityKind] = "SQLiteTable";
19112
- static Symbol = Object.assign({}, Table.Symbol, {
19113
- InlineForeignKeys
19114
- });
19115
- [Table.Symbol.Columns];
19116
- [InlineForeignKeys] = [];
19117
- [Table.Symbol.ExtraConfigBuilder] = undefined;
19118
- }
19119
- function sqliteTableBase(name, columns, extraConfig, schema, baseName = name) {
19120
- const rawTable = new SQLiteTable(name, schema, baseName);
19121
- const parsedColumns = typeof columns === "function" ? columns(getSQLiteColumnBuilders()) : columns;
19122
- const builtColumns = Object.fromEntries(Object.entries(parsedColumns).map(([name2, colBuilderBase]) => {
19123
- const colBuilder = colBuilderBase;
19124
- colBuilder.setName(name2);
19125
- const column = colBuilder.build(rawTable);
19126
- rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable));
19127
- return [name2, column];
19128
- }));
19129
- const table = Object.assign(rawTable, builtColumns);
19130
- table[Table.Symbol.Columns] = builtColumns;
19131
- table[Table.Symbol.ExtraConfigColumns] = builtColumns;
19132
- if (extraConfig) {
19133
- table[SQLiteTable.Symbol.ExtraConfigBuilder] = extraConfig;
19134
- }
19135
- return table;
19136
- }
19137
- var sqliteTable = (name, columns, extraConfig) => {
19138
- return sqliteTableBase(name, columns, extraConfig);
19139
- };
19140
-
19141
- // node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/indexes.js
19142
- class IndexBuilderOn {
19143
- constructor(name, unique2) {
19144
- this.name = name;
19145
- this.unique = unique2;
19146
- }
19147
- static [entityKind] = "SQLiteIndexBuilderOn";
19148
- on(...columns) {
19149
- return new IndexBuilder(this.name, columns, this.unique);
19150
- }
19151
- }
19152
-
19153
- class IndexBuilder {
19154
- static [entityKind] = "SQLiteIndexBuilder";
19155
- config;
19156
- constructor(name, columns, unique2) {
19157
- this.config = {
19158
- name,
19159
- columns,
19160
- unique: unique2,
19161
- where: undefined
19162
- };
19163
- }
19164
- where(condition) {
19165
- this.config.where = condition;
19166
- return this;
19167
- }
19168
- build(table) {
19169
- return new Index(this.config, table);
19170
- }
19171
- }
19172
-
19173
- class Index {
19174
- static [entityKind] = "SQLiteIndex";
19175
- config;
19176
- constructor(config, table) {
19177
- this.config = { ...config, table };
19178
- }
19179
- }
19180
- function index(name) {
19181
- return new IndexBuilderOn(name, false);
19182
- }
19183
-
19184
- // packages/runtime/src/drizzle-schema.ts
19185
- var deliveriesTable = sqliteTable("deliveries", {
19186
- id: text("id").primaryKey(),
19187
- messageId: text("message_id"),
19188
- invocationId: text("invocation_id"),
19189
- targetId: text("target_id").notNull(),
19190
- targetNodeId: text("target_node_id"),
19191
- targetKind: text("target_kind").$type().notNull(),
19192
- transport: text("transport").$type().notNull(),
19193
- reason: text("reason").$type().notNull(),
19194
- policy: text("policy").$type().notNull(),
19195
- status: text("status").$type().notNull(),
19196
- bindingId: text("binding_id"),
19197
- leaseOwner: text("lease_owner"),
19198
- leaseExpiresAt: integer("lease_expires_at"),
19199
- metadataJson: text("metadata_json"),
19200
- createdAt: integer("created_at").notNull().default(sql`(unixepoch())`)
19201
- }, (table) => [
19202
- index("idx_deliveries_status_transport").on(table.status, table.transport)
19203
- ]);
19204
- var deliveryAttemptsTable = sqliteTable("delivery_attempts", {
19205
- id: text("id").primaryKey(),
19206
- deliveryId: text("delivery_id").notNull(),
19207
- attempt: integer("attempt").notNull(),
19208
- status: text("status").$type().notNull(),
19209
- error: text("error"),
19210
- externalRef: text("external_ref"),
19211
- metadataJson: text("metadata_json"),
19212
- createdAt: integer("created_at").notNull()
19213
- });
19214
- // packages/runtime/src/broker.ts
19215
- init_dist2();
19216
- function createRuntimeId(prefix) {
19217
- return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
19218
- }
19219
- function toTargetKind(actor) {
19220
- if (!actor)
19221
- return "participant";
19222
- if (actor.kind === "agent")
19223
- return "agent";
19224
- if (actor.kind === "device")
19225
- return "device";
19226
- if (actor.kind === "bridge")
19227
- return "bridge";
19228
- return "participant";
19229
- }
19230
- function defaultTransportForActor(actor) {
19231
- if (!actor)
19232
- return "local_socket";
19233
- switch (actor.kind) {
19234
- case "bridge":
19235
- return "webhook";
19236
- case "device":
19237
- return "local_socket";
19238
- case "agent":
19239
- return "local_socket";
19240
- default:
19241
- return "local_socket";
19242
- }
19243
- }
19244
- function endpointTransportRank(transport) {
19245
- switch (transport) {
19246
- case "codex_app_server":
19247
- return 0;
19248
- case "claude_stream_json":
19249
- return 1;
19250
- case "tmux":
19251
- return 2;
19252
- case "local_socket":
19253
- return 3;
19254
- default:
19255
- return 4;
19256
- }
19257
- }
19258
- function shouldBridgeMessageToBinding(binding, message) {
19259
- if (binding.platform !== "telegram") {
19260
- return true;
19261
- }
19262
- const source = typeof message.metadata?.source === "string" ? String(message.metadata.source) : "";
19263
- if (source === "telegram") {
19264
- return false;
19265
- }
19266
- const outboundMode = typeof binding.metadata?.outboundMode === "string" ? String(binding.metadata.outboundMode) : "operator_only";
19267
- const operatorId = typeof binding.metadata?.operatorId === "string" ? String(binding.metadata.operatorId) : "operator";
19268
- const allowedActorIds = Array.isArray(binding.metadata?.allowedActorIds) ? binding.metadata.allowedActorIds.map((entry) => String(entry).trim()).filter(Boolean) : [];
19269
- if (outboundMode === "all") {
19270
- return true;
19271
- }
19272
- if (outboundMode === "allowlist") {
19273
- return allowedActorIds.includes(message.actorId);
19274
- }
19275
- return message.actorId === operatorId;
19276
- }
19277
- function resolveBindingRoutes(bindings, message) {
19278
- return bindings.filter((binding) => shouldBridgeMessageToBinding(binding, message)).map((binding) => ({
19279
- targetId: binding.id,
19280
- targetKind: "bridge",
19281
- transport: binding.platform === "telegram" ? "telegram" : binding.platform === "discord" ? "discord" : "webhook",
19282
- bindingId: binding.id
19283
- }));
19284
- }
19285
- function preferredEndpoint(endpoints) {
19286
- let preferred;
19287
- let preferredRank = Number.POSITIVE_INFINITY;
19288
- for (const endpoint of endpoints) {
19289
- const rank = endpointTransportRank(endpoint.transport);
19290
- if (!preferred || rank < preferredRank) {
19291
- preferred = endpoint;
19292
- preferredRank = rank;
19293
- }
19294
- }
19295
- return preferred;
19296
- }
19297
-
19298
- class InMemoryControlRuntime {
19299
- registry;
19300
- listeners = new Set;
19301
- eventBuffer = [];
19302
- localNodeId;
19303
- endpointIdsByAgentId = new Map;
19304
- bindingIdsByConversationId = new Map;
19305
- flightIdByInvocationId = new Map;
19306
- constructor(initial = {}, options = {}) {
19307
- this.registry = createRuntimeRegistrySnapshot(initial);
19308
- this.localNodeId = options.localNodeId;
19309
- this.rebuildIndexes();
19310
- }
19311
- snapshot() {
19312
- return createRuntimeRegistrySnapshot({
19313
- nodes: { ...this.registry.nodes },
19314
- actors: { ...this.registry.actors },
19315
- agents: { ...this.registry.agents },
19316
- endpoints: { ...this.registry.endpoints },
19317
- conversations: { ...this.registry.conversations },
19318
- bindings: { ...this.registry.bindings },
19319
- messages: { ...this.registry.messages },
19320
- flights: { ...this.registry.flights },
19321
- collaborationRecords: { ...this.registry.collaborationRecords }
19322
- });
19323
- }
19324
- peek() {
19325
- return this.registry;
19326
- }
19327
- node(nodeId) {
19328
- return this.registry.nodes[nodeId];
19329
- }
19330
- agent(agentId) {
19331
- return this.registry.agents[agentId];
19332
- }
19333
- conversation(conversationId) {
19334
- return this.registry.conversations[conversationId];
19335
- }
19336
- message(messageId) {
19337
- return this.registry.messages[messageId];
19338
- }
19339
- collaborationRecord(recordId) {
19340
- return this.registry.collaborationRecords[recordId];
19341
- }
19342
- flightForInvocation(invocationId) {
19343
- const flightId = this.flightIdByInvocationId.get(invocationId);
19344
- return flightId ? this.registry.flights[flightId] : undefined;
19345
- }
19346
- bindingsForConversation(conversationId) {
19347
- const bindingIds = this.bindingIdsByConversationId.get(conversationId);
19348
- if (!bindingIds) {
19349
- return [];
19350
- }
19351
- const bindings = [];
19352
- for (const bindingId of bindingIds) {
19353
- const binding = this.registry.bindings[bindingId];
19354
- if (binding) {
19355
- bindings.push(binding);
19356
- }
19357
- }
19358
- return bindings;
19359
- }
19360
- endpointsForAgent(agentId, options = {}) {
19361
- const endpointIds = this.endpointIdsByAgentId.get(agentId);
19362
- if (!endpointIds) {
19363
- return [];
19364
- }
19365
- const endpoints = [];
19366
- for (const endpointId of endpointIds) {
19367
- const endpoint = this.registry.endpoints[endpointId];
19368
- if (!endpoint)
19369
- continue;
19370
- if (!options.includeOffline && endpoint.state === "offline")
19371
- continue;
19372
- if (options.nodeId && endpoint.nodeId !== options.nodeId)
19373
- continue;
19374
- if (options.harness && endpoint.harness !== options.harness)
19375
- continue;
19376
- endpoints.push(endpoint);
19377
- }
19378
- return endpoints;
19379
- }
19380
- recentEvents(limit = 100) {
19381
- return this.eventBuffer.slice(-limit);
19382
- }
19383
- subscribe(listener) {
19384
- this.listeners.add(listener);
19385
- return () => {
19386
- this.listeners.delete(listener);
19387
- };
19388
- }
19389
- async dispatch(command) {
19390
- switch (command.kind) {
19391
- case "node.upsert":
19392
- await this.upsertNode(command.node);
19393
- return;
19394
- case "actor.upsert":
19395
- await this.upsertActor(command.actor);
19396
- return;
19397
- case "agent.upsert":
19398
- await this.upsertAgent(command.agent);
19399
- return;
19400
- case "agent.endpoint.upsert":
19401
- await this.upsertEndpoint(command.endpoint);
19402
- return;
19403
- case "conversation.upsert":
19404
- await this.upsertConversation(command.conversation);
19405
- return;
19406
- case "binding.upsert":
19407
- await this.upsertBinding(command.binding);
19408
- return;
19409
- case "collaboration.upsert":
19410
- await this.upsertCollaboration(command.record);
19411
- return;
19412
- case "collaboration.event.append":
19413
- await this.appendCollaborationEvent(command.event);
19414
- return;
19415
- case "conversation.post":
19416
- await this.postMessage(command.message);
19417
- return;
19418
- case "agent.invoke":
19419
- await this.invokeAgent(command.invocation);
19420
- return;
19421
- case "agent.ensure_awake":
19422
- this.emit({
19423
- id: createRuntimeId("evt"),
19424
- kind: "flight.updated",
19425
- ts: Date.now(),
19426
- actorId: command.requesterId,
19427
- nodeId: this.localNodeId,
19428
- payload: {
19429
- flight: {
19430
- id: createRuntimeId("flt"),
19431
- invocationId: command.agentId,
19432
- requesterId: command.requesterId,
19433
- targetAgentId: command.agentId,
19434
- state: "waking",
19435
- summary: command.reason
19436
- }
19437
- }
19438
- });
19439
- return;
19440
- case "stream.subscribe":
19441
- return;
19442
- default: {
19443
- const exhaustive = command;
19444
- return exhaustive;
19445
- }
19446
- }
19447
- }
19448
- async upsertNode(node) {
19449
- this.registry.nodes[node.id] = node;
19450
- this.emit({
19451
- id: createRuntimeId("evt"),
19452
- kind: "node.upserted",
19453
- ts: Date.now(),
19454
- actorId: node.id,
19455
- nodeId: node.id,
19456
- payload: { node }
19457
- });
19458
- }
19459
- async upsertActor(actor) {
19460
- this.registry.actors[actor.id] = {
19461
- id: actor.id,
19462
- kind: actor.kind,
19463
- displayName: actor.displayName,
19464
- handle: actor.handle,
19465
- labels: actor.labels,
19466
- metadata: actor.metadata
19467
- };
19468
- this.emit({
19469
- id: createRuntimeId("evt"),
19470
- kind: "actor.registered",
19471
- ts: Date.now(),
19472
- actorId: actor.id,
19473
- nodeId: this.localNodeId,
19474
- payload: { actor }
19475
- });
19476
- }
19477
- async upsertAgent(agent) {
19478
- if (!this.registry.actors[agent.id]) {
19479
- this.registry.actors[agent.id] = {
19480
- id: agent.id,
19481
- kind: agent.kind,
19482
- displayName: agent.displayName,
19483
- handle: agent.handle,
19484
- labels: agent.labels,
19485
- metadata: agent.metadata
19486
- };
19487
- }
19488
- this.registry.agents[agent.id] = agent;
19489
- this.emit({
19490
- id: createRuntimeId("evt"),
19491
- kind: "agent.registered",
19492
- ts: Date.now(),
19493
- actorId: agent.id,
19494
- nodeId: agent.authorityNodeId,
19495
- payload: { agent }
19496
- });
19497
- }
19498
- async upsertEndpoint(endpoint) {
19499
- const previous = this.registry.endpoints[endpoint.id];
19500
- if (previous) {
19501
- this.unindexEndpoint(previous);
19502
- }
19503
- this.registry.endpoints[endpoint.id] = endpoint;
19504
- this.indexEndpoint(endpoint);
19505
- this.emit({
19506
- id: createRuntimeId("evt"),
19507
- kind: "agent.endpoint.upserted",
19508
- ts: Date.now(),
19509
- actorId: endpoint.agentId,
19510
- nodeId: endpoint.nodeId,
19511
- payload: { endpoint }
19512
- });
19513
- }
19514
- async upsertConversation(conversation) {
19515
- this.registry.conversations[conversation.id] = conversation;
19516
- this.emit({
19517
- id: createRuntimeId("evt"),
19518
- kind: "conversation.upserted",
19519
- ts: Date.now(),
19520
- actorId: "system",
19521
- nodeId: conversation.authorityNodeId,
19522
- payload: { conversation }
19523
- });
19524
- }
19525
- async upsertBinding(binding) {
19526
- const previous = this.registry.bindings[binding.id];
19527
- if (previous) {
19528
- this.unindexBinding(previous);
19529
- }
19530
- this.registry.bindings[binding.id] = binding;
19531
- this.indexBinding(binding);
19532
- this.emit({
19533
- id: createRuntimeId("evt"),
19534
- kind: "binding.upserted",
19535
- ts: Date.now(),
19536
- actorId: "system",
19537
- nodeId: this.localNodeId,
19538
- payload: { binding }
19539
- });
19540
- }
19541
- async upsertCollaboration(record) {
19542
- assertValidCollaborationRecord(record);
19543
- this.registry.collaborationRecords[record.id] = record;
19544
- this.emit({
19545
- id: createRuntimeId("evt"),
19546
- kind: "collaboration.upserted",
19547
- ts: Date.now(),
19548
- actorId: record.createdById,
19549
- nodeId: this.localNodeId,
19550
- payload: { record }
19551
- });
19552
- }
19553
- async appendCollaborationEvent(event) {
19554
- const record = this.registry.collaborationRecords[event.recordId];
19555
- if (!record) {
19556
- throw new Error(`unknown collaboration record: ${event.recordId}`);
19557
- }
19558
- assertValidCollaborationEvent(event, record);
19559
- this.emit({
19560
- id: createRuntimeId("evt"),
19561
- kind: "collaboration.event.appended",
19562
- ts: Date.now(),
19563
- actorId: event.actorId,
19564
- nodeId: this.localNodeId,
19565
- payload: { event }
19566
- });
19567
- }
19568
- async upsertFlight(flight) {
19569
- const previous = this.registry.flights[flight.id];
19570
- if (previous && this.flightIdByInvocationId.get(previous.invocationId) === previous.id) {
19571
- this.flightIdByInvocationId.delete(previous.invocationId);
19572
- }
19573
- this.registry.flights[flight.id] = flight;
19574
- this.flightIdByInvocationId.set(flight.invocationId, flight.id);
19575
- this.emit({
19576
- id: createRuntimeId("evt"),
19577
- kind: "flight.updated",
19578
- ts: Date.now(),
19579
- actorId: flight.requesterId,
19580
- nodeId: this.localNodeId,
19581
- payload: { flight }
19582
- });
19583
- }
19584
- async postMessage(message, options = {}) {
19585
- const deliveries2 = this.planMessage(message, options);
19586
- await this.commitMessage(message, deliveries2);
19587
- return deliveries2;
19588
- }
19589
- planMessage(message, options = {}) {
19590
- const conversation = this.registry.conversations[message.conversationId];
19591
- if (!conversation) {
19592
- throw new Error(`unknown conversation: ${message.conversationId}`);
19593
- }
19594
- const bindingRoutes = resolveBindingRoutes(this.bindingsForConversation(conversation.id), message);
19595
- const plannedParticipantRoutes = this.resolveParticipantRoutes(conversation.participantIds);
19596
- const participantRoutes = options.localOnly ? plannedParticipantRoutes.filter((route) => !route.nodeId || route.nodeId === this.localNodeId) : plannedParticipantRoutes;
19597
- const deliveries2 = planMessageDeliveries({
19598
- localNodeId: this.localNodeId,
19599
- message,
19600
- conversation,
19601
- participantRoutes,
19602
- bindingRoutes: options.localOnly ? [] : bindingRoutes
19603
- });
19604
- return deliveries2;
19605
- }
19606
- async commitMessage(message, deliveries2) {
19607
- this.registry.messages[message.id] = message;
19608
- this.emit({
19609
- id: createRuntimeId("evt"),
19610
- kind: "message.posted",
19611
- ts: Date.now(),
19612
- actorId: message.actorId,
19613
- nodeId: message.originNodeId,
19614
- payload: { message }
19615
- });
19616
- for (const delivery of deliveries2) {
19617
- this.emit({
19618
- id: createRuntimeId("evt"),
19619
- kind: "delivery.planned",
19620
- ts: Date.now(),
19621
- actorId: message.actorId,
19622
- nodeId: message.originNodeId,
19623
- payload: { delivery }
19624
- });
19625
- }
19626
- }
19627
- async invokeAgent(invocation) {
19628
- const flight = this.planInvocation(invocation);
19629
- await this.commitInvocation(invocation, flight);
19630
- return flight;
19631
- }
19632
- planInvocation(invocation) {
19633
- const targetAgent = this.registry.agents[invocation.targetAgentId];
19634
- if (!targetAgent) {
19635
- throw new Error(`unknown agent: ${invocation.targetAgentId}`);
19636
- }
19637
- const targetEndpoints = this.endpointsForAgent(invocation.targetAgentId, {
19638
- nodeId: targetAgent.authorityNodeId,
19639
- harness: invocation.execution?.harness
19640
- });
19641
- const isLocalAuthority = !this.localNodeId || targetAgent.authorityNodeId === this.localNodeId;
19642
- const startedAt = Date.now();
19643
- let state = invocation.ensureAwake ? "waking" : "queued";
19644
- let summary;
19645
- let error;
19646
- let completedAt;
19647
- if (isLocalAuthority) {
19648
- if (targetEndpoints.length == 0) {
19649
- state = invocation.ensureAwake ? "waking" : "queued";
19650
- summary = invocation.ensureAwake ? invocation.execution?.harness ? `${targetAgent.displayName} waking on ${invocation.execution.harness}.` : `${targetAgent.displayName} waking.` : `Message stored for ${targetAgent.displayName}. Will deliver when online.`;
19651
- } else {
19652
- state = "queued";
19653
- summary = `${targetAgent.displayName} queued for local execution.`;
19654
- }
19655
- }
19656
- const flight = {
19657
- id: createRuntimeId("flt"),
19658
- invocationId: invocation.id,
19659
- requesterId: invocation.requesterId,
19660
- targetAgentId: invocation.targetAgentId,
19661
- state,
19662
- summary,
19663
- error,
19664
- startedAt,
19665
- completedAt,
19666
- metadata: invocation.metadata
19667
- };
19668
- return flight;
19669
- }
19670
- async commitInvocation(invocation, flight) {
19671
- const previous = this.registry.flights[flight.id];
19672
- if (previous && this.flightIdByInvocationId.get(previous.invocationId) === previous.id) {
19673
- this.flightIdByInvocationId.delete(previous.invocationId);
19674
- }
19675
- this.registry.flights[flight.id] = flight;
19676
- this.flightIdByInvocationId.set(flight.invocationId, flight.id);
19677
- const targetAgent = this.registry.agents[invocation.targetAgentId];
19678
- this.emit({
19679
- id: createRuntimeId("evt"),
19680
- kind: "invocation.requested",
19681
- ts: Date.now(),
19682
- actorId: invocation.requesterId,
19683
- nodeId: invocation.requesterNodeId,
19684
- payload: { invocation }
19685
- });
19686
- this.emit({
19687
- id: createRuntimeId("evt"),
19688
- kind: "flight.updated",
19689
- ts: Date.now(),
19690
- actorId: invocation.requesterId,
19691
- nodeId: targetAgent?.authorityNodeId,
19692
- payload: { flight }
19693
- });
19694
- }
19695
- rebuildIndexes() {
19696
- for (const endpoint of Object.values(this.registry.endpoints)) {
19697
- this.indexEndpoint(endpoint);
19698
- }
19699
- for (const binding of Object.values(this.registry.bindings)) {
19700
- this.indexBinding(binding);
19701
- }
19702
- for (const flight of Object.values(this.registry.flights)) {
19703
- this.flightIdByInvocationId.set(flight.invocationId, flight.id);
19704
- }
19705
- }
19706
- indexEndpoint(endpoint) {
19707
- this.addIndexedId(this.endpointIdsByAgentId, endpoint.agentId, endpoint.id);
19708
- }
19709
- unindexEndpoint(endpoint) {
19710
- this.removeIndexedId(this.endpointIdsByAgentId, endpoint.agentId, endpoint.id);
19711
- }
19712
- indexBinding(binding) {
19713
- this.addIndexedId(this.bindingIdsByConversationId, binding.conversationId, binding.id);
19714
- }
19715
- unindexBinding(binding) {
19716
- this.removeIndexedId(this.bindingIdsByConversationId, binding.conversationId, binding.id);
19717
- }
19718
- addIndexedId(index2, key, value) {
19719
- const ids = index2.get(key) ?? new Set;
19720
- ids.add(value);
19721
- index2.set(key, ids);
19722
- }
19723
- removeIndexedId(index2, key, value) {
19724
- const ids = index2.get(key);
19725
- if (!ids) {
19726
- return;
19727
- }
19728
- ids.delete(value);
19729
- if (ids.size === 0) {
19730
- index2.delete(key);
19731
- }
19732
- }
19733
- resolveParticipantRoutes(participantIds) {
19734
- const routes = [];
19735
- for (const participantId of participantIds) {
19736
- const actor = this.registry.actors[participantId];
19737
- const agent = this.registry.agents[participantId];
19738
- const targetIdentity = actor ?? agent;
19739
- const endpoints = this.endpointsForAgent(participantId);
19740
- const endpoint = preferredEndpoint(endpoints);
19741
- if (!endpoint) {
19742
- if (agent?.authorityNodeId && agent.authorityNodeId !== this.localNodeId) {
19743
- routes.push({
19744
- targetId: participantId,
19745
- nodeId: agent.authorityNodeId,
19746
- targetKind: toTargetKind(targetIdentity),
19747
- transport: defaultTransportForActor(targetIdentity),
19748
- speechEnabled: false
19749
- });
19750
- } else if (agent) {
19751
- routes.push({
19752
- targetId: participantId,
19753
- nodeId: agent.authorityNodeId ?? this.localNodeId,
19754
- targetKind: toTargetKind(targetIdentity),
19755
- transport: defaultTransportForActor(targetIdentity),
19756
- speechEnabled: false
19757
- });
19758
- }
19759
- continue;
19760
- }
19761
- routes.push({
19762
- targetId: participantId,
19763
- nodeId: endpoint.nodeId ?? agent?.authorityNodeId,
19764
- targetKind: toTargetKind(targetIdentity),
19765
- transport: endpoint.transport ?? defaultTransportForActor(targetIdentity),
19766
- speechEnabled: Boolean(actor?.kind === "device" || endpoints.some((candidate) => candidate.transport === "local_socket" || candidate.transport === "websocket"))
19767
- });
19768
- }
19769
- return routes;
19770
- }
19771
- emit(event) {
19772
- this.eventBuffer.push(event);
19773
- if (this.eventBuffer.length > 500) {
19774
- this.eventBuffer.shift();
19775
- }
19776
- for (const listener of this.listeners) {
19777
- listener(event);
19778
- }
19779
- }
19780
- }
19781
- // packages/runtime/src/tailscale.ts
19782
- import { readFile as readFile7 } from "fs/promises";
19783
- import { execFile } from "child_process";
19784
- import { promisify } from "util";
19785
- var execFileAsync = promisify(execFile);
19786
- function parseStatusJson(raw2) {
19787
- return JSON.parse(raw2);
19788
- }
19789
- function parsePeers(status) {
19790
- const peers = Object.entries(status.Peer ?? {});
19791
- return peers.map(([fallbackId, peer]) => ({
19792
- id: peer.ID ?? fallbackId,
19793
- name: peer.HostName ?? peer.DNSName ?? fallbackId,
19794
- dnsName: peer.DNSName,
19795
- addresses: peer.TailscaleIPs ?? [],
19796
- online: peer.Online ?? false,
19797
- hostName: peer.HostName,
19798
- os: peer.OS,
19799
- tags: peer.Tags ?? []
19800
- }));
19801
- }
19802
- function parseSelf(status) {
19803
- const self = status.Self;
19804
- if (!self) {
19805
- return null;
19806
- }
19807
- return {
19808
- id: self.ID ?? self.DNSName ?? self.HostName ?? "self",
19809
- name: self.HostName ?? self.DNSName ?? "self",
19810
- dnsName: self.DNSName,
19811
- addresses: self.TailscaleIPs ?? [],
19812
- online: self.Online ?? true,
19813
- hostName: self.HostName,
19814
- os: self.OS,
19815
- tailnetName: status.CurrentTailnet?.Name,
19816
- magicDnsSuffix: status.CurrentTailnet?.MagicDNSSuffix
19817
- };
19818
- }
19819
- function isBackendRunning(status) {
19820
- return (status.BackendState ?? "").trim().toLowerCase() === "running";
19821
- }
19822
- async function readStatusJsonFromFile(filePath) {
19823
- const raw2 = await readFile7(filePath, "utf8");
19824
- return parseStatusJson(raw2);
19825
- }
19826
- async function readStatusJson() {
19827
- const fixturePath = process.env.OPENSCOUT_TAILSCALE_STATUS_JSON;
19828
- if (fixturePath) {
19829
- return readStatusJsonFromFile(fixturePath);
19830
- }
19831
- try {
19832
- const tailscaleBin = process.env.OPENSCOUT_TAILSCALE_BIN ?? "tailscale";
19833
- const { stdout } = await execFileAsync(tailscaleBin, ["status", "--json"]);
19834
- return parseStatusJson(stdout);
19835
- } catch {
19836
- return null;
19837
- }
19838
- }
19839
- async function readTailscaleSelf() {
19840
- const summary = await readTailscaleStatusSummary();
19841
- if (!summary) {
19842
- return null;
19843
- }
19844
- return summary.self;
19845
- }
19846
- async function readTailscaleStatusSummary() {
19847
- const status = await readStatusJson();
19848
- if (!status) {
19849
- return null;
19850
- }
19851
- return {
19852
- backendState: status.BackendState ?? null,
19853
- running: isBackendRunning(status),
19854
- health: status.Health ?? [],
19855
- peers: parsePeers(status),
19856
- self: parseSelf(status)
19857
- };
19858
- }
19859
-
19860
- // packages/runtime/src/index.ts
19861
- init_broker_service();
19862
- init_local_agents();
19863
-
19864
- // packages/runtime/src/scout-broker.ts
19865
- init_dist2();
19866
- init_setup();
19867
- init_support_paths();
19868
- await __promiseAll([
19869
- init_local_agents(),
19870
- init_broker_service()
19871
- ]);
19872
-
19873
- // packages/runtime/src/index.ts
19874
- init_codex_app_server();
19875
- init_setup();
19876
- init_support_paths();
19877
-
19878
- // packages/runtime/src/scout-agent-cards.ts
19879
- init_dist2();
19880
- // packages/runtime/src/thread-events.ts
19881
- class ThreadWatchProtocolError extends Error {
19882
- status;
19883
- body;
19884
- constructor(status, body) {
19885
- super(body.message);
19886
- this.status = status;
19887
- this.body = body;
19888
- }
19889
- }
19890
- function watchKey(conversationId, watcherNodeId, watcherId) {
19891
- return `${conversationId}:${watcherNodeId}:${watcherId}`;
19892
- }
19893
- function writeSse(response, eventName, payload) {
19894
- response.write(`event: ${eventName}
19895
- data: ${JSON.stringify(payload)}
19896
-
19897
- `);
19898
- }
19899
-
19900
- class ThreadEventPlane {
19901
- options;
19902
- watches = new Map;
19903
- watchIdsByKey = new Map;
19904
- watchIdsByConversation = new Map;
19905
- constructor(options) {
19906
- this.options = options;
19907
- }
19908
- async openWatch(request) {
19909
- const conversation = this.requireConversationAuthority(request.conversationId);
19910
- this.requireRemoteWatchAllowed(conversation);
19911
- const afterSeq = Math.max(0, request.afterSeq ?? 0);
19912
- const oldestSeq = await this.options.projection.oldestThreadSeq(conversation.id);
19913
- const latestSeq = await this.options.projection.latestThreadSeq(conversation.id);
19914
- if (latestSeq > 0 && oldestSeq > 0 && afterSeq < oldestSeq - 1) {
19915
- throw new ThreadWatchProtocolError(409, {
19916
- code: "cursor_out_of_range",
19917
- message: `thread cursor ${afterSeq} is older than retained seq ${oldestSeq}`
19918
- });
19919
- }
19920
- const key = watchKey(conversation.id, request.watcherNodeId, request.watcherId);
19921
- const existingWatchId = this.watchIdsByKey.get(key);
19922
- if (existingWatchId) {
19923
- this.closeWatchInternal(existingWatchId);
19924
- }
19925
- const watchId = `thread-watch:${conversation.id}:${request.watcherNodeId}:${request.watcherId}`;
19926
- const leaseExpiresAt = Date.now() + this.normalizeLeaseMs(request.leaseMs);
19927
- const watch = {
19928
- watchId,
19929
- conversationId: conversation.id,
19930
- watcherNodeId: request.watcherNodeId,
19931
- watcherId: request.watcherId,
19932
- acceptedAfterSeq: afterSeq,
19933
- leaseExpiresAt,
19934
- mode: conversation.shareMode === "summary" ? "summary" : "shared",
19935
- clients: new Set
19936
- };
19937
- this.watches.set(watchId, watch);
19938
- this.watchIdsByKey.set(key, watchId);
19939
- this.indexWatch(watch);
19940
- return {
19941
- watchId,
19942
- conversationId: conversation.id,
19943
- authorityNodeId: conversation.authorityNodeId,
19944
- acceptedAfterSeq: afterSeq,
19945
- latestSeq,
19946
- leaseExpiresAt,
19947
- mode: watch.mode
19948
- };
19949
- }
19950
- async renewWatch(request) {
19951
- const watch = this.requireLiveWatch(request.watchId);
19952
- watch.leaseExpiresAt = Date.now() + this.normalizeLeaseMs(request.leaseMs);
19953
- return {
19954
- watchId: watch.watchId,
19955
- leaseExpiresAt: watch.leaseExpiresAt
19956
- };
19957
- }
19958
- async closeWatch(request) {
19959
- this.closeWatchInternal(request.watchId);
19960
- }
19961
- async replay(options) {
19962
- const conversation = this.requireConversationAuthority(options.conversationId);
19963
- this.requireRemoteWatchAllowed(conversation);
19964
- const oldestSeq = await this.options.projection.oldestThreadSeq(conversation.id);
19965
- const latestSeq = await this.options.projection.latestThreadSeq(conversation.id);
19966
- if (latestSeq > 0 && oldestSeq > 0 && options.afterSeq < oldestSeq - 1) {
19967
- throw new ThreadWatchProtocolError(409, {
19968
- code: "cursor_out_of_range",
19969
- message: `thread cursor ${options.afterSeq} is older than retained seq ${oldestSeq}`
19970
- });
19971
- }
19972
- return this.options.projection.listThreadEvents({
19973
- conversationId: conversation.id,
19974
- afterSeq: options.afterSeq,
19975
- limit: Math.min(options.limit ?? 500, this.options.maxReplayLimit ?? 2000)
19976
- });
19977
- }
19978
- async snapshot(conversationId) {
19979
- const conversation = this.requireConversationAuthority(conversationId);
19980
- this.requireRemoteWatchAllowed(conversation);
19981
- const snapshot = await this.options.projection.getThreadSnapshot(conversation.id);
19982
- if (!snapshot) {
19983
- throw new ThreadWatchProtocolError(404, {
19984
- code: "unknown_conversation",
19985
- message: `unknown conversation ${conversation.id}`
19986
- });
19987
- }
19988
- return snapshot;
19989
- }
19990
- async streamWatch(watchId, request, response) {
19991
- const watch = this.requireLiveWatch(watchId);
19992
- const backlog = await this.options.projection.listThreadEvents({
19993
- conversationId: watch.conversationId,
19994
- afterSeq: watch.acceptedAfterSeq,
19995
- limit: this.options.maxReplayLimit ?? 2000
19996
- });
19997
- response.writeHead(200, {
19998
- "content-type": "text/event-stream",
19999
- "cache-control": "no-cache, no-transform",
20000
- connection: "keep-alive"
20001
- });
20002
- writeSse(response, "hello", {
20003
- watchId: watch.watchId,
20004
- conversationId: watch.conversationId,
20005
- leaseExpiresAt: watch.leaseExpiresAt
20006
- });
20007
- for (const event of backlog) {
20008
- writeSse(response, "thread.event", event);
20009
- }
20010
- watch.clients.add(response);
20011
- request.on("close", () => {
20012
- watch.clients.delete(response);
20013
- response.end();
20014
- });
20015
- }
20016
- publish(events2) {
20017
- for (const event of events2) {
20018
- const watchIds = this.watchIdsByConversation.get(event.conversationId);
20019
- if (!watchIds || watchIds.size === 0) {
20020
- continue;
20021
- }
20022
- for (const watchId of [...watchIds]) {
20023
- const watch = this.watches.get(watchId);
20024
- if (!watch) {
20025
- watchIds.delete(watchId);
20026
- continue;
20027
- }
20028
- if (watch.leaseExpiresAt <= Date.now()) {
20029
- this.closeWatchInternal(watchId);
20030
- continue;
20031
- }
20032
- for (const client of watch.clients) {
20033
- writeSse(client, "thread.event", event);
20034
- }
20035
- }
20036
- }
20037
- }
20038
- normalizeLeaseMs(requestedLeaseMs) {
20039
- const defaultLeaseMs = this.options.defaultLeaseMs ?? 30000;
20040
- const maxLeaseMs = this.options.maxLeaseMs ?? 300000;
20041
- if (!requestedLeaseMs || !Number.isFinite(requestedLeaseMs) || requestedLeaseMs <= 0) {
20042
- return defaultLeaseMs;
20043
- }
20044
- return Math.min(Math.max(requestedLeaseMs, 5000), maxLeaseMs);
20045
- }
20046
- requireConversationAuthority(conversationId) {
20047
- const conversation = this.options.runtime.conversation(conversationId);
20048
- if (!conversation) {
20049
- throw new ThreadWatchProtocolError(404, {
20050
- code: "unknown_conversation",
20051
- message: `unknown conversation ${conversationId}`
20052
- });
20053
- }
20054
- if (conversation.authorityNodeId !== this.options.nodeId) {
20055
- throw new ThreadWatchProtocolError(409, {
20056
- code: "no_responder",
20057
- message: `conversation ${conversationId} is owned by ${conversation.authorityNodeId}`
20058
- });
20059
- }
20060
- return conversation;
20061
- }
20062
- requireRemoteWatchAllowed(conversation) {
20063
- if (conversation.shareMode === "local") {
20064
- throw new ThreadWatchProtocolError(403, {
20065
- code: "forbidden",
20066
- message: `conversation ${conversation.id} does not allow remote watches`
20067
- });
20068
- }
20069
- }
20070
- requireLiveWatch(watchId) {
20071
- const watch = this.watches.get(watchId);
20072
- if (!watch) {
20073
- throw new ThreadWatchProtocolError(404, {
20074
- code: "invalid_request",
20075
- message: `unknown watch ${watchId}`
20076
- });
20077
- }
20078
- if (watch.leaseExpiresAt <= Date.now()) {
20079
- this.closeWatchInternal(watchId);
20080
- throw new ThreadWatchProtocolError(410, {
20081
- code: "lease_expired",
20082
- message: `watch ${watchId} lease expired`
20083
- });
20084
- }
20085
- return watch;
20086
- }
20087
- indexWatch(watch) {
20088
- const ids = this.watchIdsByConversation.get(watch.conversationId) ?? new Set;
20089
- ids.add(watch.watchId);
20090
- this.watchIdsByConversation.set(watch.conversationId, ids);
20091
- }
20092
- closeWatchInternal(watchId) {
20093
- const watch = this.watches.get(watchId);
20094
- if (!watch) {
20095
- return;
20096
- }
20097
- this.watches.delete(watchId);
20098
- this.watchIdsByKey.delete(watchKey(watch.conversationId, watch.watcherNodeId, watch.watcherId));
20099
- const ids = this.watchIdsByConversation.get(watch.conversationId);
20100
- if (ids) {
20101
- ids.delete(watchId);
20102
- if (ids.size === 0) {
20103
- this.watchIdsByConversation.delete(watch.conversationId);
20104
- }
20105
- }
20106
- for (const client of watch.clients) {
20107
- client.end();
20108
- }
20109
- watch.clients.clear();
20110
- }
20111
- }
20112
- // packages/runtime/src/mobile-push.ts
20113
- init_support_paths();
20114
- // packages/web/server/core/observe/service.ts
20115
- var HISTORY_SNAPSHOT_CACHE_LIMIT = 128;
20116
- var historySnapshotCache = new Map;
20117
- var OBSERVE_SUMMARY_TAIL_SIZE = 8;
20118
- function activeEndpoint(snapshot, agentId) {
20119
- const candidates = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === agentId);
20120
- const rank = (state) => {
20121
- switch (state) {
20122
- case "active":
20123
- return 0;
20124
- case "idle":
20125
- return 1;
20126
- case "waiting":
20127
- return 2;
20128
- case "offline":
20129
- return 5;
20130
- default:
20131
- return 4;
20132
- }
20133
- };
20134
- return [...candidates].sort((left, right) => rank(left.state) - rank(right.state))[0] ?? null;
20135
- }
20136
- function expandHome(value) {
20137
- if (!value) {
20138
- return null;
20139
- }
20140
- if (value === "~") {
20141
- return homedir12();
20142
- }
20143
- if (value.startsWith("~/")) {
20144
- return join18(homedir12(), value.slice(2));
20145
- }
20146
- return value;
20147
- }
20148
- function encodeClaudeProjectsSlug2(absolutePath) {
20149
- const normalized = resolve8(absolutePath);
20150
- return `-${normalized.replace(/^\//u, "").replace(/\//gu, "-")}`;
20151
- }
20152
- function resolveClaudeHistoryPath(cwd, sessionId) {
20153
- const normalizedCwd = expandHome(cwd)?.trim();
20154
- if (!normalizedCwd) {
20155
- return null;
20156
- }
20157
- const projectDir = join18(homedir12(), ".claude", "projects", encodeClaudeProjectsSlug2(normalizedCwd));
20158
- const normalizedSessionId = sessionId?.trim().replace(/\.jsonl$/u, "") || "";
20159
- if (normalizedSessionId) {
20160
- const exactPath = join18(projectDir, `${normalizedSessionId}.jsonl`);
20161
- if (existsSync12(exactPath)) {
20162
- return exactPath;
20163
- }
20164
- }
20165
- return findMostRecentJsonl(projectDir);
20166
- }
20167
- function findMostRecentJsonl(dir) {
20168
- if (!existsSync12(dir))
20169
- return null;
20170
- try {
20171
- let best = null;
20172
- for (const entry of readdirSync2(dir)) {
20173
- if (!entry.endsWith(".jsonl"))
20174
- continue;
20175
- const full = join18(dir, entry);
20176
- const st = statSync4(full);
20177
- if (!best || st.mtimeMs > best.mtime) {
20178
- best = { path: full, mtime: st.mtimeMs };
20179
- }
20180
- }
20181
- return best?.path ?? null;
20182
- } catch {
20183
- return null;
18091
+ return best?.path ?? null;
18092
+ } catch {
18093
+ return null;
20184
18094
  }
20185
18095
  }
20186
18096
  function historyAdapterAlias(value) {
@@ -20498,8 +18408,8 @@ function syntheticContextUsage(eventCount) {
20498
18408
  return [];
20499
18409
  }
20500
18410
  const length = Math.max(2, Math.min(eventCount, 24));
20501
- return Array.from({ length }, (_, index2) => {
20502
- const progress = length <= 1 ? 1 : index2 / (length - 1);
18411
+ return Array.from({ length }, (_, index) => {
18412
+ const progress = length <= 1 ? 1 : index / (length - 1);
20503
18413
  return Math.min(0.92, 0.08 + progress * 0.66);
20504
18414
  });
20505
18415
  }
@@ -20630,9 +18540,9 @@ function buildObserveDataFromSnapshot(snapshot, timedEvents = [], live = false)
20630
18540
  }
20631
18541
  }
20632
18542
  const seconds = timelineSeconds(eventDrafts.map((draft) => draft.timestampMs), timing.baseTimestampMs);
20633
- const builtEntries = eventDrafts.map((draft, index2) => ({
18543
+ const builtEntries = eventDrafts.map((draft, index) => ({
20634
18544
  draft,
20635
- event: draft.build(seconds[index2] ?? 0)
18545
+ event: draft.build(seconds[index] ?? 0)
20636
18546
  })).filter(({ event }) => event.text.length > 0 || event.kind === "tool" || event.kind === "boot");
20637
18547
  const events2 = builtEntries.map((entry) => entry.event);
20638
18548
  for (const { draft, event } of builtEntries) {
@@ -20663,8 +18573,8 @@ function unavailableObserveData(agent) {
20663
18573
  live: false
20664
18574
  };
20665
18575
  }
20666
- async function resolveSnapshotSource(agent, broker2) {
20667
- const endpoint = broker2 ? activeEndpoint(broker2.snapshot, agent.id) : null;
18576
+ async function resolveSnapshotSource(agent, broker) {
18577
+ const endpoint = broker ? activeEndpoint(broker.snapshot, agent.id) : null;
20668
18578
  const liveSnapshot = await readLiveSnapshot(endpoint);
20669
18579
  const live = Boolean(liveSnapshot && (liveSnapshot.currentTurnId || liveSnapshot.session.status === "active"));
20670
18580
  const historyCandidate = resolveHistoryCandidate(agent, liveSnapshot);
@@ -20707,8 +18617,8 @@ async function resolveSnapshotSource(agent, broker2) {
20707
18617
  sessionId: null
20708
18618
  };
20709
18619
  }
20710
- async function buildAgentObservePayload(agent, broker2) {
20711
- const source = await resolveSnapshotSource(agent, broker2);
18620
+ async function buildAgentObservePayload(agent, broker) {
18621
+ const source = await resolveSnapshotSource(agent, broker);
20712
18622
  if (source.source === "unavailable") {
20713
18623
  return {
20714
18624
  agentId: agent.id,
@@ -20737,8 +18647,8 @@ async function loadAgentObservePayloadsInternal(agentIds) {
20737
18647
  if (filteredAgents.length === 0) {
20738
18648
  return [];
20739
18649
  }
20740
- const broker2 = await loadScoutBrokerContext();
20741
- return await Promise.all(filteredAgents.map((agent) => buildAgentObservePayload(agent, broker2)));
18650
+ const broker = await loadScoutBrokerContext();
18651
+ return await Promise.all(filteredAgents.map((agent) => buildAgentObservePayload(agent, broker)));
20742
18652
  }
20743
18653
  function summarizeAgentObservePayload(payload) {
20744
18654
  return {
@@ -20750,19 +18660,100 @@ function summarizeAgentObservePayload(payload) {
20750
18660
  }
20751
18661
  };
20752
18662
  }
20753
- async function loadAgentObserveSummaries(agentIds) {
20754
- const payloads = await loadAgentObservePayloadsInternal(agentIds);
20755
- return payloads.map(summarizeAgentObservePayload);
20756
- }
20757
- async function loadAgentObservePayload(agentId) {
20758
- const payloads = await loadAgentObservePayloadsInternal([agentId]);
20759
- return payloads[0] ?? null;
20760
- }
18663
+ async function loadAgentObserveSummaries(agentIds) {
18664
+ const payloads = await loadAgentObservePayloadsInternal(agentIds);
18665
+ return payloads.map(summarizeAgentObservePayload);
18666
+ }
18667
+ async function loadAgentObservePayload(agentId) {
18668
+ const payloads = await loadAgentObservePayloadsInternal([agentId]);
18669
+ return payloads[0] ?? null;
18670
+ }
18671
+
18672
+ // packages/web/server/core/mesh/service.ts
18673
+ await init_broker_service();
18674
+ import { execFile as execFile2 } from "child_process";
18675
+ import { promisify as promisify2 } from "util";
18676
+
18677
+ // packages/runtime/src/tailscale.ts
18678
+ import { readFile as readFile7 } from "fs/promises";
18679
+ import { execFile } from "child_process";
18680
+ import { promisify } from "util";
18681
+ var execFileAsync = promisify(execFile);
18682
+ function parseStatusJson(raw2) {
18683
+ return JSON.parse(raw2);
18684
+ }
18685
+ function parsePeers(status) {
18686
+ const peers = Object.entries(status.Peer ?? {});
18687
+ return peers.map(([fallbackId, peer]) => ({
18688
+ id: peer.ID ?? fallbackId,
18689
+ name: peer.HostName ?? peer.DNSName ?? fallbackId,
18690
+ dnsName: peer.DNSName,
18691
+ addresses: peer.TailscaleIPs ?? [],
18692
+ online: peer.Online ?? false,
18693
+ hostName: peer.HostName,
18694
+ os: peer.OS,
18695
+ tags: peer.Tags ?? []
18696
+ }));
18697
+ }
18698
+ function parseSelf(status) {
18699
+ const self = status.Self;
18700
+ if (!self) {
18701
+ return null;
18702
+ }
18703
+ return {
18704
+ id: self.ID ?? self.DNSName ?? self.HostName ?? "self",
18705
+ name: self.HostName ?? self.DNSName ?? "self",
18706
+ dnsName: self.DNSName,
18707
+ addresses: self.TailscaleIPs ?? [],
18708
+ online: self.Online ?? true,
18709
+ hostName: self.HostName,
18710
+ os: self.OS,
18711
+ tailnetName: status.CurrentTailnet?.Name,
18712
+ magicDnsSuffix: status.CurrentTailnet?.MagicDNSSuffix
18713
+ };
18714
+ }
18715
+ function isBackendRunning(status) {
18716
+ return (status.BackendState ?? "").trim().toLowerCase() === "running";
18717
+ }
18718
+ async function readStatusJsonFromFile(filePath) {
18719
+ const raw2 = await readFile7(filePath, "utf8");
18720
+ return parseStatusJson(raw2);
18721
+ }
18722
+ async function readStatusJson() {
18723
+ const fixturePath = process.env.OPENSCOUT_TAILSCALE_STATUS_JSON;
18724
+ if (fixturePath) {
18725
+ return readStatusJsonFromFile(fixturePath);
18726
+ }
18727
+ try {
18728
+ const tailscaleBin = process.env.OPENSCOUT_TAILSCALE_BIN ?? "tailscale";
18729
+ const { stdout } = await execFileAsync(tailscaleBin, ["status", "--json"]);
18730
+ return parseStatusJson(stdout);
18731
+ } catch {
18732
+ return null;
18733
+ }
18734
+ }
18735
+ async function readTailscaleSelf() {
18736
+ const summary = await readTailscaleStatusSummary();
18737
+ if (!summary) {
18738
+ return null;
18739
+ }
18740
+ return summary.self;
18741
+ }
18742
+ async function readTailscaleStatusSummary() {
18743
+ const status = await readStatusJson();
18744
+ if (!status) {
18745
+ return null;
18746
+ }
18747
+ return {
18748
+ backendState: status.BackendState ?? null,
18749
+ running: isBackendRunning(status),
18750
+ health: status.Health ?? [],
18751
+ peers: parsePeers(status),
18752
+ self: parseSelf(status)
18753
+ };
18754
+ }
20761
18755
 
20762
18756
  // packages/web/server/core/mesh/service.ts
20763
- await init_broker_service();
20764
- import { execFile as execFile2 } from "child_process";
20765
- import { promisify as promisify2 } from "util";
20766
18757
  var execFileAsync2 = promisify2(execFile2);
20767
18758
  function normalizeHost(host) {
20768
18759
  return host.trim().replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
@@ -20808,7 +18799,7 @@ async function readTailscaleStatus() {
20808
18799
  function formatIssueWarning(issue) {
20809
18800
  return issue.action ? `${issue.title} \u2014 ${issue.summary} ${issue.action}` : `${issue.title} \u2014 ${issue.summary}`;
20810
18801
  }
20811
- function computeIssues(health, localNode, nodes, tailscale2) {
18802
+ function computeIssues(health, localNode, nodes, tailscale) {
20812
18803
  const issues = [];
20813
18804
  if (!health.reachable) {
20814
18805
  issues.push({
@@ -20821,7 +18812,7 @@ function computeIssues(health, localNode, nodes, tailscale2) {
20821
18812
  });
20822
18813
  return issues;
20823
18814
  }
20824
- if (tailscale2.available && !tailscale2.running) {
18815
+ if (tailscale.available && !tailscale.running) {
20825
18816
  issues.push({
20826
18817
  code: "tailscale_stopped",
20827
18818
  severity: "warning",
@@ -20851,7 +18842,7 @@ function computeIssues(health, localNode, nodes, tailscale2) {
20851
18842
  });
20852
18843
  }
20853
18844
  const remoteNodes = Object.values(nodes).filter((n) => n.id !== localNode?.id);
20854
- if (!tailscale2.available && remoteNodes.length === 0) {
18845
+ if (!tailscale.available && remoteNodes.length === 0) {
20855
18846
  issues.push({
20856
18847
  code: "discovery_unconfigured",
20857
18848
  severity: "warning",
@@ -20940,7 +18931,7 @@ function filterCurrentMeshNodes(allNodes, meshId, localNodeId, now) {
20940
18931
  }
20941
18932
  async function loadMeshStatus() {
20942
18933
  const brokerUrl = resolveScoutBrokerUrl();
20943
- const [health, context, tailscale2] = await Promise.all([
18934
+ const [health, context, tailscale] = await Promise.all([
20944
18935
  readScoutBrokerHealth(brokerUrl),
20945
18936
  loadScoutBrokerContext(brokerUrl),
20946
18937
  readTailscaleStatus()
@@ -20950,9 +18941,9 @@ async function loadMeshStatus() {
20950
18941
  const meshId = health.meshId ?? localNode?.meshId ?? null;
20951
18942
  const identity = computeIdentitySummary(health, localNode, meshId);
20952
18943
  const nodes = filterCurrentMeshNodes(allNodes, meshId, localNode?.id, Date.now());
20953
- const issues = computeIssues(health, localNode, nodes, tailscale2);
18944
+ const issues = computeIssues(health, localNode, nodes, tailscale);
20954
18945
  const warnings = issues.map(formatIssueWarning);
20955
- return { brokerUrl, health, localNode, meshId, identity, nodes, tailscale: tailscale2, issues, warnings };
18946
+ return { brokerUrl, health, localNode, meshId, identity, nodes, tailscale, issues, warnings };
20956
18947
  }
20957
18948
  function preferredAnnounceHost(self, currentBrokerUrl) {
20958
18949
  const dnsName = stripTrailingDot(self?.dnsName);
@@ -21055,415 +19046,6 @@ init_setup();
21055
19046
  init_support_paths();
21056
19047
  init_claude_stream_json();
21057
19048
  init_harness_catalog();
21058
-
21059
- // ../hudson/packages/hudson-relay/src/relay/session.ts
21060
- import { createRequire } from "module";
21061
- import { execSync as execSync2 } from "child_process";
21062
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
21063
- import { join as join19, dirname as pathDirname } from "path";
21064
- var require2 = createRequire(import.meta.url);
21065
- var pty = require2("node-pty");
21066
- var DEFAULT_ORPHAN_TTL_MS = 30 * 60 * 1000;
21067
- var MAX_BUFFER_SIZE = 512 * 1024;
21068
- var sessions3 = new Map;
21069
- function generateId() {
21070
- return Math.random().toString(36).slice(2, 10);
21071
- }
21072
- function send(ws, data) {
21073
- if (ws.readyState === 1) {
21074
- ws.send(JSON.stringify(data));
21075
- }
21076
- }
21077
- function resolveCwd(raw2) {
21078
- const home = process.env.HOME || "/tmp";
21079
- const expanded = (raw2 || home).replace(/^~/, home);
21080
- if (existsSync13(expanded))
21081
- return expanded;
21082
- try {
21083
- mkdirSync6(expanded, { recursive: true });
21084
- console.log(`[relay] Created missing cwd: ${expanded}`);
21085
- return expanded;
21086
- } catch {
21087
- console.warn(`[relay] Could not create cwd ${expanded}, falling back to ${home}`);
21088
- return home;
21089
- }
21090
- }
21091
- function findBin(name, envOverride) {
21092
- if (envOverride && process.env[envOverride])
21093
- return process.env[envOverride];
21094
- try {
21095
- return execSync2(`which ${name}`, { encoding: "utf8" }).trim() || null;
21096
- } catch {
21097
- return null;
21098
- }
21099
- }
21100
- function findClaudeBin() {
21101
- return findBin("claude", "CLAUDE_BIN");
21102
- }
21103
- function findPiBin() {
21104
- return findBin("pi", "PI_BIN");
21105
- }
21106
- function tmuxSessionExists2(name) {
21107
- try {
21108
- execSync2(`tmux has-session -t ${name} 2>/dev/null`, { encoding: "utf8" });
21109
- return true;
21110
- } catch {
21111
- return false;
21112
- }
21113
- }
21114
- function bootstrapFiles(cwd, files, sessionId) {
21115
- for (const [relPath, content] of Object.entries(files)) {
21116
- const absPath = join19(cwd, relPath);
21117
- if (!existsSync13(absPath)) {
21118
- try {
21119
- const dir = pathDirname(absPath);
21120
- if (!existsSync13(dir))
21121
- mkdirSync6(dir, { recursive: true });
21122
- writeFileSync6(absPath, content, "utf-8");
21123
- console.log(`[relay] Session ${sessionId}: bootstrapped ${relPath}`);
21124
- } catch (err) {
21125
- console.warn(`[relay] Session ${sessionId}: failed to bootstrap ${relPath}:`, err);
21126
- }
21127
- }
21128
- }
21129
- }
21130
- function spawnTmuxSession(tmuxName, cols, rows, cwd, claudeBin, claudeArgs, env) {
21131
- const exists = tmuxSessionExists2(tmuxName);
21132
- if (!exists) {
21133
- const shellCmd = [claudeBin, ...claudeArgs].map((a) => a.includes(" ") ? `'${a}'` : a).join(" ");
21134
- execSync2(`tmux new-session -d -s ${tmuxName} -x ${cols} -y ${rows} -c '${cwd}' '${shellCmd}'`, { env });
21135
- console.log(`[relay] Created tmux session: ${tmuxName}`);
21136
- } else {
21137
- try {
21138
- execSync2(`tmux resize-window -t ${tmuxName} -x ${cols} -y ${rows} 2>/dev/null`);
21139
- } catch {}
21140
- console.log(`[relay] Attaching to existing tmux session: ${tmuxName}`);
21141
- }
21142
- return pty.spawn("tmux", ["attach", "-t", tmuxName], {
21143
- name: "xterm-256color",
21144
- cols,
21145
- rows,
21146
- cwd,
21147
- env
21148
- });
21149
- }
21150
- function createSession(ws, msg) {
21151
- const id = generateId();
21152
- const cols = Math.max(msg.cols || 80, 20);
21153
- const rows = Math.max(msg.rows || 24, 4);
21154
- const backend = msg.backend || "pty";
21155
- const tmuxName = msg.tmuxSession || `hudson-${id}`;
21156
- const agent = msg.agent || "claude";
21157
- let agentBin;
21158
- if (agent === "pi") {
21159
- agentBin = findPiBin();
21160
- if (!agentBin) {
21161
- const reason = "pi CLI not found. Install it with: npm install -g @mariozechner/pi-coding-agent";
21162
- console.error(`[relay] Session ${id} failed: ${reason}`);
21163
- send(ws, { type: "session:error", error: reason });
21164
- return null;
21165
- }
21166
- } else {
21167
- agentBin = findClaudeBin();
21168
- if (!agentBin) {
21169
- const reason = "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code";
21170
- console.error(`[relay] Session ${id} failed: ${reason}`);
21171
- send(ws, { type: "session:error", error: reason });
21172
- return null;
21173
- }
21174
- }
21175
- if (!existsSync13(agentBin)) {
21176
- const reason = `${agent} binary not found at ${agentBin}`;
21177
- console.error(`[relay] Session ${id} failed: ${reason}`);
21178
- send(ws, { type: "session:error", error: reason });
21179
- return null;
21180
- }
21181
- if (backend === "tmux" && !findBin("tmux")) {
21182
- const reason = "tmux not found. Install it with: brew install tmux";
21183
- console.error(`[relay] Session ${id} failed: ${reason}`);
21184
- send(ws, { type: "session:error", error: reason });
21185
- return null;
21186
- }
21187
- const cwd = resolveCwd(msg.cwd);
21188
- if (msg.workspaceFiles) {
21189
- bootstrapFiles(cwd, msg.workspaceFiles, id);
21190
- }
21191
- let agentArgs;
21192
- if (agent === "pi") {
21193
- agentArgs = ["--verbose"];
21194
- if (msg.provider)
21195
- agentArgs.push("--provider", msg.provider);
21196
- if (msg.model)
21197
- agentArgs.push("--model", msg.model);
21198
- if (msg.systemPrompt)
21199
- agentArgs.push("--system-prompt", msg.systemPrompt);
21200
- } else {
21201
- agentArgs = ["--verbose"];
21202
- if (msg.systemPrompt)
21203
- agentArgs.push("--system-prompt", msg.systemPrompt);
21204
- }
21205
- const env = { ...process.env, TERM: "xterm-256color", FORCE_COLOR: "1" };
21206
- delete env.CLAUDECODE;
21207
- let ptyProcess;
21208
- try {
21209
- if (backend === "tmux") {
21210
- console.log(`[relay] Session ${id}: tmux backend (session: ${tmuxName}) in ${cwd} [agent: ${agent}]`);
21211
- ptyProcess = spawnTmuxSession(tmuxName, cols, rows, cwd, agentBin, agentArgs, env);
21212
- } else {
21213
- console.log(`[relay] Session ${id}: pty backend, spawning ${agentBin} in ${cwd} [agent: ${agent}]`);
21214
- ptyProcess = pty.spawn(agentBin, agentArgs, {
21215
- name: "xterm-256color",
21216
- cols,
21217
- rows,
21218
- cwd,
21219
- env
21220
- });
21221
- }
21222
- } catch (err) {
21223
- const message = err instanceof Error ? err.message : String(err);
21224
- console.error(`[relay] Session ${id}: failed to spawn PTY \u2014 ${message}`);
21225
- send(ws, { type: "session:error", error: `Failed to spawn terminal: ${message}` });
21226
- return null;
21227
- }
21228
- const orphanTTL = msg.orphanTTL && msg.orphanTTL > 0 ? msg.orphanTTL : DEFAULT_ORPHAN_TTL_MS;
21229
- const session = {
21230
- id,
21231
- pty: ptyProcess,
21232
- ws,
21233
- outputBuffer: "",
21234
- cols,
21235
- rows,
21236
- reapTimer: null,
21237
- orphanTTL,
21238
- backend,
21239
- ...backend === "tmux" ? { tmuxSession: tmuxName } : {},
21240
- exited: false,
21241
- exitCode: null
21242
- };
21243
- const startTime = Date.now();
21244
- ptyProcess.onData((data) => {
21245
- session.outputBuffer += data;
21246
- if (session.outputBuffer.length > MAX_BUFFER_SIZE) {
21247
- session.outputBuffer = session.outputBuffer.slice(-MAX_BUFFER_SIZE);
21248
- }
21249
- if (session.ws && session.ws.readyState === 1) {
21250
- send(session.ws, { type: "terminal:data", data });
21251
- }
21252
- });
21253
- ptyProcess.onExit(({ exitCode }) => {
21254
- session.exited = true;
21255
- session.exitCode = exitCode;
21256
- const uptime = Date.now() - startTime;
21257
- const crashed = exitCode !== 0 && uptime < 5000;
21258
- let reason;
21259
- if (crashed) {
21260
- const clean = session.outputBuffer.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim();
21261
- const lines = clean.split(`
21262
- `).filter((l) => l.trim()).slice(-5);
21263
- reason = lines.join(`
21264
- `) || `Process exited with code ${exitCode}`;
21265
- console.error(`[relay] Session ${id} crashed after ${uptime}ms (code ${exitCode}): ${reason}`);
21266
- }
21267
- if (session.ws) {
21268
- send(session.ws, { type: "session:exit", exitCode, ...reason ? { reason } : {} });
21269
- }
21270
- scheduleReap(session, 1e4);
21271
- });
21272
- sessions3.set(id, session);
21273
- console.log(`[relay] Session ${id} created (${cols}x${rows})`);
21274
- return session;
21275
- }
21276
- function attachSession(session, ws, cols, rows) {
21277
- if (session.reapTimer) {
21278
- clearTimeout(session.reapTimer);
21279
- session.reapTimer = null;
21280
- }
21281
- session.ws = ws;
21282
- if (cols && rows) {
21283
- const c = Math.max(cols, 20);
21284
- const r = Math.max(rows, 4);
21285
- if (c !== session.cols || r !== session.rows) {
21286
- session.pty.resize(c, r);
21287
- session.cols = c;
21288
- session.rows = r;
21289
- }
21290
- }
21291
- if (session.exited) {
21292
- send(ws, { type: "session:exit", exitCode: session.exitCode });
21293
- } else if (session.outputBuffer.length > 0) {
21294
- send(ws, { type: "terminal:data", data: session.outputBuffer });
21295
- }
21296
- console.log(`[relay] Session ${session.id} reconnected`);
21297
- }
21298
- function detachSession(session) {
21299
- session.ws = null;
21300
- if (session.exited) {
21301
- scheduleReap(session, 5000);
21302
- } else {
21303
- scheduleReap(session, session.orphanTTL);
21304
- console.log(`[relay] Session ${session.id} detached (orphaned for ${session.orphanTTL / 1000}s)`);
21305
- }
21306
- }
21307
- function scheduleReap(session, delay) {
21308
- if (session.reapTimer)
21309
- clearTimeout(session.reapTimer);
21310
- session.reapTimer = setTimeout(() => {
21311
- if (!session.ws) {
21312
- destroy(session.id);
21313
- }
21314
- }, delay);
21315
- }
21316
- function destroy(sessionId) {
21317
- const session = sessions3.get(sessionId);
21318
- if (!session)
21319
- return;
21320
- if (session.reapTimer)
21321
- clearTimeout(session.reapTimer);
21322
- try {
21323
- session.pty.kill();
21324
- } catch {}
21325
- sessions3.delete(sessionId);
21326
- if (session.backend === "tmux") {
21327
- console.log(`[relay] Session ${sessionId} bridge destroyed (tmux session '${session.tmuxSession}' still alive)`);
21328
- } else {
21329
- console.log(`[relay] Session ${sessionId} destroyed`);
21330
- }
21331
- }
21332
-
21333
- // packages/web/server/relay.ts
21334
- import { mkdir as mkdir7 } from "fs/promises";
21335
- import { join as join20 } from "path";
21336
- import { randomUUID as randomUUID3 } from "crypto";
21337
- var UPLOAD_DIR = "/tmp/scout-uploads";
21338
- async function handleRelayUpload(req) {
21339
- const { name, data } = await req.json();
21340
- if (!name || !data) {
21341
- return Response.json({ error: "Missing name or data" }, { status: 400 });
21342
- }
21343
- await mkdir7(UPLOAD_DIR, { recursive: true });
21344
- const filename = `${randomUUID3()}-${name}`;
21345
- const filepath = join20(UPLOAD_DIR, filename);
21346
- await Bun.write(filepath, Buffer.from(data, "base64"));
21347
- return Response.json({ path: filepath });
21348
- }
21349
- var _pendingCommand = null;
21350
- function queueTerminalCommand(cmd) {
21351
- for (const [, session] of sessions3) {
21352
- if (!session.exited) {
21353
- session.pty.write(cmd + `
21354
- `);
21355
- return;
21356
- }
21357
- }
21358
- _pendingCommand = cmd;
21359
- }
21360
- function drainPendingCommand() {
21361
- const cmd = _pendingCommand;
21362
- _pendingCommand = null;
21363
- return cmd;
21364
- }
21365
- function asRelay(ws) {
21366
- return ws;
21367
- }
21368
- function parseMessage(raw2) {
21369
- try {
21370
- const msg = JSON.parse(raw2);
21371
- if (typeof msg.type === "string")
21372
- return msg;
21373
- } catch {}
21374
- return null;
21375
- }
21376
- var relayWebSocket = {
21377
- open(_ws) {},
21378
- message(ws, raw2) {
21379
- const msg = parseMessage(typeof raw2 === "string" ? raw2 : raw2.toString());
21380
- if (!msg)
21381
- return;
21382
- const rws = asRelay(ws);
21383
- switch (msg.type) {
21384
- case "session:init": {
21385
- if (ws.data.sessionId) {
21386
- const prev = sessions3.get(ws.data.sessionId);
21387
- if (prev)
21388
- detachSession(prev);
21389
- }
21390
- const session = createSession(rws, msg);
21391
- if (!session)
21392
- break;
21393
- ws.data.sessionId = session.id;
21394
- send(rws, { type: "session:ready", sessionId: session.id });
21395
- const pending = drainPendingCommand();
21396
- if (pending)
21397
- setTimeout(() => session.pty.write(pending + `
21398
- `), 400);
21399
- break;
21400
- }
21401
- case "session:reconnect": {
21402
- const existing = sessions3.get(msg.sessionId);
21403
- if (existing && !existing.exited) {
21404
- if (ws.data.sessionId && ws.data.sessionId !== msg.sessionId) {
21405
- const prev = sessions3.get(ws.data.sessionId);
21406
- if (prev)
21407
- detachSession(prev);
21408
- }
21409
- if (existing.ws && existing.ws !== rws) {
21410
- send(existing.ws, { type: "session:detached" });
21411
- }
21412
- ws.data.sessionId = existing.id;
21413
- attachSession(existing, rws, msg.cols, msg.rows);
21414
- send(rws, {
21415
- type: "session:ready",
21416
- sessionId: existing.id,
21417
- reconnected: true
21418
- });
21419
- const pending = drainPendingCommand();
21420
- if (pending)
21421
- setTimeout(() => existing.pty.write(pending + `
21422
- `), 400);
21423
- } else {
21424
- send(rws, { type: "session:expired", sessionId: msg.sessionId });
21425
- }
21426
- break;
21427
- }
21428
- case "terminal:input": {
21429
- if (!ws.data.sessionId)
21430
- return;
21431
- const session = sessions3.get(ws.data.sessionId);
21432
- if (session && !session.exited) {
21433
- session.pty.write(msg.data);
21434
- }
21435
- break;
21436
- }
21437
- case "terminal:resize": {
21438
- if (!ws.data.sessionId)
21439
- return;
21440
- const session = sessions3.get(ws.data.sessionId);
21441
- if (session && !session.exited) {
21442
- const cols = Math.max(msg.cols || 80, 20);
21443
- const rows = Math.max(msg.rows || 24, 4);
21444
- session.pty.resize(cols, rows);
21445
- session.cols = cols;
21446
- session.rows = rows;
21447
- }
21448
- break;
21449
- }
21450
- }
21451
- },
21452
- close(ws) {
21453
- if (ws.data.sessionId) {
21454
- const session = sessions3.get(ws.data.sessionId);
21455
- if (session)
21456
- detachSession(session);
21457
- ws.data.sessionId = null;
21458
- }
21459
- }
21460
- };
21461
- function destroyAllRelaySessions() {
21462
- for (const [id] of sessions3)
21463
- destroy(id);
21464
- }
21465
-
21466
- // packages/web/server/create-openscout-web-server.ts
21467
19049
  function parseOptionalPositiveInt(value, fallback) {
21468
19050
  if (typeof value !== "string" || value.trim().length === 0) {
21469
19051
  return fallback;
@@ -21518,7 +19100,7 @@ function resolveStaticRoot(staticRoot) {
21518
19100
  return configured;
21519
19101
  }
21520
19102
  const bundled = resolveBundledStaticClientRoot(import.meta.url);
21521
- if (existsSync14(resolve9(bundled, "index.html"))) {
19103
+ if (existsSync13(resolve9(bundled, "index.html"))) {
21522
19104
  return bundled;
21523
19105
  }
21524
19106
  return resolveSourceStaticClientRoot(import.meta.url);
@@ -21791,7 +19373,15 @@ async function createOpenScoutWebServer(options) {
21791
19373
  const body = await c.req.json();
21792
19374
  if (!body.command)
21793
19375
  return c.json({ error: "missing command" }, 400);
21794
- queueTerminalCommand(body.command);
19376
+ if (!options.runTerminalCommand) {
19377
+ return c.json({ error: "terminal relay is unavailable" }, 503);
19378
+ }
19379
+ try {
19380
+ await options.runTerminalCommand(body.command);
19381
+ } catch (error) {
19382
+ const message = error instanceof Error ? error.message : "failed to queue command";
19383
+ return c.json({ error: message }, 503);
19384
+ }
21795
19385
  return c.json({ ok: true });
21796
19386
  });
21797
19387
  app.post("/api/agents/:agentId/interrupt", async (c) => {
@@ -21887,6 +19477,254 @@ async function createOpenScoutWebServer(options) {
21887
19477
  return { app, warmupCaches };
21888
19478
  }
21889
19479
 
19480
+ // packages/web/server/relay.ts
19481
+ import { mkdir as mkdir7 } from "fs/promises";
19482
+ import { join as join19 } from "path";
19483
+ import { randomUUID as randomUUID3 } from "crypto";
19484
+ var UPLOAD_DIR = "/tmp/scout-uploads";
19485
+ async function handleRelayUpload(req) {
19486
+ const { name, data } = await req.json();
19487
+ if (!name || !data) {
19488
+ return Response.json({ error: "Missing name or data" }, { status: 400 });
19489
+ }
19490
+ await mkdir7(UPLOAD_DIR, { recursive: true });
19491
+ const filename = `${randomUUID3()}-${name}`;
19492
+ const filepath = join19(UPLOAD_DIR, filename);
19493
+ await Bun.write(filepath, Buffer.from(data, "base64"));
19494
+ return Response.json({ path: filepath });
19495
+ }
19496
+ function flushPending(ws) {
19497
+ const upstream = ws.data.upstream;
19498
+ if (!upstream || upstream.readyState !== WebSocket.OPEN) {
19499
+ return;
19500
+ }
19501
+ for (const payload of ws.data.pending) {
19502
+ upstream.send(payload);
19503
+ }
19504
+ ws.data.pending.length = 0;
19505
+ }
19506
+ function forwardToClient(ws, data) {
19507
+ if (ws.readyState !== WebSocket.OPEN) {
19508
+ return;
19509
+ }
19510
+ if (typeof data === "string") {
19511
+ ws.send(data);
19512
+ return;
19513
+ }
19514
+ if (data instanceof Blob) {
19515
+ data.arrayBuffer().then((buffer) => {
19516
+ if (ws.readyState === WebSocket.OPEN) {
19517
+ ws.send(buffer);
19518
+ }
19519
+ }).catch(() => {});
19520
+ return;
19521
+ }
19522
+ ws.send(data);
19523
+ }
19524
+ function createRelayWebSocketProxy(targetUrl) {
19525
+ return {
19526
+ open(ws) {
19527
+ ws.data.pending = [];
19528
+ const upstream = new WebSocket(targetUrl);
19529
+ upstream.binaryType = "arraybuffer";
19530
+ ws.data.upstream = upstream;
19531
+ upstream.onopen = () => {
19532
+ flushPending(ws);
19533
+ };
19534
+ upstream.onmessage = (event) => {
19535
+ forwardToClient(ws, event.data);
19536
+ };
19537
+ upstream.onerror = () => {
19538
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
19539
+ ws.close();
19540
+ }
19541
+ };
19542
+ upstream.onclose = () => {
19543
+ ws.data.upstream = null;
19544
+ ws.data.pending.length = 0;
19545
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
19546
+ ws.close();
19547
+ }
19548
+ };
19549
+ },
19550
+ message(ws, raw2) {
19551
+ const upstream = ws.data.upstream;
19552
+ const payload = raw2;
19553
+ if (!upstream || upstream.readyState === WebSocket.CONNECTING) {
19554
+ ws.data.pending.push(payload);
19555
+ return;
19556
+ }
19557
+ if (upstream.readyState === WebSocket.OPEN) {
19558
+ upstream.send(payload);
19559
+ }
19560
+ },
19561
+ close(ws) {
19562
+ const upstream = ws.data.upstream;
19563
+ ws.data.upstream = null;
19564
+ ws.data.pending.length = 0;
19565
+ if (!upstream) {
19566
+ return;
19567
+ }
19568
+ if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
19569
+ upstream.close();
19570
+ }
19571
+ }
19572
+ };
19573
+ }
19574
+
19575
+ // packages/web/server/managed-terminal-relay.ts
19576
+ import { existsSync as existsSync14 } from "fs";
19577
+ import { spawn as spawn5, spawnSync as spawnSync2 } from "child_process";
19578
+ import { dirname as dirname13, resolve as resolve10 } from "path";
19579
+ import { fileURLToPath as fileURLToPath8 } from "url";
19580
+ function relayPortForWebPort(webPort) {
19581
+ const configured = process.env.OPENSCOUT_WEB_TERMINAL_RELAY_PORT?.trim();
19582
+ if (configured) {
19583
+ const parsed = Number.parseInt(configured, 10);
19584
+ if (Number.isFinite(parsed) && parsed > 0) {
19585
+ return parsed;
19586
+ }
19587
+ }
19588
+ return webPort + 1;
19589
+ }
19590
+ function relayHostForWebHost(hostname2) {
19591
+ return process.env.OPENSCOUT_WEB_TERMINAL_RELAY_HOST?.trim() || hostname2;
19592
+ }
19593
+ function relayLoopbackHost(hostname2) {
19594
+ if (hostname2 === "0.0.0.0" || hostname2 === "::") {
19595
+ return "127.0.0.1";
19596
+ }
19597
+ return hostname2;
19598
+ }
19599
+ async function isHealthy(targetHttpUrl) {
19600
+ try {
19601
+ const response = await fetch(`${targetHttpUrl}/health`, {
19602
+ signal: AbortSignal.timeout(1000)
19603
+ });
19604
+ return response.ok;
19605
+ } catch {
19606
+ return false;
19607
+ }
19608
+ }
19609
+ async function waitForHealthy(targetHttpUrl, timeoutMs) {
19610
+ const deadline = Date.now() + timeoutMs;
19611
+ while (Date.now() < deadline) {
19612
+ if (await isHealthy(targetHttpUrl)) {
19613
+ return true;
19614
+ }
19615
+ await new Promise((resolveTimer) => setTimeout(resolveTimer, 100));
19616
+ }
19617
+ return false;
19618
+ }
19619
+ function resolveRuntimeEntry() {
19620
+ const selfDir = dirname13(fileURLToPath8(import.meta.url));
19621
+ const sourceEntry = resolve10(selfDir, "terminal-relay-node.ts");
19622
+ const bundledEntry = resolve10(selfDir, "openscout-terminal-relay.mjs");
19623
+ const sourceBundle = resolve10(selfDir, "../dist/openscout-terminal-relay.mjs");
19624
+ if (existsSync14(sourceEntry)) {
19625
+ const build = spawnSync2("bun", [
19626
+ "build",
19627
+ sourceEntry,
19628
+ "--target=node",
19629
+ "--format=esm",
19630
+ "--outfile",
19631
+ sourceBundle,
19632
+ "--external",
19633
+ "node-pty"
19634
+ ], {
19635
+ cwd: resolve10(selfDir, ".."),
19636
+ stdio: "inherit"
19637
+ });
19638
+ if ((build.status ?? 1) !== 0) {
19639
+ throw new Error("Failed to build terminal relay bundle");
19640
+ }
19641
+ return sourceBundle;
19642
+ }
19643
+ if (existsSync14(bundledEntry)) {
19644
+ return bundledEntry;
19645
+ }
19646
+ throw new Error("Could not locate the terminal relay runtime entry");
19647
+ }
19648
+ async function startManagedTerminalRelay(args) {
19649
+ const relayPort = relayPortForWebPort(args.webPort);
19650
+ const bindHost = relayHostForWebHost(args.hostname);
19651
+ const loopbackHost = relayLoopbackHost(bindHost);
19652
+ const targetHttpUrl = `http://${loopbackHost}:${relayPort}`;
19653
+ const targetWebSocketUrl = `ws://${loopbackHost}:${relayPort}`;
19654
+ if (await isHealthy(targetHttpUrl)) {
19655
+ return {
19656
+ healthcheck: () => isHealthy(targetHttpUrl),
19657
+ queueCommand: async (command) => {
19658
+ const response = await fetch(`${targetHttpUrl}/api/terminal/run`, {
19659
+ method: "POST",
19660
+ headers: { "Content-Type": "application/json" },
19661
+ body: JSON.stringify({ command })
19662
+ });
19663
+ if (!response.ok) {
19664
+ throw new Error("Terminal relay rejected queued command");
19665
+ }
19666
+ },
19667
+ shutdown() {},
19668
+ targetHttpUrl,
19669
+ targetWebSocketUrl
19670
+ };
19671
+ }
19672
+ const runtimeEntry = resolveRuntimeEntry();
19673
+ const child = spawn5("node", [runtimeEntry], {
19674
+ cwd: resolve10(dirname13(runtimeEntry), ".."),
19675
+ env: {
19676
+ ...process.env,
19677
+ OPENSCOUT_WEB_TERMINAL_RELAY_HOST: bindHost,
19678
+ OPENSCOUT_WEB_TERMINAL_RELAY_PORT: String(relayPort)
19679
+ },
19680
+ stdio: "inherit"
19681
+ });
19682
+ let spawnError = null;
19683
+ child.once("error", (error) => {
19684
+ spawnError = error;
19685
+ });
19686
+ child.on("exit", (code, signal) => {
19687
+ if (signal === "SIGTERM" || signal === "SIGINT") {
19688
+ return;
19689
+ }
19690
+ console.error(`[relay] Managed terminal relay exited unexpectedly (code: ${code ?? "null"}, signal: ${signal ?? "none"})`);
19691
+ });
19692
+ const ready = await waitForHealthy(targetHttpUrl, 5000);
19693
+ if (!ready) {
19694
+ if (!child.killed) {
19695
+ child.kill("SIGTERM");
19696
+ }
19697
+ if (spawnError) {
19698
+ throw spawnError;
19699
+ }
19700
+ throw new Error(`Terminal relay did not become healthy at ${targetHttpUrl}`);
19701
+ }
19702
+ return {
19703
+ healthcheck: () => isHealthy(targetHttpUrl),
19704
+ queueCommand: async (command) => {
19705
+ const response = await fetch(`${targetHttpUrl}/api/terminal/run`, {
19706
+ method: "POST",
19707
+ headers: { "Content-Type": "application/json" },
19708
+ body: JSON.stringify({ command })
19709
+ });
19710
+ if (!response.ok) {
19711
+ throw new Error("Terminal relay rejected queued command");
19712
+ }
19713
+ },
19714
+ shutdown() {
19715
+ terminateChild(child);
19716
+ },
19717
+ targetHttpUrl,
19718
+ targetWebSocketUrl
19719
+ };
19720
+ }
19721
+ function terminateChild(child) {
19722
+ if (child.killed) {
19723
+ return;
19724
+ }
19725
+ child.kill("SIGTERM");
19726
+ }
19727
+
21890
19728
  // packages/web/server/index.ts
21891
19729
  var port = Number(process.env.OPENSCOUT_WEB_PORT ?? process.env.SCOUT_WEB_PORT ?? "3200");
21892
19730
  var hostname2 = process.env.OPENSCOUT_WEB_HOST?.trim() || process.env.SCOUT_WEB_HOST?.trim() || "127.0.0.1";
@@ -21896,13 +19734,13 @@ function resolveStaticRoot2() {
21896
19734
  if (process.env.OPENSCOUT_WEB_STATIC_ROOT?.trim()) {
21897
19735
  return process.env.OPENSCOUT_WEB_STATIC_ROOT.trim();
21898
19736
  }
21899
- const selfDir = dirname13(fileURLToPath8(import.meta.url));
21900
- const siblingClientRoot = join21(selfDir, "client");
21901
- if (existsSync15(join21(siblingClientRoot, "index.html"))) {
19737
+ const selfDir = dirname14(fileURLToPath9(import.meta.url));
19738
+ const siblingClientRoot = join20(selfDir, "client");
19739
+ if (existsSync15(join20(siblingClientRoot, "index.html"))) {
21902
19740
  return siblingClientRoot;
21903
19741
  }
21904
- const sourceDistClientRoot = resolve10(selfDir, "../dist/client");
21905
- if (existsSync15(join21(sourceDistClientRoot, "index.html"))) {
19742
+ const sourceDistClientRoot = resolve11(selfDir, "../dist/client");
19743
+ if (existsSync15(join20(sourceDistClientRoot, "index.html"))) {
21906
19744
  return sourceDistClientRoot;
21907
19745
  }
21908
19746
  return;
@@ -21911,26 +19749,47 @@ var staticRoot = resolveStaticRoot2();
21911
19749
  var viteDevUrl = process.env.OPENSCOUT_WEB_VITE_URL?.trim() || undefined;
21912
19750
  var useViteProxy = Boolean(viteDevUrl) || !staticRoot;
21913
19751
  var idleTimeoutSeconds = Number.parseInt(process.env.OPENSCOUT_WEB_IDLE_TIMEOUT_SECONDS?.trim() || (useViteProxy ? "180" : "30"), 10);
19752
+ var terminalRelay = null;
19753
+ try {
19754
+ terminalRelay = await startManagedTerminalRelay({
19755
+ hostname: hostname2,
19756
+ webPort: port
19757
+ });
19758
+ } catch (error) {
19759
+ const message = error instanceof Error ? error.message : String(error);
19760
+ console.warn(`[scout] Terminal relay unavailable: ${message}`);
19761
+ }
21914
19762
  var { app, warmupCaches } = await createOpenScoutWebServer({
21915
19763
  currentDirectory,
21916
19764
  shellStateCacheTtlMs,
21917
19765
  assetMode: useViteProxy ? "vite-proxy" : "static",
21918
19766
  viteDevUrl,
21919
- staticRoot
19767
+ staticRoot,
19768
+ runTerminalCommand: terminalRelay?.queueCommand
21920
19769
  });
21921
19770
  var honoFetch = app.fetch;
19771
+ var relayWebSocket = terminalRelay ? createRelayWebSocketProxy(terminalRelay.targetWebSocketUrl) : {
19772
+ open(ws) {
19773
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
19774
+ ws.close(1013, "Terminal relay unavailable");
19775
+ }
19776
+ },
19777
+ message() {},
19778
+ close() {}
19779
+ };
21922
19780
  var server = Bun.serve({
21923
19781
  port,
21924
19782
  hostname: hostname2,
21925
19783
  idleTimeout: idleTimeoutSeconds,
21926
- fetch(req, server2) {
19784
+ async fetch(req, server2) {
21927
19785
  const url = new URL(req.url);
21928
19786
  if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
21929
- const ok = server2.upgrade(req, { data: { sessionId: null } });
19787
+ const ok = server2.upgrade(req, { data: { upstream: null, pending: [] } });
21930
19788
  return ok ? undefined : new Response("WebSocket upgrade failed", { status: 500 });
21931
19789
  }
21932
19790
  if (req.method === "GET" && url.pathname === "/health") {
21933
- return Response.json({ ok: true });
19791
+ const relayOk = terminalRelay ? await terminalRelay.healthcheck() : false;
19792
+ return Response.json({ ok: relayOk, relay: relayOk ? "up" : "down" }, { status: relayOk ? 200 : 503 });
21934
19793
  }
21935
19794
  if (req.method === "POST" && (url.pathname === "/api/upload" || url.pathname === "/api/relay/upload")) {
21936
19795
  return handleRelayUpload(req);
@@ -21941,8 +19800,8 @@ var server = Bun.serve({
21941
19800
  });
21942
19801
  var shutdown = () => {
21943
19802
  console.log(`
21944
- [scout] Shutting down relay sessions...`);
21945
- destroyAllRelaySessions();
19803
+ [scout] Shutting down terminal relay...`);
19804
+ terminalRelay?.shutdown();
21946
19805
  server.stop();
21947
19806
  process.exit(0);
21948
19807
  };