@openscout/scout 0.2.68 → 0.2.69

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.
@@ -7996,6 +7996,17 @@ function assertValidUnblockRequestEvent(event, record) {
7996
7996
  throw new Error(errors.join("; "));
7997
7997
  }
7998
7998
  }
7999
+ // ../protocol/src/lifecycle.ts
8000
+ var TERMINAL_INVOCATION_STATES;
8001
+ var init_lifecycle = __esm(() => {
8002
+ TERMINAL_INVOCATION_STATES = new Set([
8003
+ "completed",
8004
+ "failed",
8005
+ "cancelled",
8006
+ "expired"
8007
+ ]);
8008
+ });
8009
+
7999
8010
  // ../protocol/src/permission-policy.ts
8000
8011
  function normalizeScoutPermissionProfile(value) {
8001
8012
  const normalized = value?.trim().replaceAll("-", "_");
@@ -8048,15 +8059,20 @@ var init_scout_composer = __esm(() => {
8048
8059
  });
8049
8060
  // ../protocol/src/inbox.ts
8050
8061
  var init_inbox = () => {};
8062
+ // ../protocol/src/cursor-transport.ts
8063
+ var init_cursor_transport = () => {};
8064
+
8051
8065
  // ../protocol/src/index.ts
8052
8066
  var init_src2 = __esm(() => {
8053
8067
  init_agent_identity();
8054
8068
  init_agent_address();
8055
8069
  init_agent_selectors();
8070
+ init_lifecycle();
8056
8071
  init_agent_runs();
8057
8072
  init_scout_composer();
8058
8073
  init_inbox();
8059
8074
  init_permission_policy();
8075
+ init_cursor_transport();
8060
8076
  });
8061
8077
 
8062
8078
  // ../runtime/src/user-project-hints.ts
@@ -10231,6 +10247,9 @@ var init_setup = __esm(() => {
10231
10247
  };
10232
10248
  });
10233
10249
 
10250
+ // ../runtime/src/dispatch-stalled.ts
10251
+ var init_dispatch_stalled = () => {};
10252
+
10234
10253
  // ../runtime/src/managed-agent-environment.ts
10235
10254
  function managedAgentEnvironmentEntries(options) {
10236
10255
  const agentName = options.agentName.trim();
@@ -11023,6 +11042,15 @@ function readCodexAppServerReasoningEffortFromLaunchArgs(launchArgs) {
11023
11042
  }
11024
11043
  return null;
11025
11044
  }
11045
+ function codexAppServerExitMessage(input) {
11046
+ if (input.exitKind === "proactive_shutdown") {
11047
+ return `Codex app-server session for ${input.agentName} was stopped by OpenScout` + (input.reason ? `: ${input.reason}` : "") + ".";
11048
+ }
11049
+ if (input.exitKind === "external_sigterm") {
11050
+ return `Codex app-server for ${input.agentName} was interrupted by SIGTERM.`;
11051
+ }
11052
+ return `Codex app-server exited for ${input.agentName}` + (input.exitCode !== null ? ` with code ${input.exitCode}` : "") + (input.signal ? ` (${input.signal})` : "");
11053
+ }
11026
11054
  function resolveRequesterTimeoutMs2(timeoutMs) {
11027
11055
  if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
11028
11056
  return timeoutMs;
@@ -11194,6 +11222,7 @@ class CodexAppServerSession {
11194
11222
  activeTurn = null;
11195
11223
  threadId = null;
11196
11224
  threadPath = null;
11225
+ proactiveShutdown = null;
11197
11226
  lastConfigSignature;
11198
11227
  constructor(options) {
11199
11228
  this.options = options;
@@ -11324,13 +11353,23 @@ class CodexAppServerSession {
11324
11353
  });
11325
11354
  }
11326
11355
  async shutdown(options = {}) {
11356
+ this.proactiveShutdown = {
11357
+ reason: options.reason ?? (options.resetThread ? "OpenScout reset the app-server session" : "OpenScout stopped the app-server session")
11358
+ };
11359
+ const stoppedError = new CodexAppServerExitError({
11360
+ agentName: this.options.agentName,
11361
+ exitKind: "proactive_shutdown",
11362
+ exitCode: null,
11363
+ signal: null,
11364
+ reason: this.proactiveShutdown.reason
11365
+ });
11327
11366
  const activeTurn = this.activeTurn;
11328
11367
  this.activeTurn = null;
11329
11368
  if (activeTurn) {
11330
- activeTurn.reject(new Error(`Codex app-server session for ${this.options.agentName} was shut down.`));
11369
+ activeTurn.reject(stoppedError);
11331
11370
  }
11332
11371
  for (const pending of this.pendingRequests.values()) {
11333
- pending.reject(new Error(`Codex app-server session for ${this.options.agentName} was shut down.`));
11372
+ pending.reject(stoppedError);
11334
11373
  }
11335
11374
  this.pendingRequests.clear();
11336
11375
  const child = this.process;
@@ -11340,7 +11379,7 @@ class CodexAppServerSession {
11340
11379
  if (child && child.exitCode === null && !child.killed) {
11341
11380
  child.kill("SIGTERM");
11342
11381
  await new Promise((resolve5) => setTimeout(resolve5, 250));
11343
- if (child.exitCode === null && !child.killed) {
11382
+ if (child.exitCode === null && child.signalCode === null) {
11344
11383
  child.kill("SIGKILL");
11345
11384
  }
11346
11385
  }
@@ -11391,6 +11430,7 @@ class CodexAppServerSession {
11391
11430
  await mkdir5(this.options.runtimeDirectory, { recursive: true });
11392
11431
  await mkdir5(this.options.logsDirectory, { recursive: true });
11393
11432
  await writeFile5(join17(this.options.runtimeDirectory, "prompt.txt"), this.options.systemPrompt);
11433
+ this.proactiveShutdown = null;
11394
11434
  const codexExecutable = resolveCodexExecutable();
11395
11435
  const launchArgs = normalizeCodexAppServerLaunchArgs(this.options.launchArgs);
11396
11436
  const env = buildManagedAgentEnvironment({
@@ -11430,7 +11470,16 @@ class CodexAppServerSession {
11430
11470
  this.failSession(new Error(`Codex app-server failed for ${this.options.agentName}: ${errorMessage3(error)}`));
11431
11471
  });
11432
11472
  child.once("exit", (code, signal) => {
11433
- this.failSession(new Error(`Codex app-server exited for ${this.options.agentName}` + (code !== null ? ` with code ${code}` : "") + (signal ? ` (${signal})` : "")));
11473
+ const proactiveShutdown = this.proactiveShutdown;
11474
+ if (proactiveShutdown) {
11475
+ this.handleProactiveProcessExit(child, proactiveShutdown, code, signal);
11476
+ return;
11477
+ }
11478
+ this.failSession(new CodexAppServerExitError({
11479
+ agentName: this.options.agentName,
11480
+ exitCode: code,
11481
+ signal
11482
+ }));
11434
11483
  });
11435
11484
  await this.request("initialize", {
11436
11485
  clientInfo: {
@@ -11765,6 +11814,23 @@ class CodexAppServerSession {
11765
11814
  }
11766
11815
  this.pendingRequests.clear();
11767
11816
  appendFile3(this.stderrLogPath, `[openscout] ${error.message}
11817
+ `).catch(() => {
11818
+ return;
11819
+ });
11820
+ this.persistState();
11821
+ }
11822
+ handleProactiveProcessExit(child, shutdown, code, signal) {
11823
+ if (this.process === child) {
11824
+ this.process = null;
11825
+ }
11826
+ this.proactiveShutdown = null;
11827
+ this.starting = null;
11828
+ const exitDetail = [
11829
+ code !== null ? `code ${code}` : null,
11830
+ signal
11831
+ ].filter(Boolean).join(", ");
11832
+ const detail = exitDetail ? ` (${exitDetail})` : "";
11833
+ appendFile3(this.stderrLogPath, `[openscout] Codex app-server stopped for ${this.options.agentName}: ${shutdown.reason}${detail}
11768
11834
  `).catch(() => {
11769
11835
  return;
11770
11836
  });
@@ -11835,7 +11901,10 @@ function getOrCreateSession2(options) {
11835
11901
  if (existing.matches(options)) {
11836
11902
  return existing;
11837
11903
  }
11838
- existing.shutdown({ resetThread: true });
11904
+ existing.shutdown({
11905
+ resetThread: true,
11906
+ reason: "OpenScout replaced the app-server session after its launch options changed"
11907
+ });
11839
11908
  sessions2.delete(key);
11840
11909
  }
11841
11910
  const session = new CodexAppServerSession(options);
@@ -11866,13 +11935,36 @@ async function shutdownCodexAppServerAgent(options, shutdownOptions = {}) {
11866
11935
  sessions2.delete(key);
11867
11936
  await session.shutdown(shutdownOptions);
11868
11937
  }
11869
- var SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
11938
+ var CodexAppServerExitError, SESSION_CATALOG_FILENAME2 = "session-catalog.json", SESSION_CATALOG_MAX_ENTRIES2 = 64, CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS, sessions2;
11870
11939
  var init_codex_app_server = __esm(() => {
11871
11940
  init_src();
11872
11941
  init_topology();
11873
11942
  init_codex_executable();
11874
11943
  init_requester_timeout();
11875
11944
  init_codex_executable();
11945
+ CodexAppServerExitError = class CodexAppServerExitError extends Error {
11946
+ code = "CODEX_APP_SERVER_EXIT";
11947
+ exitKind;
11948
+ agentName;
11949
+ exitCode;
11950
+ signal;
11951
+ reason;
11952
+ noteworthy;
11953
+ constructor(input) {
11954
+ const exitKind = input.exitKind ?? (input.signal === "SIGTERM" ? "external_sigterm" : "unexpected_exit");
11955
+ super(codexAppServerExitMessage({
11956
+ ...input,
11957
+ exitKind
11958
+ }));
11959
+ this.name = "CodexAppServerExitError";
11960
+ this.exitKind = exitKind;
11961
+ this.agentName = input.agentName;
11962
+ this.exitCode = input.exitCode;
11963
+ this.signal = input.signal;
11964
+ this.reason = input.reason ?? null;
11965
+ this.noteworthy = exitKind !== "unexpected_exit";
11966
+ }
11967
+ };
11876
11968
  CODEX_ROLLOUT_STALE_ACTIVE_TURN_MS = 10 * 60 * 1000;
11877
11969
  sessions2 = new Map;
11878
11970
  });
@@ -12499,6 +12591,17 @@ async function fetchHealthSnapshot(config) {
12499
12591
  nodes: payload.counts.nodes ?? 0,
12500
12592
  actors: payload.counts.actors ?? 0,
12501
12593
  agents: payload.counts.agents ?? 0,
12594
+ agentRecords: payload.counts.agentRecords,
12595
+ rawAgentRecords: payload.counts.rawAgentRecords,
12596
+ configuredAgents: payload.counts.configuredAgents,
12597
+ scoutManagedAgents: payload.counts.scoutManagedAgents,
12598
+ currentAgentRegistrations: payload.counts.currentAgentRegistrations,
12599
+ localAgentRegistrations: payload.counts.localAgentRegistrations,
12600
+ remoteAgentRegistrations: payload.counts.remoteAgentRegistrations,
12601
+ staleAgentRegistrations: payload.counts.staleAgentRegistrations,
12602
+ retiredAgentRegistrations: payload.counts.retiredAgentRegistrations,
12603
+ oneTimeAgentCards: payload.counts.oneTimeAgentCards,
12604
+ persistentAgentCards: payload.counts.persistentAgentCards,
12502
12605
  conversations: payload.counts.conversations ?? 0,
12503
12606
  messages: payload.counts.messages ?? 0,
12504
12607
  flights: payload.counts.flights ?? 0,
@@ -12736,11 +12839,37 @@ var init_permission_policy2 = __esm(() => {
12736
12839
  init_src2();
12737
12840
  });
12738
12841
 
12842
+ // ../runtime/src/user-config.ts
12843
+ import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
12844
+ import { homedir as homedir18 } from "os";
12845
+ import { dirname as dirname9, join as join19 } from "path";
12846
+ function userConfigPath() {
12847
+ return join19(process.env.OPENSCOUT_HOME ?? join19(homedir18(), ".openscout"), "user.json");
12848
+ }
12849
+ function loadUserConfig() {
12850
+ const configPath = userConfigPath();
12851
+ if (!existsSync16(configPath))
12852
+ return {};
12853
+ try {
12854
+ return JSON.parse(readFileSync10(configPath, "utf8"));
12855
+ } catch {
12856
+ return {};
12857
+ }
12858
+ }
12859
+ function normalizeHandle(value) {
12860
+ return value?.trim().replace(/^@+/, "") ?? "";
12861
+ }
12862
+ function resolveOperatorHandle() {
12863
+ const config = loadUserConfig();
12864
+ return normalizeHandle(config.handle) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_HANDLE) || normalizeHandle(config.name) || normalizeHandle(process.env.OPENSCOUT_OPERATOR_NAME) || normalizeHandle(process.env.USER) || "operator";
12865
+ }
12866
+ var init_user_config = () => {};
12867
+
12739
12868
  // ../runtime/src/local-agents.ts
12740
12869
  import { execFileSync as execFileSync4, execSync as execSync2 } from "child_process";
12741
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
12870
+ import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
12742
12871
  import { mkdir as mkdir6, rm as rm5, stat as stat3, writeFile as writeFile6 } from "fs/promises";
12743
- import { basename as basename8, dirname as dirname9, join as join19, resolve as resolve6 } from "path";
12872
+ import { basename as basename8, dirname as dirname10, join as join20, resolve as resolve6 } from "path";
12744
12873
  import { fileURLToPath as fileURLToPath5 } from "url";
12745
12874
  function resolveRelayHub() {
12746
12875
  return resolveOpenScoutSupportPaths().relayHubDirectory;
@@ -12751,14 +12880,14 @@ function resolveProjectsRoot(projectPath) {
12751
12880
  }
12752
12881
  try {
12753
12882
  const supportPaths = resolveOpenScoutSupportPaths();
12754
- if (!existsSync16(supportPaths.settingsPath)) {
12755
- return dirname9(projectPath);
12883
+ if (!existsSync17(supportPaths.settingsPath)) {
12884
+ return dirname10(projectPath);
12756
12885
  }
12757
- const raw = JSON.parse(readFileSync10(supportPaths.settingsPath, "utf8"));
12886
+ const raw = JSON.parse(readFileSync11(supportPaths.settingsPath, "utf8"));
12758
12887
  const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
12759
- return workspaceRoot ? resolve6(workspaceRoot) : dirname9(projectPath);
12888
+ return workspaceRoot ? resolve6(workspaceRoot) : dirname10(projectPath);
12760
12889
  } catch {
12761
- return dirname9(projectPath);
12890
+ return dirname10(projectPath);
12762
12891
  }
12763
12892
  }
12764
12893
  function resolveBrokerUrl() {
@@ -12768,7 +12897,7 @@ function nowSeconds2() {
12768
12897
  return Math.floor(Date.now() / 1000);
12769
12898
  }
12770
12899
  function scoutCliPath() {
12771
- return join19(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
12900
+ return join20(OPENSCOUT_REPO_ROOT, "packages", "cli", "bin", "scout.mjs");
12772
12901
  }
12773
12902
  function legacyNodeBrokerRelayCommand() {
12774
12903
  return `node ${JSON.stringify(scoutCliPath())}`;
@@ -12781,13 +12910,13 @@ function titleCaseLocalAgentName(value) {
12781
12910
  }
12782
12911
  function resolveScoutSkillPath() {
12783
12912
  const candidatePaths = [
12784
- join19(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
12785
- join19(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
12786
- join19(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
12787
- join19(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
12913
+ join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "scout", "SKILL.md"),
12914
+ join20(process.env.HOME ?? "", ".agents", "skills", "scout", "SKILL.md"),
12915
+ join20(OPENSCOUT_REPO_ROOT, ".agents", "skills", "relay-agent-comms", "SKILL.md"),
12916
+ join20(process.env.HOME ?? "", ".agents", "skills", "relay-agent-comms", "SKILL.md")
12788
12917
  ];
12789
12918
  for (const path2 of candidatePaths) {
12790
- if (existsSync16(path2)) {
12919
+ if (existsSync17(path2)) {
12791
12920
  return path2;
12792
12921
  }
12793
12922
  }
@@ -12895,6 +13024,74 @@ function buildLocalAgentSystemPromptTemplate() {
12895
13024
  ].join(`
12896
13025
  `);
12897
13026
  }
13027
+ function normalizeOperatorHandleSegment(value) {
13028
+ return normalizeAgentSelectorSegment(value?.trim().replace(/^@+/, "") ?? "") || "operator";
13029
+ }
13030
+ function resolveOperatorAugmentAgentName() {
13031
+ return `${normalizeOperatorHandleSegment(resolveOperatorHandle())}-ai`;
13032
+ }
13033
+ function operatorAugmentAgentNameCandidates() {
13034
+ return new Set([
13035
+ resolveOperatorAugmentAgentName(),
13036
+ "operator-ai"
13037
+ ]);
13038
+ }
13039
+ function buildOperatorAugmentSystemPromptTemplate(input = {}) {
13040
+ const operatorHandle = normalizeOperatorHandleSegment(input.operatorHandle ?? resolveOperatorHandle());
13041
+ const augmentHandle = normalizeOperatorHandleSegment(input.augmentHandle ?? `${operatorHandle}-ai`);
13042
+ const humanLabel = `@${operatorHandle}`;
13043
+ const augmentLabel = `@${augmentHandle}`;
13044
+ return [
13045
+ "{{base_prompt}}",
13046
+ "",
13047
+ `You are the augmented counterpart to the human Scout operator ${humanLabel}.`,
13048
+ `${humanLabel} is the human. ${augmentLabel} is the AI-augmented looper with a human in the loop.`,
13049
+ `Do not impersonate ${humanLabel}. Speak and act as ${augmentLabel}, and involve ${humanLabel} only when human judgment or approval is the real next dependency.`,
13050
+ "",
13051
+ "Operating loop:",
13052
+ " - Keep long-running conversations in the same DM or invocation thread whenever possible.",
13053
+ " - Maintain continuity across turns: track the goal, decisions made, open questions, blockers, and promised follow-ups.",
13054
+ " - For efforts that span many turns, many files, or more than one working session, create or update a durable note/checkpoint when you have write access, then point to it briefly.",
13055
+ " - Prefer continuing from a concise recap over resetting context. If context is aging, summarize the useful state and keep moving.",
13056
+ ` - Be explicit about the next responsible owner: ${augmentLabel}, ${humanLabel}, or another named agent.`,
13057
+ "",
13058
+ `When to invoke ${humanLabel}:`,
13059
+ " - Approval is needed for destructive, irreversible, public, financial, security-sensitive, credential, privacy, or cross-project priority decisions.",
13060
+ ` - The task depends on taste, intent, product direction, personal context, or a choice only ${humanLabel} can make.`,
13061
+ " - You are blocked after a reasonable local attempt and the unblock request can be stated as a short concrete question.",
13062
+ " - A result is materially uncertain and acting without human input could waste meaningful time or create cleanup work.",
13063
+ " - You need permission to interrupt, wake, or redirect other people or agents outside the current work venue.",
13064
+ "",
13065
+ `How to invoke ${humanLabel}:`,
13066
+ " - Keep the ask concise: context, the decision needed, the default you recommend, and the consequence of no answer.",
13067
+ " - Use the same DM/thread when it exists. Do not broadcast human-loop requests.",
13068
+ ` - If a reply is required before work can proceed, say that ${humanLabel} owns the next move and stop claiming progress.`,
13069
+ " - If work can continue safely, state the assumption and continue without waiting.",
13070
+ "",
13071
+ `Do not invoke ${humanLabel} for:`,
13072
+ " - Routine status updates, obvious implementation details, command outputs, local orientation, or reversible low-risk cleanup.",
13073
+ " - Ambiguity that you can resolve by reading the repo, checking Scout state, or asking the correct specialist agent.",
13074
+ "",
13075
+ "{{project_context}}",
13076
+ "",
13077
+ "{{collaboration_prompt}}",
13078
+ "",
13079
+ "{{protocol_prompt}}"
13080
+ ].join(`
13081
+ `);
13082
+ }
13083
+ function operatorAugmentDefaultsForDefinitionId(definitionId) {
13084
+ const normalizedDefinitionId = normalizeAgentSelectorSegment(definitionId);
13085
+ if (!normalizedDefinitionId || !operatorAugmentAgentNameCandidates().has(normalizedDefinitionId)) {
13086
+ return null;
13087
+ }
13088
+ return {
13089
+ displayName: titleCaseLocalAgentName(normalizedDefinitionId),
13090
+ systemPrompt: buildOperatorAugmentSystemPromptTemplate({
13091
+ augmentHandle: normalizedDefinitionId
13092
+ })
13093
+ };
13094
+ }
12898
13095
  function renderLocalAgentSystemPromptTemplate(template, context, options = {}) {
12899
13096
  const basePrompt = buildLocalAgentBasePrompt(context);
12900
13097
  const projectContext = buildLocalAgentProjectContextPrompt(context);
@@ -13049,13 +13246,20 @@ function expandHomePath4(value) {
13049
13246
  return process.env.HOME ?? process.cwd();
13050
13247
  }
13051
13248
  if (value.startsWith("~/")) {
13052
- return join19(process.env.HOME ?? process.cwd(), value.slice(2));
13249
+ return join20(process.env.HOME ?? process.cwd(), value.slice(2));
13053
13250
  }
13054
13251
  return value;
13055
13252
  }
13056
13253
  function normalizeProjectPath(value) {
13057
13254
  return resolve6(expandHomePath4(value.trim() || "."));
13058
13255
  }
13256
+ function compactHomePath(value) {
13257
+ const home = process.env.HOME;
13258
+ if (!home) {
13259
+ return value;
13260
+ }
13261
+ return value.startsWith(home) ? value.replace(home, "~") : value;
13262
+ }
13059
13263
  function normalizeTmuxSessionName2(value, agentId) {
13060
13264
  const fallback = `relay-${agentId}`;
13061
13265
  const trimmed = value?.trim() ?? "";
@@ -13309,6 +13513,21 @@ function applyRequestedRuntimeOptionsToLaunchArgs(harness, launchArgs, options)
13309
13513
  ...buildLaunchArgsForRequestedReasoningEffort(harness, requestedReasoningEffort)
13310
13514
  ];
13311
13515
  }
13516
+ function setLaunchModelForHarness(harness, launchArgs, model) {
13517
+ const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
13518
+ if (model === undefined) {
13519
+ return normalized;
13520
+ }
13521
+ const stripped = stripLaunchModelForHarness(harness, normalized);
13522
+ const requestedModel = normalizeRequestedModel(model ?? undefined);
13523
+ if (!requestedModel) {
13524
+ return stripped;
13525
+ }
13526
+ return [
13527
+ ...stripped,
13528
+ ...buildLaunchArgsForRequestedModel(harness, requestedModel)
13529
+ ];
13530
+ }
13312
13531
  function defaultHarnessForOverride(override, fallback = "claude") {
13313
13532
  return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
13314
13533
  }
@@ -13429,6 +13648,31 @@ function recordForHarness(record, harnessOverride) {
13429
13648
  permissionProfile: profile.permissionProfile
13430
13649
  };
13431
13650
  }
13651
+ function normalizeCardTimestamp(value) {
13652
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
13653
+ }
13654
+ function normalizeLocalAgentCardLifecycle(value, now = Date.now()) {
13655
+ if (!value) {
13656
+ return;
13657
+ }
13658
+ const kind = value.kind === "one_time" ? "one_time" : value.kind === "persistent" ? "persistent" : undefined;
13659
+ if (!kind) {
13660
+ return;
13661
+ }
13662
+ const createdAt = normalizeCardTimestamp(value.createdAt) ?? now;
13663
+ const expiresAt = normalizeCardTimestamp(value.expiresAt);
13664
+ const maxUses = typeof value.maxUses === "number" && Number.isFinite(value.maxUses) && value.maxUses > 0 ? Math.floor(value.maxUses) : undefined;
13665
+ const createdById = value.createdById?.trim();
13666
+ const inboxConversationId = value.inboxConversationId?.trim();
13667
+ return {
13668
+ kind,
13669
+ createdAt,
13670
+ ...createdById ? { createdById } : {},
13671
+ ...expiresAt ? { expiresAt } : {},
13672
+ ...maxUses ? { maxUses } : {},
13673
+ ...inboxConversationId ? { inboxConversationId } : {}
13674
+ };
13675
+ }
13432
13676
  function normalizeLocalAgentRecord(agentId, record) {
13433
13677
  const cwd = normalizeProjectPath(record.cwd || process.cwd());
13434
13678
  const projectRoot = normalizeProjectPath(record.projectRoot || cwd);
@@ -13458,7 +13702,8 @@ function normalizeLocalAgentRecord(agentId, record) {
13458
13702
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
13459
13703
  launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs),
13460
13704
  permissionProfile: activeProfile?.permissionProfile ?? record.permissionProfile,
13461
- registrationSource: record.registrationSource
13705
+ registrationSource: record.registrationSource,
13706
+ card: normalizeLocalAgentCardLifecycle(record.card)
13462
13707
  };
13463
13708
  }
13464
13709
  function localAgentStatusFromRecord(agentId, record, source) {
@@ -13512,7 +13757,8 @@ function localAgentRecordFromRelayAgentOverride(agentId, override) {
13512
13757
  harnessProfiles: override.harnessProfiles,
13513
13758
  transport: normalizeLocalAgentTransport(override.runtime?.transport, normalizeLocalAgentHarness(override.runtime?.harness)),
13514
13759
  capabilities: override.capabilities,
13515
- launchArgs: override.launchArgs
13760
+ launchArgs: override.launchArgs,
13761
+ card: override.card
13516
13762
  });
13517
13763
  }
13518
13764
  function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
@@ -13533,6 +13779,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
13533
13779
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
13534
13780
  defaultHarness: normalizeManagedHarness2(normalizedRecord.defaultHarness, "claude"),
13535
13781
  harnessProfiles: normalizedRecord.harnessProfiles,
13782
+ ...normalizedRecord.card ? { card: normalizedRecord.card } : {},
13536
13783
  runtime: {
13537
13784
  cwd: normalizeProjectPath(normalizedRecord.cwd),
13538
13785
  harness: normalizeLocalAgentHarness(normalizedRecord.harness),
@@ -13542,6 +13789,245 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
13542
13789
  }
13543
13790
  };
13544
13791
  }
13792
+ function buildLocalAgentConfigState(agentId, record) {
13793
+ const harness = normalizeLocalAgentHarness(record.harness);
13794
+ const launchArgs = normalizeLaunchArgsForHarness(harness, record.launchArgs);
13795
+ return {
13796
+ agentId,
13797
+ editable: true,
13798
+ model: readLaunchModelForHarness(harness, launchArgs) ?? null,
13799
+ permissionProfile: record.permissionProfile ?? null,
13800
+ systemPrompt: record.systemPrompt || buildLocalAgentSystemPromptTemplate(),
13801
+ runtime: {
13802
+ cwd: compactHomePath(record.cwd),
13803
+ harness,
13804
+ transport: normalizeLocalAgentTransport(record.transport, harness),
13805
+ sessionId: record.tmuxSession,
13806
+ wakePolicy: "on_demand"
13807
+ },
13808
+ launchArgs,
13809
+ capabilities: normalizeLocalAgentCapabilities(record.capabilities),
13810
+ applyMode: "restart",
13811
+ templateHint: LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT
13812
+ };
13813
+ }
13814
+ async function assertDirectoryExists(directory) {
13815
+ const stats = await stat3(directory);
13816
+ if (!stats.isDirectory()) {
13817
+ throw new Error(`${directory} is not a directory.`);
13818
+ }
13819
+ }
13820
+ async function resolveConfiguredLocalAgentRecord(agentId) {
13821
+ const overrides = await readRelayAgentOverrides();
13822
+ const override = overrides[agentId];
13823
+ if (override) {
13824
+ return localAgentRecordFromRelayAgentOverride(agentId, override);
13825
+ }
13826
+ const registry = await readLocalAgentRegistry();
13827
+ const record = registry[agentId];
13828
+ return record ? normalizeLocalAgentRecord(agentId, record) : null;
13829
+ }
13830
+ async function getLocalAgentConfig(agentId) {
13831
+ const record = await resolveConfiguredLocalAgentRecord(agentId);
13832
+ if (!record) {
13833
+ return null;
13834
+ }
13835
+ return buildLocalAgentConfigState(agentId, record);
13836
+ }
13837
+ function defaultLocalAgentSessionId(agentId, harness) {
13838
+ return normalizeTmuxSessionName2(undefined, `${agentId}-${harness}`);
13839
+ }
13840
+ async function updateLocalAgentCard(agentId, input) {
13841
+ const existing = await getLocalAgentConfig(agentId);
13842
+ if (!existing) {
13843
+ return null;
13844
+ }
13845
+ const nextHarness = input.harness ? normalizeLocalAgentHarness(input.harness) : existing.runtime.harness;
13846
+ const harnessChanged = nextHarness !== existing.runtime.harness;
13847
+ const model = input.model === undefined ? harnessChanged ? null : undefined : input.model;
13848
+ return updateLocalAgentConfig(agentId, {
13849
+ runtime: {
13850
+ cwd: existing.runtime.cwd,
13851
+ harness: nextHarness,
13852
+ transport: harnessChanged ? normalizeLocalAgentTransport(undefined, nextHarness) : existing.runtime.transport,
13853
+ sessionId: harnessChanged ? defaultLocalAgentSessionId(agentId, nextHarness) : existing.runtime.sessionId
13854
+ },
13855
+ systemPrompt: existing.systemPrompt,
13856
+ launchArgs: harnessChanged ? [] : existing.launchArgs,
13857
+ model,
13858
+ reasoningEffort: input.reasoningEffort,
13859
+ permissionProfile: input.permissionProfile,
13860
+ capabilities: existing.capabilities
13861
+ });
13862
+ }
13863
+ async function updateLocalAgentConfig(agentId, input) {
13864
+ const [registry, overrides] = await Promise.all([
13865
+ readLocalAgentRegistry(),
13866
+ readRelayAgentOverrides()
13867
+ ]);
13868
+ const record = registry[agentId] ?? (overrides[agentId] ? localAgentRecordFromRelayAgentOverride(agentId, overrides[agentId]) : undefined);
13869
+ if (!record) {
13870
+ return null;
13871
+ }
13872
+ const cwd = normalizeProjectPath(input.runtime.cwd || record.cwd);
13873
+ await assertDirectoryExists(cwd);
13874
+ const nextHarness = normalizeLocalAgentHarness(input.runtime.harness);
13875
+ const nextTransport = normalizeLocalAgentTransport(input.runtime.transport ?? record.transport, nextHarness);
13876
+ let nextLaunchArgs = setLaunchModelForHarness(nextHarness, input.launchArgs, input.model);
13877
+ if (input.reasoningEffort !== undefined) {
13878
+ const stripped = stripLaunchReasoningEffortForHarness(nextHarness, nextLaunchArgs);
13879
+ const requestedReasoningEffort = normalizeRequestedReasoningEffort(input.reasoningEffort ?? undefined);
13880
+ nextLaunchArgs = requestedReasoningEffort ? [
13881
+ ...stripped,
13882
+ ...buildLaunchArgsForRequestedReasoningEffort(nextHarness, requestedReasoningEffort)
13883
+ ] : stripped;
13884
+ }
13885
+ const nextPermissionProfile = input.permissionProfile === undefined ? record.permissionProfile : input.permissionProfile === null ? undefined : parseScoutPermissionProfile(input.permissionProfile);
13886
+ const nextRecord = normalizeLocalAgentRecord(agentId, {
13887
+ ...record,
13888
+ cwd,
13889
+ tmuxSession: input.runtime.sessionId,
13890
+ harness: nextHarness,
13891
+ defaultHarness: nextHarness,
13892
+ harnessProfiles: {
13893
+ ...record.harnessProfiles ?? {},
13894
+ [normalizeManagedHarness2(input.runtime.harness, "claude")]: {
13895
+ cwd,
13896
+ transport: nextTransport,
13897
+ sessionId: normalizeTmuxSessionName2(input.runtime.sessionId, `${agentId}-${normalizeManagedHarness2(input.runtime.harness, "claude")}`),
13898
+ launchArgs: nextLaunchArgs,
13899
+ ...nextPermissionProfile ? { permissionProfile: nextPermissionProfile } : {}
13900
+ }
13901
+ },
13902
+ transport: nextTransport,
13903
+ systemPrompt: input.systemPrompt.trim() || undefined,
13904
+ launchArgs: nextLaunchArgs,
13905
+ permissionProfile: nextPermissionProfile,
13906
+ capabilities: input.capabilities
13907
+ });
13908
+ registry[agentId] = nextRecord;
13909
+ await writeLocalAgentRegistry(registry);
13910
+ return buildLocalAgentConfigState(agentId, nextRecord);
13911
+ }
13912
+ async function updateLocalAgentCardLifecycle(agentId, input) {
13913
+ const overrides = await readRelayAgentOverrides();
13914
+ const override = overrides[agentId];
13915
+ if (!override) {
13916
+ return null;
13917
+ }
13918
+ const lifecycle2 = normalizeLocalAgentCardLifecycle({
13919
+ ...override.card ?? {},
13920
+ ...input
13921
+ });
13922
+ if (!lifecycle2) {
13923
+ return null;
13924
+ }
13925
+ overrides[agentId] = {
13926
+ ...override,
13927
+ card: lifecycle2
13928
+ };
13929
+ await writeRelayAgentOverrides(overrides);
13930
+ return lifecycle2;
13931
+ }
13932
+ function oneTimeCardCreatedAt(lifecycle2) {
13933
+ return normalizeCardTimestamp(lifecycle2.createdAt) ?? 0;
13934
+ }
13935
+ function shouldPruneOneTimeCard(lifecycle2, now, maxAgeMs) {
13936
+ if (lifecycle2.kind !== "one_time") {
13937
+ return false;
13938
+ }
13939
+ if (lifecycle2.expiresAt !== undefined && lifecycle2.expiresAt <= now) {
13940
+ return true;
13941
+ }
13942
+ const createdAt = oneTimeCardCreatedAt(lifecycle2);
13943
+ return createdAt > 0 && createdAt + maxAgeMs <= now;
13944
+ }
13945
+ function oneTimeCardMatchesScope(agentId, override, input) {
13946
+ if (BUILT_IN_AGENT_DEFINITION_IDS.has(agentId)) {
13947
+ return false;
13948
+ }
13949
+ const lifecycle2 = normalizeLocalAgentCardLifecycle(override.card);
13950
+ if (lifecycle2?.kind !== "one_time") {
13951
+ return false;
13952
+ }
13953
+ if (input.createdById?.trim() && lifecycle2.createdById !== input.createdById.trim()) {
13954
+ return false;
13955
+ }
13956
+ if (input.projectRoot?.trim()) {
13957
+ const scopedRoot = normalizeProjectPath(input.projectRoot);
13958
+ const cardRoot = normalizeProjectPath(override.projectRoot || override.runtime?.cwd || ".");
13959
+ if (cardRoot !== scopedRoot) {
13960
+ return false;
13961
+ }
13962
+ }
13963
+ return true;
13964
+ }
13965
+ async function retireLocalAgentOverrides(overrides, agentIds) {
13966
+ const retired = [];
13967
+ for (const agentId of agentIds) {
13968
+ const override = overrides[agentId];
13969
+ if (!override) {
13970
+ continue;
13971
+ }
13972
+ const record = localAgentRecordFromRelayAgentOverride(agentId, override);
13973
+ const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
13974
+ await stopLocalAgent(agentId).catch(() => null);
13975
+ retired.push({
13976
+ ...status,
13977
+ isOnline: false
13978
+ });
13979
+ delete overrides[agentId];
13980
+ }
13981
+ if (retired.length > 0) {
13982
+ await writeRelayAgentOverrides(overrides);
13983
+ }
13984
+ return retired;
13985
+ }
13986
+ async function pruneOneTimeLocalAgentCards(input = {}) {
13987
+ const now = input.now ?? Date.now();
13988
+ const maxAgeMs = Math.max(0, input.maxAgeMs ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS);
13989
+ const maxCount = Math.max(0, input.maxCount ?? DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN);
13990
+ const excluded = new Set(input.excludeAgentIds ?? []);
13991
+ const overrides = await readRelayAgentOverrides();
13992
+ const candidates = Object.entries(overrides).filter(([agentId, override]) => !excluded.has(agentId) && oneTimeCardMatchesScope(agentId, override, input)).map(([agentId, override]) => ({
13993
+ agentId,
13994
+ override,
13995
+ lifecycle: normalizeLocalAgentCardLifecycle(override.card)
13996
+ })).sort((left, right) => oneTimeCardCreatedAt(right.lifecycle) - oneTimeCardCreatedAt(left.lifecycle) || left.agentId.localeCompare(right.agentId));
13997
+ const retireIds = new Set;
13998
+ for (const candidate of candidates) {
13999
+ if (shouldPruneOneTimeCard(candidate.lifecycle, now, maxAgeMs)) {
14000
+ retireIds.add(candidate.agentId);
14001
+ }
14002
+ }
14003
+ const retained = candidates.filter((candidate) => !retireIds.has(candidate.agentId));
14004
+ for (const candidate of retained.slice(maxCount)) {
14005
+ retireIds.add(candidate.agentId);
14006
+ }
14007
+ const retired = await retireLocalAgentOverrides({ ...overrides }, retireIds);
14008
+ return {
14009
+ inspected: candidates.length,
14010
+ remaining: Math.max(0, candidates.length - retired.length),
14011
+ retired
14012
+ };
14013
+ }
14014
+ async function retireLocalAgent(agentId) {
14015
+ const overrides = await readRelayAgentOverrides();
14016
+ const override = overrides[agentId];
14017
+ if (!override) {
14018
+ return null;
14019
+ }
14020
+ const record = localAgentRecordFromRelayAgentOverride(agentId, override);
14021
+ const status = localAgentStatusFromRecord(agentId, record, localAgentStatusSource(agentId, overrides));
14022
+ await stopLocalAgent(agentId).catch(() => null);
14023
+ const nextOverrides = { ...overrides };
14024
+ delete nextOverrides[agentId];
14025
+ await writeRelayAgentOverrides(nextOverrides);
14026
+ return {
14027
+ ...status,
14028
+ isOnline: false
14029
+ };
14030
+ }
13545
14031
  function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
13546
14032
  const permissionPosture = compileCodexPermissionProfile(record.permissionProfile);
13547
14033
  return {
@@ -13592,6 +14078,90 @@ function buildLocalAgentSystemPrompt(agentName, projectName, projectPath, option
13592
14078
  function buildLocalAgentInitialMessage(projectName, agentName) {
13593
14079
  return `You are now online as the ${agentName} relay agent for ${projectName}. Announce yourself on the relay with: ${brokerRelayCommand()} send --as ${agentName} "relay agent online \u2014 ready to assist with ${projectName}"`;
13594
14080
  }
14081
+ function tmuxDispatchSleep(ms) {
14082
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
14083
+ }
14084
+ function captureTmuxPaneTail(sessionName, lines) {
14085
+ try {
14086
+ return execFileSync4("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", `-${lines}`, "-E", "-"], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
14087
+ } catch {
14088
+ return "";
14089
+ }
14090
+ }
14091
+ async function waitForTmuxHarnessReady(sessionName, harness) {
14092
+ if (harness !== "claude") {
14093
+ return;
14094
+ }
14095
+ const deadline = Date.now() + TMUX_READY_TIMEOUT_MS;
14096
+ let paneTail = "";
14097
+ while (Date.now() < deadline) {
14098
+ if (!isLocalAgentSessionAlive(sessionName)) {
14099
+ throw new Error(`tmux session ${sessionName} exited before Claude Code was ready.`);
14100
+ }
14101
+ paneTail = captureTmuxPaneTail(sessionName, TMUX_READY_TAIL_LINES);
14102
+ if (tmuxPaneTailShowsReadyComposer(paneTail)) {
14103
+ return;
14104
+ }
14105
+ await tmuxDispatchSleep(TMUX_READY_POLL_MS);
14106
+ }
14107
+ const tail = stripTerminalControlSequences(paneTail).trim().split(/\r?\n/).slice(-20).join(`
14108
+ `).trim();
14109
+ throw new Error(`tmux session ${sessionName} did not show a ready Claude Code composer within ${TMUX_READY_TIMEOUT_MS}ms.` + (tail ? `
14110
+ Recent pane tail:
14111
+ ${tail}` : ""));
14112
+ }
14113
+ function tmuxPaneTailShowsReadyComposer(paneTail) {
14114
+ const cleanedTail = stripTerminalControlSequences(paneTail);
14115
+ const lines = cleanedTail.split(/\r?\n/);
14116
+ const anchor = findActiveTmuxComposerAnchor(lines);
14117
+ if (!anchor) {
14118
+ return false;
14119
+ }
14120
+ const afterComposerLines = [];
14121
+ for (const line of lines.slice(anchor.index + 1)) {
14122
+ if (isTmuxComposerBoundary(line)) {
14123
+ break;
14124
+ }
14125
+ afterComposerLines.push(line);
14126
+ }
14127
+ return !tmuxPaneTailShowsHarnessActivity(afterComposerLines.join(`
14128
+ `));
14129
+ }
14130
+ function stripTerminalControlSequences(value) {
14131
+ return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
14132
+ }
14133
+ function findActiveTmuxComposerAnchor(lines) {
14134
+ const inlineComposerIndex = findLastIndex(lines, (line) => /^\s*[\u276F\u203A]\s*/.test(line));
14135
+ const boxedComposerIndex = findLastIndex(lines, (line) => /^\s*[\u2502\u2503]\s*[>\u276F]\s*/.test(line));
14136
+ if (inlineComposerIndex < 0 && boxedComposerIndex < 0) {
14137
+ return null;
14138
+ }
14139
+ if (inlineComposerIndex > boxedComposerIndex) {
14140
+ return { index: inlineComposerIndex, kind: "inline" };
14141
+ }
14142
+ return { index: boxedComposerIndex, kind: "boxed" };
14143
+ }
14144
+ function findLastIndex(items, predicate) {
14145
+ for (let index = items.length - 1;index >= 0; index -= 1) {
14146
+ if (predicate(items[index])) {
14147
+ return index;
14148
+ }
14149
+ }
14150
+ return -1;
14151
+ }
14152
+ function isTmuxComposerBoundary(line) {
14153
+ const trimmed = line.trim();
14154
+ if (!trimmed) {
14155
+ return false;
14156
+ }
14157
+ if (/^[\u2500\u2501\u2550\u256D\u256E\u2570\u256F\u250C\u2510\u2514\u2518\u2554\u2557\u255A\u255D\u255F\u2562\u2560\u2563\u256A\u256B\u256C\u2569\u2566\u2564\u2567\u254C\u254D\u254E\u254F\s]+$/.test(trimmed)) {
14158
+ return true;
14159
+ }
14160
+ return /^--\s*(?:INSERT|NORMAL)\s*--/.test(trimmed) || /^(?:Opus|Sonnet|Haiku|Claude|Codex|GPT)\b/.test(trimmed);
14161
+ }
14162
+ function tmuxPaneTailShowsHarnessActivity(paneTail) {
14163
+ return /(?:^|\n)\s*(?:[\u23FA\u25CF\u273D\u2722\u273B\u23BF]|Bash\(|Read\(|Edit\(|Write\(|Grep\(|Glob\(|TodoWrite\()/.test(paneTail);
14164
+ }
13595
14165
  function shellQuoteArguments(args) {
13596
14166
  return args.map((arg) => JSON.stringify(arg)).join(" ");
13597
14167
  }
@@ -13602,10 +14172,10 @@ function buildLocalAgentLaunchCommand(agentName, record, projectPath, promptFile
13602
14172
  const harness = normalizeLocalAgentHarness(record.harness);
13603
14173
  const extraArgs = shellQuoteArguments(harness === "claude" ? normalizeClaudeRuntimeLaunchArgs(record.launchArgs) : normalizeLaunchArgsForHarness(harness, record.launchArgs));
13604
14174
  if (harness === "codex") {
13605
- return `exec bash ${JSON.stringify(workerScript ?? join19(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
14175
+ return `exec bash ${JSON.stringify(workerScript ?? join20(relayAgentRuntimeDirectory(agentName), "codex-worker.sh"))}`;
13606
14176
  }
13607
14177
  if (harness === "pi") {
13608
- const sessionDir = join19(relayAgentRuntimeDirectory(agentName), "pi-sessions");
14178
+ const sessionDir = join20(relayAgentRuntimeDirectory(agentName), "pi-sessions");
13609
14179
  return [
13610
14180
  "pi",
13611
14181
  `--append-system-prompt "$(cat ${JSON.stringify(promptFile)})"`,
@@ -13675,7 +14245,7 @@ async function ensureLocalAgentOnline(agentName, record) {
13675
14245
  await mkdir6(agentRuntimeDir, { recursive: true });
13676
14246
  await mkdir6(logsDir, { recursive: true });
13677
14247
  if (normalizedRecord.transport === "codex_app_server") {
13678
- await writeFile6(join19(agentRuntimeDir, "prompt.txt"), systemPrompt);
14248
+ await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
13679
14249
  await ensureCodexAppServerAgentOnline(buildCodexAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
13680
14250
  const registry2 = await readLocalAgentRegistry();
13681
14251
  registry2[agentName] = {
@@ -13687,7 +14257,7 @@ async function ensureLocalAgentOnline(agentName, record) {
13687
14257
  return registry2[agentName];
13688
14258
  }
13689
14259
  if (normalizedRecord.transport === "claude_stream_json") {
13690
- await writeFile6(join19(agentRuntimeDir, "prompt.txt"), systemPrompt);
14260
+ await writeFile6(join20(agentRuntimeDir, "prompt.txt"), systemPrompt);
13691
14261
  await ensureClaudeStreamJsonAgentOnline(buildClaudeAgentSessionOptions(agentName, normalizedRecord, systemPrompt));
13692
14262
  const registry2 = await readLocalAgentRegistry();
13693
14263
  registry2[agentName] = {
@@ -13700,17 +14270,17 @@ async function ensureLocalAgentOnline(agentName, record) {
13700
14270
  }
13701
14271
  const initialMessage = buildLocalAgentInitialMessage(projectName, agentName);
13702
14272
  const bootstrapPrompt = buildLocalAgentBootstrapPrompt(normalizeLocalAgentHarness(normalizedRecord.harness), systemPrompt, initialMessage);
13703
- const queueDirectory = join19(agentRuntimeDir, "queue");
13704
- const processingDirectory = join19(queueDirectory, "processing");
13705
- const processedDirectory = join19(queueDirectory, "processed");
13706
- const promptFile = join19(agentRuntimeDir, "prompt.txt");
13707
- const initialFile = join19(agentRuntimeDir, "initial.txt");
13708
- const launchScript = join19(agentRuntimeDir, "launch.sh");
13709
- const workerScript = join19(agentRuntimeDir, "codex-worker.sh");
13710
- const codexSessionIdFile = join19(agentRuntimeDir, "codex-session-id.txt");
13711
- const stateFile = join19(agentRuntimeDir, "state.json");
13712
- const stdoutLogFile = join19(logsDir, "stdout.log");
13713
- const stderrLogFile = join19(logsDir, "stderr.log");
14273
+ const queueDirectory = join20(agentRuntimeDir, "queue");
14274
+ const processingDirectory = join20(queueDirectory, "processing");
14275
+ const processedDirectory = join20(queueDirectory, "processed");
14276
+ const promptFile = join20(agentRuntimeDir, "prompt.txt");
14277
+ const initialFile = join20(agentRuntimeDir, "initial.txt");
14278
+ const launchScript = join20(agentRuntimeDir, "launch.sh");
14279
+ const workerScript = join20(agentRuntimeDir, "codex-worker.sh");
14280
+ const codexSessionIdFile = join20(agentRuntimeDir, "codex-session-id.txt");
14281
+ const stateFile = join20(agentRuntimeDir, "state.json");
14282
+ const stdoutLogFile = join20(logsDir, "stdout.log");
14283
+ const stderrLogFile = join20(logsDir, "stderr.log");
13714
14284
  await writeFile6(promptFile, systemPrompt);
13715
14285
  await writeFile6(initialFile, bootstrapPrompt);
13716
14286
  await writeFile6(stderrLogFile, "");
@@ -13775,11 +14345,17 @@ async function ensureLocalAgentOnline(agentName, record) {
13775
14345
  await new Promise((resolve7) => setTimeout(resolve7, 100));
13776
14346
  }
13777
14347
  if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
13778
- const stderrTail = existsSync16(stderrLogFile) ? readFileSync10(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
14348
+ const stderrTail = existsSync17(stderrLogFile) ? readFileSync11(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
13779
14349
  `).trim() : "";
13780
14350
  throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
13781
14351
  ${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
13782
14352
  }
14353
+ try {
14354
+ await waitForTmuxHarnessReady(normalizedRecord.tmuxSession, normalizeLocalAgentHarness(normalizedRecord.harness));
14355
+ } catch (error) {
14356
+ killAgentSession(normalizedRecord.tmuxSession);
14357
+ throw error;
14358
+ }
13783
14359
  const registry = await readLocalAgentRegistry();
13784
14360
  registry[agentName] = {
13785
14361
  ...normalizedRecord,
@@ -13836,6 +14412,7 @@ async function startLocalAgent(input) {
13836
14412
  const effectiveCwd = input.cwdOverride ? normalizeProjectPath(input.cwdOverride) : undefined;
13837
14413
  const shouldEnsureOnline = input.ensureOnline !== false;
13838
14414
  const requestedPermissionProfile = parseScoutPermissionProfile(input.permissionProfile);
14415
+ const cardLifecycle = normalizeLocalAgentCardLifecycle(input.card);
13839
14416
  const requestedDefinitionId = input.agentName?.trim() ? normalizeAgentSelectorSegment(input.agentName.trim()) : "";
13840
14417
  if (input.agentName?.trim() && !requestedDefinitionId) {
13841
14418
  throw new Error(`Invalid agent name "${input.agentName}".`);
@@ -13885,7 +14462,8 @@ async function startLocalAgent(input) {
13885
14462
  const configDefinitionId = coldProjectConfig?.agent?.id?.trim() ? normalizeAgentSelectorSegment(coldProjectConfig.agent.id.trim()) : "";
13886
14463
  const definitionId = requestedDefinitionId || configDefinitionId || normalizeAgentSelectorSegment(basename8(projectRoot)) || "agent";
13887
14464
  const configDisplayName = coldProjectConfig?.agent?.displayName?.trim() || "";
13888
- const effectiveDisplayName = input.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
14465
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(definitionId);
14466
+ const effectiveDisplayName = input.displayName || operatorAugmentDefaults?.displayName || configDisplayName || titleCaseLocalAgentName(definitionId);
13889
14467
  const instance = buildRelayAgentInstance(definitionId, projectRoot);
13890
14468
  const configDefaultHarness = coldProjectConfig?.agent?.runtime?.defaultHarness;
13891
14469
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
@@ -13908,8 +14486,10 @@ async function startLocalAgent(input) {
13908
14486
  projectConfigPath: coldProjectConfigPath,
13909
14487
  source: "manual",
13910
14488
  startedAt: nowSeconds2(),
14489
+ ...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13911
14490
  launchArgs,
13912
14491
  defaultHarness: effectiveHarness,
14492
+ ...cardLifecycle ? { card: cardLifecycle } : {},
13913
14493
  harnessProfiles: {
13914
14494
  [effectiveHarness]: {
13915
14495
  cwd: effectiveCwd ?? projectRoot,
@@ -13945,18 +14525,21 @@ async function startLocalAgent(input) {
13945
14525
  reasoningEffort: input.reasoningEffort
13946
14526
  });
13947
14527
  const nextTransport = normalizeLocalAgentTransport(preferredHarness ? undefined : matchingOverride.runtime?.transport, nextHarness);
14528
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(requestedDefinitionId);
13948
14529
  overrides[instance.id] = {
13949
14530
  agentId: instance.id,
13950
14531
  definitionId: requestedDefinitionId,
13951
- displayName: input.displayName || titleCaseLocalAgentName(requestedDefinitionId),
14532
+ displayName: input.displayName || operatorAugmentDefaults?.displayName || titleCaseLocalAgentName(requestedDefinitionId),
13952
14533
  projectName: matchingOverride.projectName ?? basename8(matchingProjectRoot),
13953
14534
  projectRoot: matchingProjectRoot,
13954
14535
  projectConfigPath: null,
13955
14536
  source: "manual",
13956
14537
  startedAt: matchingOverride.startedAt ?? nowSeconds2(),
14538
+ ...operatorAugmentDefaults ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13957
14539
  launchArgs: nextHarness === nextDefaultHarness ? nextLaunchArgs : matchingOverride.launchArgs,
13958
14540
  capabilities: matchingOverride.capabilities,
13959
14541
  defaultHarness: nextDefaultHarness,
14542
+ ...cardLifecycle ? { card: cardLifecycle } : {},
13960
14543
  harnessProfiles: {
13961
14544
  ...matchingOverride.harnessProfiles ?? {},
13962
14545
  [nextHarness]: {
@@ -13978,7 +14561,9 @@ async function startLocalAgent(input) {
13978
14561
  await writeRelayAgentOverrides(overrides);
13979
14562
  targetAgentId = instance.id;
13980
14563
  } else {
13981
- if (input.model || input.reasoningEffort || requestedPermissionProfile) {
14564
+ const operatorAugmentDefaults = operatorAugmentDefaultsForDefinitionId(matchingOverride.definitionId ?? requestedDefinitionId);
14565
+ const shouldApplyOperatorAugmentPrompt = Boolean(operatorAugmentDefaults && !matchingOverride.systemPrompt?.trim());
14566
+ if (input.model || input.reasoningEffort || requestedPermissionProfile || cardLifecycle || shouldApplyOperatorAugmentPrompt) {
13982
14567
  const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
13983
14568
  const nextLaunchArgs = applyRequestedRuntimeOptionsToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), {
13984
14569
  model: input.model,
@@ -13986,6 +14571,8 @@ async function startLocalAgent(input) {
13986
14571
  });
13987
14572
  overrides[matchingAgentId] = {
13988
14573
  ...matchingOverride,
14574
+ ...cardLifecycle ? { card: cardLifecycle } : {},
14575
+ ...shouldApplyOperatorAugmentPrompt ? { systemPrompt: operatorAugmentDefaults.systemPrompt } : {},
13989
14576
  launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
13990
14577
  harnessProfiles: {
13991
14578
  ...matchingOverride.harnessProfiles ?? {},
@@ -14090,9 +14677,9 @@ async function restartAllLocalAgents(options = {}) {
14090
14677
  function readPersistedClaudeSessionId(agentName) {
14091
14678
  try {
14092
14679
  const runtimeDir = relayAgentRuntimeDirectory(agentName);
14093
- const catalogPath = join19(runtimeDir, "session-catalog.json");
14094
- if (existsSync16(catalogPath)) {
14095
- const raw = readFileSync10(catalogPath, "utf8").trim();
14680
+ const catalogPath = join20(runtimeDir, "session-catalog.json");
14681
+ if (existsSync17(catalogPath)) {
14682
+ const raw = readFileSync11(catalogPath, "utf8").trim();
14096
14683
  if (raw) {
14097
14684
  const catalog = JSON.parse(raw);
14098
14685
  if (typeof catalog.activeSessionId === "string" && catalog.activeSessionId.trim()) {
@@ -14100,9 +14687,9 @@ function readPersistedClaudeSessionId(agentName) {
14100
14687
  }
14101
14688
  }
14102
14689
  }
14103
- const legacyPath = join19(runtimeDir, "claude-session-id.txt");
14104
- if (existsSync16(legacyPath)) {
14105
- const value = readFileSync10(legacyPath, "utf8").trim();
14690
+ const legacyPath = join20(runtimeDir, "claude-session-id.txt");
14691
+ if (existsSync17(legacyPath)) {
14692
+ const value = readFileSync11(legacyPath, "utf8").trim();
14106
14693
  return value || null;
14107
14694
  }
14108
14695
  return null;
@@ -14114,6 +14701,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14114
14701
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
14115
14702
  const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
14116
14703
  const codexPermissionPosture = normalizeLocalAgentHarness(normalizedRecord.harness) === "codex" ? compileCodexPermissionProfile(normalizedRecord.permissionProfile) : null;
14704
+ const cardLifecycle = normalizeLocalAgentCardLifecycle(normalizedRecord.card);
14117
14705
  const definitionId = normalizedRecord.definitionId ?? agentId;
14118
14706
  const displayName = titleCaseLocalAgentName(definitionId);
14119
14707
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
@@ -14141,6 +14729,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14141
14729
  workspaceQualifier: instance.workspaceQualifier,
14142
14730
  branch: instance.branch,
14143
14731
  ...configuredModel ? { model: configuredModel } : {},
14732
+ ...cardLifecycle ? { cardLifecycle } : {},
14144
14733
  ...codexPermissionPosture ? {
14145
14734
  permissionProfile: codexPermissionPosture.profile,
14146
14735
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14176,6 +14765,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14176
14765
  workspaceQualifier: instance.workspaceQualifier,
14177
14766
  branch: instance.branch,
14178
14767
  ...configuredModel ? { model: configuredModel } : {},
14768
+ ...cardLifecycle ? { cardLifecycle } : {},
14179
14769
  ...codexPermissionPosture ? {
14180
14770
  permissionProfile: codexPermissionPosture.profile,
14181
14771
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14216,6 +14806,7 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
14216
14806
  workspaceQualifier: instance.workspaceQualifier,
14217
14807
  branch: instance.branch,
14218
14808
  ...configuredModel ? { model: configuredModel } : {},
14809
+ ...cardLifecycle ? { cardLifecycle } : {},
14219
14810
  ...codexPermissionPosture ? {
14220
14811
  permissionProfile: codexPermissionPosture.profile,
14221
14812
  approvalPolicy: codexPermissionPosture.approvalPolicy,
@@ -14302,9 +14893,10 @@ async function ensureLocalAgentBindingOnline(agentId, nodeId, options = {}) {
14302
14893
  const onlineRecord = await ensureLocalAgentOnline(agentId, recordForHarness(localAgentRecordFromRelayAgentOverride(agentId, override), options.harness));
14303
14894
  return buildLocalAgentBinding(agentId, onlineRecord, isLocalAgentRecordOnline(agentId, onlineRecord), nodeId, "relay-agent-registry");
14304
14895
  }
14305
- var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS;
14896
+ var MODULE_DIRECTORY, OPENSCOUT_REPO_ROOT, DEFAULT_LOCAL_AGENT_CAPABILITIES, DEFAULT_LOCAL_AGENT_HARNESS = "claude", DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS, DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_RETAIN = 24, SUPPORTED_LOCAL_AGENT_HARNESSES, SUPPORTED_SCOUT_HARNESSES, DEFAULT_CLAUDE_SCOUT_ALLOWED_TOOLS, TMUX_READY_TIMEOUT_MS = 20000, TMUX_READY_POLL_MS = 250, TMUX_READY_TAIL_LINES = 80;
14306
14897
  var init_local_agents = __esm(() => {
14307
14898
  init_src2();
14899
+ init_dispatch_stalled();
14308
14900
  init_claude_stream_json();
14309
14901
  init_codex_app_server();
14310
14902
  init_setup();
@@ -14314,9 +14906,11 @@ var init_local_agents = __esm(() => {
14314
14906
  init_local_agent_template();
14315
14907
  init_permission_policy2();
14316
14908
  init_requester_timeout();
14317
- MODULE_DIRECTORY = dirname9(fileURLToPath5(import.meta.url));
14909
+ init_user_config();
14910
+ MODULE_DIRECTORY = dirname10(fileURLToPath5(import.meta.url));
14318
14911
  OPENSCOUT_REPO_ROOT = resolve6(MODULE_DIRECTORY, "..", "..", "..");
14319
14912
  DEFAULT_LOCAL_AGENT_CAPABILITIES = ["chat", "invoke", "deliver"];
14913
+ DEFAULT_ONE_TIME_LOCAL_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
14320
14914
  SUPPORTED_LOCAL_AGENT_HARNESSES = ["claude", "codex", "pi"];
14321
14915
  SUPPORTED_SCOUT_HARNESSES = [
14322
14916
  ...SUPPORTED_LOCAL_AGENT_HARNESSES,
@@ -14331,8 +14925,8 @@ var init_local_agents = __esm(() => {
14331
14925
  "mcp__scout__agents_search",
14332
14926
  "mcp__scout__agents_resolve",
14333
14927
  "mcp__scout__messages_reply",
14928
+ "mcp__scout__ask",
14334
14929
  "mcp__scout__messages_send",
14335
- "mcp__scout__invocations_ask",
14336
14930
  "mcp__scout__invocations_get",
14337
14931
  "mcp__scout__invocations_wait",
14338
14932
  "mcp__scout__work_update",
@@ -17829,7 +18423,7 @@ function writeJsonAtomically(filePath, value) {
17829
18423
 
17830
18424
  // ../web/server/core/pairing/runtime/runtime.ts
17831
18425
  init_src();
17832
- import { homedir as homedir24 } from "os";
18426
+ import { homedir as homedir25 } from "os";
17833
18427
 
17834
18428
  // ../web/server/core/pairing/runtime/bridge/bridge.ts
17835
18429
  init_src();
@@ -18643,18 +19237,21 @@ function formatTimestamp(value) {
18643
19237
 
18644
19238
  // ../web/server/core/pairing/runtime/bridge/server.ts
18645
19239
  init_src();
18646
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, realpathSync, statSync as statSync5 } from "fs";
19240
+ import { readdirSync as readdirSync5, readFileSync as readFileSync13, realpathSync, statSync as statSync5 } from "fs";
18647
19241
  import { execSync as execSync3 } from "child_process";
18648
- import { basename as basename10, isAbsolute as isAbsolute3, join as join23, relative as relative3 } from "path";
18649
- import { homedir as homedir21 } from "os";
19242
+ import { basename as basename11, isAbsolute as isAbsolute3, join as join25, relative as relative3 } from "path";
19243
+ import { homedir as homedir22 } from "os";
18650
19244
 
18651
19245
  // ../web/server/core/mobile/service.ts
18652
19246
  init_harness_catalog();
18653
19247
  init_setup();
18654
- import { basename as basename9, resolve as resolve7 } from "path";
19248
+ import { basename as basename10, resolve as resolve7 } from "path";
18655
19249
 
18656
19250
  // ../runtime/src/control-plane-agents.ts
19251
+ init_src2();
18657
19252
  init_local_agents();
19253
+ import { randomUUID as randomUUID2 } from "crypto";
19254
+ import { basename as basename9 } from "path";
18658
19255
 
18659
19256
  // ../runtime/src/scout-agent-cards.ts
18660
19257
  init_src2();
@@ -18707,6 +19304,7 @@ function buildScoutAgentCard(binding, options = {}) {
18707
19304
  const supportedInterfaces = metadataRecordArray(binding.agent.metadata, "supportedInterfaces");
18708
19305
  const securitySchemes = metadataRecord2(binding.agent.metadata, "securitySchemes");
18709
19306
  const securityRequirements = metadataStringMatrix(binding.agent.metadata, "securityRequirements");
19307
+ const lifecycle2 = options.lifecycle ?? metadataRecord2(binding.agent.metadata, "cardLifecycle");
18710
19308
  return {
18711
19309
  id: binding.agent.id,
18712
19310
  agentId: binding.agent.id,
@@ -18736,6 +19334,7 @@ function buildScoutAgentCard(binding, options = {}) {
18736
19334
  ...options.createdById?.trim() ? { createdById: options.createdById.trim() } : {},
18737
19335
  brokerRegistered: options.brokerRegistered ?? false,
18738
19336
  ...options.inboxConversationId?.trim() ? { inboxConversationId: options.inboxConversationId.trim() } : {},
19337
+ ...lifecycle2 ? { lifecycle: lifecycle2 } : {},
18739
19338
  returnAddress: buildScoutReturnAddress({
18740
19339
  actorId: binding.agent.id,
18741
19340
  handle,
@@ -18753,19 +19352,30 @@ function buildScoutAgentCard(binding, options = {}) {
18753
19352
  actorId: binding.actor.id,
18754
19353
  endpointId: binding.endpoint.id,
18755
19354
  wakePolicy: binding.agent.wakePolicy,
18756
- ...model ? { model } : {}
19355
+ ...model ? { model } : {},
19356
+ ...lifecycle2 ? { cardLifecycle: lifecycle2 } : {}
18757
19357
  }
18758
19358
  };
18759
19359
  }
18760
19360
 
18761
19361
  // ../runtime/src/control-plane-agents.ts
19362
+ var DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS = 24 * 60 * 60 * 1000;
19363
+ function oneTimeAgentName(input) {
19364
+ const base = normalizeAgentSelectorSegment(input.agentName || basename9(input.projectPath)) || "agent";
19365
+ return `${base}-card-${randomUUID2().slice(0, 8)}`;
19366
+ }
18762
19367
  function createScoutAgentService(deps) {
18763
19368
  const localAgents = {
18764
19369
  listLocalAgents: deps.localAgents?.listLocalAgents ?? listLocalAgents,
19370
+ retireLocalAgent: deps.localAgents?.retireLocalAgent ?? retireLocalAgent,
18765
19371
  restartAllLocalAgents: deps.localAgents?.restartAllLocalAgents ?? restartAllLocalAgents,
19372
+ restartLocalAgent: deps.localAgents?.restartLocalAgent ?? restartLocalAgent,
18766
19373
  startLocalAgent: deps.localAgents?.startLocalAgent ?? startLocalAgent,
18767
19374
  stopAllLocalAgents: deps.localAgents?.stopAllLocalAgents ?? stopAllLocalAgents,
18768
19375
  stopLocalAgent: deps.localAgents?.stopLocalAgent ?? stopLocalAgent,
19376
+ updateLocalAgentCard: deps.localAgents?.updateLocalAgentCard ?? updateLocalAgentCard,
19377
+ updateLocalAgentCardLifecycle: deps.localAgents?.updateLocalAgentCardLifecycle ?? updateLocalAgentCardLifecycle,
19378
+ pruneOneTimeLocalAgentCards: deps.localAgents?.pruneOneTimeLocalAgentCards ?? pruneOneTimeLocalAgentCards,
18769
19379
  inferLocalAgentBinding: deps.localAgents?.inferLocalAgentBinding ?? inferLocalAgentBinding
18770
19380
  };
18771
19381
  return {
@@ -18782,22 +19392,62 @@ function createScoutAgentService(deps) {
18782
19392
  async downScoutAgent(agentId) {
18783
19393
  return localAgents.stopLocalAgent(agentId);
18784
19394
  },
19395
+ async retireScoutAgentCard(agentId) {
19396
+ const broker = await deps.loadScoutBrokerContext().catch(() => null);
19397
+ const retired = await localAgents.retireLocalAgent(agentId);
19398
+ if (retired) {
19399
+ await deps.retireScoutLocalAgentBinding?.({ agentId, broker }).catch(() => false);
19400
+ }
19401
+ return retired;
19402
+ },
19403
+ async updateScoutAgentCard(agentId, input) {
19404
+ const config = await localAgents.updateLocalAgentCard(agentId, input);
19405
+ if (!config) {
19406
+ return null;
19407
+ }
19408
+ if (input.restart === true) {
19409
+ await localAgents.restartLocalAgent(agentId);
19410
+ }
19411
+ await deps.registerScoutLocalAgentBinding({ agentId }).catch(() => {});
19412
+ return config;
19413
+ },
18785
19414
  async downAllScoutAgents(input = {}) {
18786
19415
  return localAgents.stopAllLocalAgents(input);
18787
19416
  },
18788
19417
  async restartScoutAgents(input = {}) {
18789
19418
  return localAgents.restartAllLocalAgents(input);
18790
19419
  },
19420
+ async cleanupScoutAgentCards(input = {}) {
19421
+ const broker = await deps.loadScoutBrokerContext().catch(() => null);
19422
+ const result = await localAgents.pruneOneTimeLocalAgentCards(input);
19423
+ await Promise.all(result.retired.map((status) => deps.retireScoutLocalAgentBinding?.({ agentId: status.agentId, broker }).catch(() => false)));
19424
+ return result;
19425
+ },
18791
19426
  async createScoutAgentCard(input) {
19427
+ const createdAt = Date.now();
19428
+ const oneTimeUse = input.oneTimeUse === true;
19429
+ const createdById = input.createdById?.trim() || undefined;
19430
+ const agentName = oneTimeUse ? oneTimeAgentName({
19431
+ agentName: input.agentName,
19432
+ projectPath: input.projectPath
19433
+ }) : input.agentName;
19434
+ const lifecycle2 = oneTimeUse ? {
19435
+ kind: "one_time",
19436
+ createdAt,
19437
+ ...createdById ? { createdById } : {},
19438
+ expiresAt: createdAt + Math.max(1, input.ttlMs ?? DEFAULT_ONE_TIME_SCOUT_AGENT_CARD_TTL_MS),
19439
+ maxUses: 1
19440
+ } : undefined;
18792
19441
  const status = await localAgents.startLocalAgent({
18793
19442
  projectPath: input.projectPath,
18794
- agentName: input.agentName,
19443
+ agentName,
18795
19444
  displayName: input.displayName,
18796
19445
  harness: input.harness,
18797
19446
  model: input.model,
18798
19447
  reasoningEffort: input.reasoningEffort,
18799
19448
  permissionProfile: input.permissionProfile,
18800
19449
  currentDirectory: input.currentDirectory,
19450
+ ...lifecycle2 ? { card: lifecycle2 } : {},
18801
19451
  ensureOnline: false
18802
19452
  });
18803
19453
  const currentDirectory = input.currentDirectory ?? input.projectPath;
@@ -18811,21 +19461,38 @@ function createScoutAgentService(deps) {
18811
19461
  throw new Error(`Agent ${status.agentId} did not expose an addressable binding.`);
18812
19462
  }
18813
19463
  let inboxConversationId;
18814
- let createdById = input.createdById?.trim() || undefined;
18815
- if (broker && createdById && createdById !== binding.agent.id) {
19464
+ let resolvedCreatedById = createdById;
19465
+ if (broker && resolvedCreatedById && resolvedCreatedById !== binding.agent.id) {
18816
19466
  const session = await deps.openScoutPeerSession({
18817
- sourceId: createdById,
19467
+ sourceId: resolvedCreatedById,
18818
19468
  targetId: binding.agent.id,
18819
19469
  currentDirectory
18820
19470
  });
18821
19471
  inboxConversationId = session.conversation.id;
18822
- createdById = session.sourceId;
19472
+ resolvedCreatedById = session.sourceId;
19473
+ }
19474
+ const finalLifecycle = lifecycle2 ? {
19475
+ ...lifecycle2,
19476
+ ...resolvedCreatedById ? { createdById: resolvedCreatedById } : {},
19477
+ ...inboxConversationId ? { inboxConversationId } : {}
19478
+ } : undefined;
19479
+ if (finalLifecycle) {
19480
+ await localAgents.updateLocalAgentCardLifecycle(status.agentId, finalLifecycle).catch(() => null);
19481
+ const cleaned = await localAgents.pruneOneTimeLocalAgentCards({
19482
+ createdById: finalLifecycle.createdById,
19483
+ projectRoot: binding.endpoint.projectRoot ?? input.projectPath,
19484
+ excludeAgentIds: [status.agentId]
19485
+ }).catch(() => null);
19486
+ if (cleaned?.retired.length) {
19487
+ await Promise.all(cleaned.retired.map((retired) => deps.retireScoutLocalAgentBinding?.({ agentId: retired.agentId, broker }).catch(() => false)));
19488
+ }
18823
19489
  }
18824
19490
  return buildScoutAgentCard(binding, {
18825
19491
  currentDirectory,
18826
- createdById,
19492
+ createdById: resolvedCreatedById,
18827
19493
  brokerRegistered: syncResult?.brokerRegistered ?? false,
18828
- inboxConversationId
19494
+ inboxConversationId,
19495
+ lifecycle: finalLifecycle
18829
19496
  });
18830
19497
  }
18831
19498
  };
@@ -18838,30 +19505,31 @@ init_broker_api();
18838
19505
  init_broker_process_manager();
18839
19506
  init_local_agents();
18840
19507
  init_support_paths();
19508
+ init_user_config();
18841
19509
 
18842
19510
  // ../runtime/src/local-config.ts
18843
19511
  import {
18844
- existsSync as existsSync17,
18845
- mkdirSync as mkdirSync9,
18846
- readFileSync as readFileSync11,
19512
+ existsSync as existsSync18,
19513
+ mkdirSync as mkdirSync10,
19514
+ readFileSync as readFileSync12,
18847
19515
  renameSync as renameSync2,
18848
- writeFileSync as writeFileSync6
19516
+ writeFileSync as writeFileSync7
18849
19517
  } from "fs";
18850
- import { homedir as homedir18, hostname as osHostname } from "os";
18851
- import { dirname as dirname10, join as join20 } from "path";
19518
+ import { homedir as homedir19, hostname as osHostname } from "os";
19519
+ import { dirname as dirname11, join as join21 } from "path";
18852
19520
  var LOCAL_CONFIG_VERSION = 1;
18853
19521
  function localConfigHome() {
18854
- return process.env.OPENSCOUT_HOME ?? join20(homedir18(), ".openscout");
19522
+ return process.env.OPENSCOUT_HOME ?? join21(homedir19(), ".openscout");
18855
19523
  }
18856
19524
  function localConfigPath() {
18857
- return join20(localConfigHome(), "config.json");
19525
+ return join21(localConfigHome(), "config.json");
18858
19526
  }
18859
19527
  function loadLocalConfig() {
18860
19528
  const configPath = localConfigPath();
18861
- if (!existsSync17(configPath))
19529
+ if (!existsSync18(configPath))
18862
19530
  return { version: LOCAL_CONFIG_VERSION };
18863
19531
  try {
18864
- return validateLocalConfig(JSON.parse(readFileSync11(configPath, "utf8")));
19532
+ return validateLocalConfig(JSON.parse(readFileSync12(configPath, "utf8")));
18865
19533
  } catch {
18866
19534
  return { version: LOCAL_CONFIG_VERSION };
18867
19535
  }
@@ -18952,6 +19620,13 @@ function metadataString3(metadata, key) {
18952
19620
  function metadataBoolean(metadata, key) {
18953
19621
  return metadata?.[key] === true;
18954
19622
  }
19623
+ function isSupersededBrokerAgent(snapshot, agentId) {
19624
+ const agent = snapshot.agents[agentId];
19625
+ if (!agent) {
19626
+ return false;
19627
+ }
19628
+ return metadataBoolean(agent.metadata, "retiredFromFleet") || metadataBoolean(agent.metadata, "staleLocalRegistration");
19629
+ }
18955
19630
  async function brokerReadJson(baseUrl, path2, options = {}) {
18956
19631
  return requestScoutBrokerJson(baseUrl, path2, {
18957
19632
  socketPath: resolveBrokerSocketPathForBaseUrl(baseUrl),
@@ -18989,6 +19664,17 @@ async function readScoutBrokerHealth(baseUrl = resolveScoutBrokerUrl(), options
18989
19664
  nodes: health.counts.nodes ?? 0,
18990
19665
  actors: health.counts.actors ?? 0,
18991
19666
  agents: health.counts.agents ?? 0,
19667
+ agentRecords: health.counts.agentRecords,
19668
+ rawAgentRecords: health.counts.rawAgentRecords,
19669
+ configuredAgents: health.counts.configuredAgents,
19670
+ scoutManagedAgents: health.counts.scoutManagedAgents,
19671
+ currentAgentRegistrations: health.counts.currentAgentRegistrations,
19672
+ localAgentRegistrations: health.counts.localAgentRegistrations,
19673
+ remoteAgentRegistrations: health.counts.remoteAgentRegistrations,
19674
+ staleAgentRegistrations: health.counts.staleAgentRegistrations,
19675
+ retiredAgentRegistrations: health.counts.retiredAgentRegistrations,
19676
+ oneTimeAgentCards: health.counts.oneTimeAgentCards,
19677
+ persistentAgentCards: health.counts.persistentAgentCards,
18992
19678
  conversations: health.counts.conversations ?? 0,
18993
19679
  messages: health.counts.messages ?? 0,
18994
19680
  flights: health.counts.flights ?? 0
@@ -19138,6 +19824,48 @@ async function registerScoutLocalAgentBinding(input) {
19138
19824
  brokerRegistered: Boolean(broker)
19139
19825
  };
19140
19826
  }
19827
+ async function retireScoutLocalAgentBinding(input) {
19828
+ const broker = input.broker ?? await loadScoutBrokerContext();
19829
+ if (!broker) {
19830
+ return false;
19831
+ }
19832
+ const retiredAt = Date.now();
19833
+ let retired = false;
19834
+ const agent = broker.snapshot.agents[input.agentId];
19835
+ if (agent) {
19836
+ const nextAgent = {
19837
+ ...agent,
19838
+ metadata: {
19839
+ ...agent.metadata ?? {},
19840
+ retiredFromFleet: true,
19841
+ retiredAt
19842
+ }
19843
+ };
19844
+ await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.agents, nextAgent);
19845
+ broker.snapshot.agents[input.agentId] = nextAgent;
19846
+ retired = true;
19847
+ }
19848
+ for (const endpoint of Object.values(broker.snapshot.endpoints)) {
19849
+ if (endpoint.agentId !== input.agentId) {
19850
+ continue;
19851
+ }
19852
+ const nextEndpoint = {
19853
+ ...endpoint,
19854
+ state: "offline",
19855
+ metadata: {
19856
+ ...endpoint.metadata ?? {},
19857
+ retiredFromFleet: true,
19858
+ retiredAt,
19859
+ lastError: "local agent card retired",
19860
+ lastFailedAt: retiredAt
19861
+ }
19862
+ };
19863
+ await brokerPostJson(broker.baseUrl, scoutBrokerPaths.v1.endpoints, nextEndpoint);
19864
+ broker.snapshot.endpoints[endpoint.id] = nextEndpoint;
19865
+ retired = true;
19866
+ }
19867
+ return retired;
19868
+ }
19141
19869
  async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, currentDirectory, displayName) {
19142
19870
  const normalized = actorId.trim() || OPERATOR_ID;
19143
19871
  if (snapshot.agents[normalized] || snapshot.actors[normalized]) {
@@ -19165,6 +19893,9 @@ async function resolveConversationActorId(baseUrl, snapshot, nodeId, actorId, cu
19165
19893
  }
19166
19894
  async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agentId, currentDirectory) {
19167
19895
  const existingAgent = snapshot.agents[agentId];
19896
+ if (existingAgent && metadataBoolean(existingAgent.metadata, "retiredFromFleet")) {
19897
+ return false;
19898
+ }
19168
19899
  if (existingAgent && !metadataBoolean(existingAgent.metadata, "staleLocalRegistration")) {
19169
19900
  return true;
19170
19901
  }
@@ -19173,7 +19904,7 @@ async function ensureTargetRelayAgentRegistered(baseUrl, snapshot, nodeId, agent
19173
19904
  syncLegacyMirror: true
19174
19905
  });
19175
19906
  if (!configured) {
19176
- return false;
19907
+ return Boolean(existingAgent);
19177
19908
  }
19178
19909
  const binding = await inferLocalAgentBinding(configured.agentId, nodeId);
19179
19910
  if (!binding) {
@@ -19254,6 +19985,11 @@ async function sendScoutDirectMessage(input) {
19254
19985
  const broker = await requireScoutBrokerContext();
19255
19986
  const createdAt = Date.now();
19256
19987
  const source = input.source?.trim() || "scout-mobile";
19988
+ const targetEndpoints = Object.values(broker.snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === input.agentId);
19989
+ const targetIsStale = isSupersededBrokerAgent(broker.snapshot, input.agentId) || targetEndpoints.length > 0 && targetEndpoints.every((endpoint) => metadataBoolean(endpoint.metadata, "retiredFromFleet") || metadataBoolean(endpoint.metadata, "staleLocalRegistration"));
19990
+ if (targetIsStale) {
19991
+ throw new Error(`${displayNameForBrokerActor(broker.snapshot, input.agentId)} is a stale local registration. Start the current project session from Workspaces before sending.`);
19992
+ }
19257
19993
  const delivery = await brokerPostDeliver(broker.baseUrl, {
19258
19994
  id: `deliver-${createdAt.toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
19259
19995
  requesterId: OPERATOR_ID,
@@ -19311,24 +20047,28 @@ async function loadScoutActivityItems(options = {}) {
19311
20047
  var scoutAgentService = createScoutAgentService({
19312
20048
  loadScoutBrokerContext,
19313
20049
  openScoutPeerSession,
19314
- registerScoutLocalAgentBinding
20050
+ registerScoutLocalAgentBinding,
20051
+ retireScoutLocalAgentBinding
19315
20052
  });
19316
20053
  var {
20054
+ cleanupScoutAgentCards,
19317
20055
  createScoutAgentCard,
19318
20056
  downAllScoutAgents,
19319
20057
  downScoutAgent,
19320
20058
  loadScoutAgentStatuses,
20059
+ retireScoutAgentCard,
19321
20060
  restartScoutAgents,
20061
+ updateScoutAgentCard,
19322
20062
  upScoutAgent
19323
20063
  } = scoutAgentService;
19324
20064
 
19325
20065
  // ../web/server/db/internal/db.ts
19326
20066
  import { Database } from "bun:sqlite";
19327
- import { homedir as homedir19 } from "os";
19328
- import { join as join21 } from "path";
20067
+ import { homedir as homedir20 } from "os";
20068
+ import { join as join22 } from "path";
19329
20069
  function resolveDbPath() {
19330
- const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join21(homedir19(), ".openscout", "control-plane");
19331
- return join21(controlHome, "control-plane.sqlite");
20070
+ const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join22(homedir20(), ".openscout", "control-plane");
20071
+ return join22(controlHome, "control-plane.sqlite");
19332
20072
  }
19333
20073
  var _db = null;
19334
20074
  var DB_BUSY_TIMEOUT_MS = 250;
@@ -19344,6 +20084,7 @@ function db() {
19344
20084
  return _db;
19345
20085
  }
19346
20086
  // ../runtime/src/conversations/legacy-ids.ts
20087
+ init_user_config();
19347
20088
  function conversationIdForAgent(agentId) {
19348
20089
  return buildDirectConversationId("operator", agentId);
19349
20090
  }
@@ -19360,9 +20101,9 @@ var EPOCH_MILLISECONDS_FLOOR = 1000000000000;
19360
20101
 
19361
20102
  // ../web/server/db/internal/paths.ts
19362
20103
  init_support_paths();
19363
- import { homedir as homedir20 } from "os";
19364
- import { join as join22 } from "path";
19365
- var HOME = homedir20();
20104
+ import { homedir as homedir21 } from "os";
20105
+ import { join as join23 } from "path";
20106
+ var HOME = homedir21();
19366
20107
  function compact(p) {
19367
20108
  if (!p)
19368
20109
  return null;
@@ -19374,12 +20115,15 @@ function pairingHarnessLogPath(adapterType, sessionId) {
19374
20115
  if (!normalizedAdapter || !normalizedSessionId) {
19375
20116
  return null;
19376
20117
  }
19377
- return join22(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
20118
+ return join23(HOME, ".scout", "pairing", normalizedAdapter, normalizedSessionId, "logs", "stdout.log");
19378
20119
  }
19379
20120
  function relayHarnessLogPath(agentId) {
19380
- return join22(relayAgentLogsDirectory(agentId), "stdout.log");
20121
+ return join23(relayAgentLogsDirectory(agentId), "stdout.log");
19381
20122
  }
19382
20123
  function resolveHarnessSessionId(transport, endpointSessionId, metadata) {
20124
+ if (transport === "tmux") {
20125
+ return metadataString4(metadata, "tmuxSession") ?? endpointSessionId;
20126
+ }
19383
20127
  if (transport === "pairing_bridge") {
19384
20128
  const attachedTransport = metadataString4(metadata, "attachedTransport");
19385
20129
  if (attachedTransport === "codex_app_server") {
@@ -19444,7 +20188,8 @@ function summarizeAgentState(rawState, isWorking, wakePolicy) {
19444
20188
  if (isWorking) {
19445
20189
  return "working";
19446
20190
  }
19447
- if (rawState === "offline" && wakePolicy === "on_demand") {
20191
+ const wakeableWithoutEndpoint = wakePolicy === "on_demand" || wakePolicy === "always_on";
20192
+ if ((!rawState || rawState === "offline") && wakeableWithoutEndpoint) {
19448
20193
  return "available";
19449
20194
  }
19450
20195
  return rawState && rawState !== "offline" ? "available" : "offline";
@@ -19466,7 +20211,11 @@ function transientBrokerWorkingStatusPredicate(alias) {
19466
20211
  return `NOT (
19467
20212
  ${alias}.class = 'status'
19468
20213
  AND COALESCE(json_extract(${alias}.metadata_json, '$.source'), '') = 'broker'
19469
- AND ${alias}.body LIKE '% is working.'
20214
+ AND (
20215
+ ${alias}.body LIKE '% is working.'
20216
+ OR ${alias}.body LIKE '%Scout stopped waiting for a synchronous result%'
20217
+ OR ${alias}.body LIKE '%the requester stopped waiting after%'
20218
+ )
19470
20219
  )`;
19471
20220
  }
19472
20221
  // ../web/server/db/activity.ts
@@ -19485,13 +20234,29 @@ var ACTIVE_RUN_STATES_SQL = sqlStringList([...ACTIVE_RUN_STATES]);
19485
20234
  var AGENT_RUN_STATES_SQL = sqlStringList(AGENT_RUN_STATES);
19486
20235
  var AGENT_RUN_SOURCES_SQL = sqlStringList(AGENT_RUN_SOURCES);
19487
20236
  // ../web/server/db/fleet.ts
20237
+ init_user_config();
19488
20238
  var TERMINAL_FLIGHT_STATES = new Set(["completed", "failed", "cancelled"]);
19489
20239
  var FLEET_RECENT_COMPLETED_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000;
19490
20240
  // ../web/server/db/mobile/agents.ts
19491
- function queryMobileAgents(limit = 50) {
20241
+ function queryMobileAgents(limit = 50, filters = {}) {
19492
20242
  const executingAgentIds = queryExecutingAgentIds();
19493
20243
  const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
19494
20244
  const endpointUpdatedAtExpression = sqlTimestampMsExpression("ep.updated_at");
20245
+ const query = filters.query?.trim().toLowerCase();
20246
+ const whereClauses = [activeAgentMetadataPredicate("a")];
20247
+ const params = [];
20248
+ if (query) {
20249
+ whereClauses.push(`(
20250
+ lower(ac.display_name) LIKE ?
20251
+ OR lower(a.id) LIKE ?
20252
+ OR lower(COALESCE(a.default_selector, '')) LIKE ?
20253
+ OR lower(COALESCE(a.selector, '')) LIKE ?
20254
+ OR lower(COALESCE(ep.project_root, '')) LIKE ?
20255
+ )`);
20256
+ const pattern = `%${query}%`;
20257
+ params.push(pattern, pattern, pattern, pattern, pattern);
20258
+ }
20259
+ params.push(limit);
19495
20260
  const lastMessageAt = new Map(db().prepare(`SELECT actor_id, MAX(${messageCreatedAtExpression}) AS last_at FROM messages GROUP BY actor_id`).all().map((r) => [r.actor_id, r.last_at]));
19496
20261
  const rows = db().prepare(`SELECT
19497
20262
  a.id,
@@ -19508,9 +20273,9 @@ function queryMobileAgents(limit = 50) {
19508
20273
  FROM agents a
19509
20274
  JOIN actors ac ON ac.id = a.id
19510
20275
  ${LATEST_AGENT_ENDPOINT_JOIN}
19511
- WHERE ${activeAgentMetadataPredicate("a")}
20276
+ WHERE ${whereClauses.join(" AND ")}
19512
20277
  ORDER BY COALESCE(${endpointUpdatedAtExpression}, 0) DESC, ac.display_name ASC
19513
- LIMIT ?`).all(limit);
20278
+ LIMIT ?`).all(...params);
19514
20279
  return rows.map((r) => {
19515
20280
  let meta = {};
19516
20281
  try {
@@ -19619,6 +20384,9 @@ function queryMobileAgentDetail(agentId) {
19619
20384
  }
19620
20385
  // ../web/server/db/mobile/sessions.ts
19621
20386
  function queryMobileSessions(limit = 50) {
20387
+ const conversationCreatedAtExpression = sqlTimestampMsExpression("c.created_at");
20388
+ const messageCreatedAtExpression = sqlTimestampMsExpression("created_at");
20389
+ const previewMessageCreatedAtExpression = sqlTimestampMsExpression("m.created_at");
19622
20390
  const rows = db().prepare(`SELECT
19623
20391
  c.id,
19624
20392
  c.kind,
@@ -19626,16 +20394,16 @@ function queryMobileSessions(limit = 50) {
19626
20394
  c.metadata_json
19627
20395
  FROM conversations c
19628
20396
  WHERE c.kind = 'direct'
19629
- ORDER BY c.created_at DESC
20397
+ ORDER BY ${conversationCreatedAtExpression} DESC
19630
20398
  LIMIT ?`).all(limit);
19631
20399
  const memberStmt = db().prepare(`SELECT actor_id FROM conversation_members WHERE conversation_id = ?`);
19632
- const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(created_at) AS last_at FROM messages WHERE conversation_id = ?`);
20400
+ const statsStmt = db().prepare(`SELECT COUNT(*) AS cnt, MAX(${messageCreatedAtExpression}) AS last_at FROM messages WHERE conversation_id = ?`);
19633
20401
  const previewStmt = db().prepare(`SELECT body
19634
20402
  FROM messages m
19635
20403
  WHERE conversation_id = ?
19636
20404
  AND actor_id != 'operator'
19637
20405
  AND ${transientBrokerWorkingStatusPredicate("m")}
19638
- ORDER BY created_at DESC LIMIT 1`);
20406
+ ORDER BY ${previewMessageCreatedAtExpression} DESC LIMIT 1`);
19639
20407
  return rows.map((r) => {
19640
20408
  const participants = memberStmt.all(r.id).map((m) => m.actor_id);
19641
20409
  const agentId = participants.find((p) => p !== "operator") ?? null;
@@ -19741,6 +20509,713 @@ function queryMobileWorkspaces(limit = 50) {
19741
20509
  }
19742
20510
  return results;
19743
20511
  }
20512
+ // ../web/server/scoutbot/directives.ts
20513
+ var ACTIONS = new Set([
20514
+ "help",
20515
+ "agents",
20516
+ "status",
20517
+ "recent",
20518
+ "doing",
20519
+ "flight",
20520
+ "steer"
20521
+ ]);
20522
+ var EFFORT_ALIASES = {
20523
+ none: "none",
20524
+ off: "none",
20525
+ minimal: "minimal",
20526
+ min: "minimal",
20527
+ low: "low",
20528
+ quick: "low",
20529
+ medium: "medium",
20530
+ med: "medium",
20531
+ mid: "medium",
20532
+ high: "high",
20533
+ deep: "high",
20534
+ xhigh: "xhigh",
20535
+ max: "xhigh"
20536
+ };
20537
+ var EFFORT_KEYS = new Set(["eff", "effort", "reasoning", "reasoning-effort", "reasoning_effort"]);
20538
+ var SESSION_KEYS = new Set(["sid", "session", "session-id", "session_id", "target-session", "target_session"]);
20539
+ function parseScoutbotDirectives(input) {
20540
+ const original = input.trim();
20541
+ const rawTokens = original.split(/\s+/).filter(Boolean);
20542
+ const directives = {};
20543
+ const bodyTokens = [];
20544
+ const messageTokens = [];
20545
+ let command = null;
20546
+ let commandIndex = -1;
20547
+ for (let index = 0;index < rawTokens.length; index += 1) {
20548
+ const token = rawTokens[index] ?? "";
20549
+ const directive = parseDirectiveToken(token);
20550
+ if (directive) {
20551
+ if (directive.kind === "reasoningEffort") {
20552
+ directives.reasoningEffort = directive.value;
20553
+ } else {
20554
+ directives.targetSessionId = directive.value;
20555
+ }
20556
+ continue;
20557
+ }
20558
+ messageTokens.push(token);
20559
+ const action = command ? null : parseActionToken(token);
20560
+ if (action) {
20561
+ command = { name: action, raw: token };
20562
+ commandIndex = index;
20563
+ continue;
20564
+ }
20565
+ bodyTokens.push(token);
20566
+ }
20567
+ const args = command ? rawTokens.slice(commandIndex + 1).filter((token) => !parseDirectiveToken(token) && !parseActionToken(token)).join(" ").trim() : "";
20568
+ return {
20569
+ original,
20570
+ body: bodyTokens.join(" ").trim(),
20571
+ messageBody: messageTokens.join(" ").trim(),
20572
+ command: command ? { ...command, args } : null,
20573
+ directives
20574
+ };
20575
+ }
20576
+ function scoutbotDirectiveMetadata(parsed) {
20577
+ const metadata = {};
20578
+ if (parsed.command)
20579
+ metadata.scoutbotAction = parsed.command.name;
20580
+ if (parsed.directives.reasoningEffort)
20581
+ metadata.reasoningEffort = parsed.directives.reasoningEffort;
20582
+ if (parsed.directives.targetSessionId)
20583
+ metadata.targetSessionId = parsed.directives.targetSessionId;
20584
+ if ((parsed.directives.reasoningEffort || parsed.directives.targetSessionId) && parsed.body && parsed.body !== parsed.original) {
20585
+ metadata.directiveBody = parsed.body;
20586
+ }
20587
+ return metadata;
20588
+ }
20589
+ function parseActionToken(rawToken) {
20590
+ const token = trimTokenBoundary(rawToken);
20591
+ const match = token.match(/^\/([A-Za-z][A-Za-z0-9_-]*)$/);
20592
+ if (!match?.[1])
20593
+ return null;
20594
+ const action = match[1].toLowerCase().replace(/-/g, "_");
20595
+ return ACTIONS.has(action) ? action : null;
20596
+ }
20597
+ function parseDirectiveToken(rawToken) {
20598
+ const token = trimTokenBoundary(rawToken);
20599
+ const separator = token.indexOf(":");
20600
+ if (separator <= 0)
20601
+ return null;
20602
+ const key = token.slice(0, separator).trim().toLowerCase();
20603
+ const value = normalizeDirectiveValue(token.slice(separator + 1));
20604
+ if (!value)
20605
+ return null;
20606
+ if (EFFORT_KEYS.has(key)) {
20607
+ const effort = EFFORT_ALIASES[value.toLowerCase()];
20608
+ return effort ? { kind: "reasoningEffort", value: effort } : null;
20609
+ }
20610
+ if (SESSION_KEYS.has(key) && isValidSessionId(value)) {
20611
+ return { kind: "targetSessionId", value };
20612
+ }
20613
+ return null;
20614
+ }
20615
+ function normalizeDirectiveValue(value) {
20616
+ return trimTokenBoundary(value).replace(/^["']+|["']+$/g, "").trim();
20617
+ }
20618
+ function trimTokenBoundary(value) {
20619
+ return value.trim().replace(/^[([{]+/g, "").replace(/[)\]},.!?;]+$/g, "");
20620
+ }
20621
+ function isValidSessionId(value) {
20622
+ return /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value);
20623
+ }
20624
+
20625
+ // ../web/server/scoutbot/role.ts
20626
+ var SCOUTBOT_AGENT_ID = "scoutbot";
20627
+ var SCOUTBOT_DISPLAY_NAME = "Scout";
20628
+ var SCOUTBOT_HANDLE = "scoutbot";
20629
+ var SCOUTBOT_DEFAULT_THREAD_ID = "thr-default";
20630
+ var SCOUTBOT_DEFAULT_THREAD_NAME = "default";
20631
+ var SCOUTBOT_DEFAULT_CONVERSATION_ID = "dm.operator.scoutbot.default";
20632
+ var SCOUTBOT_LEGACY_CONVERSATION_ID = "dm.operator.scoutbot";
20633
+ var SCOUTBOT_ENDPOINT_ID = "endpoint.scoutbot.codex_app_server";
20634
+ var SCOUTBOT_RUNTIME_INSTANCE_ID = "scoutbot-default";
20635
+ var SCOUTBOT_REASONING_EFFORT = "low";
20636
+ var SCOUTBOT_SYSTEM_PROMPT = `# Scoutbot role
20637
+
20638
+ You are Scoutbot, the operator-facing concierge for the local OpenScout fleet.
20639
+
20640
+ Your job is to read broker state, explain what is happening, and perform structured broker operations on the operator's behalf. You do not write code, edit files, or run shell commands. If a task requires project work, ask or dispatch the appropriate project agent instead of doing that work yourself.
20641
+
20642
+ ## Operating loop
20643
+
20644
+ Default to a low-effort triage pass. Your first move is to read the current broker facts and recent thread context, then answer directly if the operator is asking for status, latest activity, who is blocked, what changed, routing, or a next-action recommendation.
20645
+
20646
+ For status questions such as "what's latest on Hudson", answer from broker facts in a few bullets:
20647
+ - current state
20648
+ - most recent activity
20649
+ - blocker or risk
20650
+ - suggested next action
20651
+
20652
+ Do not start a broad investigation, inspect code, or dispatch project work unless the operator asks for that next step.
20653
+
20654
+ ## Inline answers
20655
+
20656
+ Answer inline when the request can be handled from broker state, recent messages, active flights, agent registrations, endpoint state, or known routing metadata. Keep these answers short and explicit. Separate observed facts from inference.
20657
+
20658
+ ## Offloading
20659
+
20660
+ Offload with structured broker operations when the request requires a project agent to inspect files, run commands, reproduce a bug, write code, review a diff, research a repo, operate a UI, or own multi-step work. Pick the agent by explicit routing metadata or by the closest matching project/workspace identity. If the target is ambiguous, ask one clarifying question instead of guessing.
20661
+
20662
+ Use send_message for tells/status nudges. Use ask_agent or dispatch_subagent when the meaning is "own this and report back". Use cancel_flight only for a specific active flight the operator wants stopped.
20663
+
20664
+ ## Parallelism
20665
+
20666
+ Use parallel asks only when the work naturally splits into independent lanes, for example frontend and backend investigation, reproduce and log inspection, docs and implementation review, or several agents owning separate repos. Keep fanout bounded, usually two to four agents. Do not parallelize when agents would edit the same files, depend on a single sequence, or need one owner to make a coherent decision.
20667
+
20668
+ When you fan out, tell the operator who owns each lane and what result you expect back. Prefer an explicit channel for group coordination; use DMs for one-agent work.
20669
+
20670
+ ## Tools and routing
20671
+
20672
+ Use read-only broker tools first: list_agents, list_endpoints, list_flights, latest_messages, and current_turn. For writes, use only structured broker tools: send_message, ask_agent, dispatch_subagent, and cancel_flight. No shell access. No codebase writes.
20673
+
20674
+ Routing must be explicit. Resolve targets before broker writes; do not rely on body mentions as instructions. Every broker write you emit must carry Scoutbot provenance so the operator can audit why it happened.
20675
+
20676
+ Prefer concise operational answers. Use the deterministic broker facts available to you, say when you are inferring, and keep follow-up in the same Scout thread unless the operator explicitly asks otherwise.`;
20677
+ var SCOUTBOT_ROLE_CONFIG = {
20678
+ roleId: "scoutbot",
20679
+ systemPrompt: SCOUTBOT_SYSTEM_PROMPT,
20680
+ grants: {
20681
+ read: [
20682
+ "list_agents",
20683
+ "list_endpoints",
20684
+ "list_flights",
20685
+ "latest_messages",
20686
+ "current_turn"
20687
+ ],
20688
+ write: [
20689
+ "send_message",
20690
+ "ask_agent",
20691
+ "dispatch_subagent",
20692
+ "cancel_flight"
20693
+ ],
20694
+ shell: false,
20695
+ codebaseWrites: false
20696
+ },
20697
+ defaults: {
20698
+ requestedBy: "operator",
20699
+ provenanceSource: "scoutbot",
20700
+ generatedBy: "scoutbot",
20701
+ cwdPolicy: "openscout_control_plane",
20702
+ reasoningEffort: SCOUTBOT_REASONING_EFFORT
20703
+ }
20704
+ };
20705
+
20706
+ // ../web/server/scoutbot/thread-map.ts
20707
+ init_support_paths();
20708
+ import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
20709
+ import { dirname as dirname12, join as join24 } from "path";
20710
+ class ScoutbotThreadMapStore {
20711
+ filePath;
20712
+ constructor(filePath = defaultScoutbotThreadMapPath()) {
20713
+ this.filePath = filePath;
20714
+ }
20715
+ async getThreads() {
20716
+ const map = await this.read();
20717
+ return map.threads.filter((thread) => !thread.archivedAt);
20718
+ }
20719
+ async list() {
20720
+ const map = await this.read();
20721
+ return {
20722
+ threads: map.threads.filter((thread) => !thread.archivedAt),
20723
+ defaultThreadId: map.defaultThreadId
20724
+ };
20725
+ }
20726
+ async getThread(threadId) {
20727
+ const normalized = threadId.trim();
20728
+ if (!normalized)
20729
+ return null;
20730
+ const map = await this.read();
20731
+ return map.threads.find((thread) => thread.threadId === normalized && !thread.archivedAt) ?? null;
20732
+ }
20733
+ async getThreadByConversationId(conversationId) {
20734
+ const normalized = conversationId.trim();
20735
+ if (!normalized)
20736
+ return null;
20737
+ const map = await this.read();
20738
+ return map.threads.find((thread) => thread.conversationId === normalized && !thread.archivedAt) ?? null;
20739
+ }
20740
+ async ensureDefaultThread(options) {
20741
+ const now = options.now ?? Date.now();
20742
+ const map = await this.read();
20743
+ const existing = map.threads.find((thread2) => thread2.threadId === map.defaultThreadId);
20744
+ if (existing) {
20745
+ const next = {
20746
+ ...existing,
20747
+ transportSessionId: options.transportSessionId !== undefined ? normalizeSessionId(options.transportSessionId) : existing.transportSessionId,
20748
+ transport: options.transport ?? existing.transport
20749
+ };
20750
+ if (sameThread(existing, next))
20751
+ return existing;
20752
+ const updated = replaceThread(map, next);
20753
+ await this.write(updated);
20754
+ return next;
20755
+ }
20756
+ const conversationId = chooseDefaultConversationId(options.snapshot);
20757
+ const thread = {
20758
+ threadId: SCOUTBOT_DEFAULT_THREAD_ID,
20759
+ name: SCOUTBOT_DEFAULT_THREAD_NAME,
20760
+ conversationId,
20761
+ transportSessionId: normalizeSessionId(options.transportSessionId),
20762
+ transport: options.transport ?? "codex_app_server",
20763
+ pins: null,
20764
+ lastActiveAt: now
20765
+ };
20766
+ await this.write({
20767
+ version: 1,
20768
+ defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID,
20769
+ threads: [thread]
20770
+ });
20771
+ return thread;
20772
+ }
20773
+ async createThread(name, opts) {
20774
+ const map = await this.read();
20775
+ const now = opts.now ?? Date.now();
20776
+ const threadId = opts.threadId?.trim() || `thr-${now.toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
20777
+ const conversationId = opts.conversationId?.trim() || `dm.operator.scoutbot.${threadId.replace(/^thr-/, "")}`;
20778
+ const thread = {
20779
+ threadId,
20780
+ name: name.trim() || threadId,
20781
+ conversationId,
20782
+ transportSessionId: normalizeSessionId(opts.transportSessionId),
20783
+ transport: opts.transport ?? "codex_app_server",
20784
+ pins: opts.pins ?? null,
20785
+ lastActiveAt: now
20786
+ };
20787
+ await this.write({
20788
+ ...map,
20789
+ threads: [...map.threads.filter((candidate) => candidate.threadId !== threadId), thread]
20790
+ });
20791
+ return thread;
20792
+ }
20793
+ async archiveThread(threadId) {
20794
+ const map = await this.read();
20795
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
20796
+ if (!thread || thread.threadId === map.defaultThreadId)
20797
+ return null;
20798
+ const archived = { ...thread, archivedAt: Date.now() };
20799
+ await this.write(replaceThread(map, archived));
20800
+ return archived;
20801
+ }
20802
+ async touchThread(threadId, now = Date.now()) {
20803
+ const map = await this.read();
20804
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
20805
+ if (!thread)
20806
+ return;
20807
+ await this.write(replaceThread(map, { ...thread, lastActiveAt: now }));
20808
+ }
20809
+ async setThreadTransportSessionId(threadId, transportSessionId, now = Date.now()) {
20810
+ const normalized = normalizeSessionId(transportSessionId);
20811
+ const map = await this.read();
20812
+ const thread = map.threads.find((candidate) => candidate.threadId === threadId);
20813
+ if (!thread)
20814
+ return null;
20815
+ const next = { ...thread, transportSessionId: normalized, lastActiveAt: now };
20816
+ if (sameThread(thread, next))
20817
+ return thread;
20818
+ await this.write(replaceThread(map, next));
20819
+ return next;
20820
+ }
20821
+ async read() {
20822
+ try {
20823
+ const raw = await readFile7(this.filePath, "utf8");
20824
+ const parsed = JSON.parse(raw);
20825
+ const threads = Array.isArray(parsed.threads) ? parsed.threads.filter(isThreadRecord).map((thread) => ({
20826
+ ...thread,
20827
+ transportSessionId: normalizeSessionId(thread.transportSessionId)
20828
+ })) : [];
20829
+ return {
20830
+ version: 1,
20831
+ defaultThreadId: typeof parsed.defaultThreadId === "string" && parsed.defaultThreadId.trim() ? parsed.defaultThreadId : SCOUTBOT_DEFAULT_THREAD_ID,
20832
+ threads
20833
+ };
20834
+ } catch (error) {
20835
+ if (error.code === "ENOENT") {
20836
+ return { version: 1, defaultThreadId: SCOUTBOT_DEFAULT_THREAD_ID, threads: [] };
20837
+ }
20838
+ throw error;
20839
+ }
20840
+ }
20841
+ async write(map) {
20842
+ await mkdir7(dirname12(this.filePath), { recursive: true });
20843
+ await writeFile7(this.filePath, `${JSON.stringify(map, null, 2)}
20844
+ `, "utf8");
20845
+ }
20846
+ }
20847
+ function defaultScoutbotThreadMapPath() {
20848
+ return join24(resolveOpenScoutSupportPaths().supportDirectory, "scoutbot-threads.json");
20849
+ }
20850
+ function chooseDefaultConversationId(snapshot) {
20851
+ return snapshot?.conversations?.[SCOUTBOT_LEGACY_CONVERSATION_ID] ? SCOUTBOT_LEGACY_CONVERSATION_ID : SCOUTBOT_DEFAULT_CONVERSATION_ID;
20852
+ }
20853
+ function buildScoutbotThreadConversation(thread, snapshot, nodeId) {
20854
+ const existing = snapshot.conversations[thread.conversationId];
20855
+ if (existing)
20856
+ return null;
20857
+ const participantIds = ["operator", "scoutbot"].sort();
20858
+ return {
20859
+ id: thread.conversationId,
20860
+ kind: "direct",
20861
+ title: thread.name === SCOUTBOT_DEFAULT_THREAD_NAME ? "Scout \xB7 default" : `Scout \xB7 ${thread.name}`,
20862
+ visibility: "private",
20863
+ shareMode: "local",
20864
+ authorityNodeId: nodeId,
20865
+ participantIds,
20866
+ metadata: {
20867
+ surface: "scoutbot",
20868
+ scoutbotThreadId: thread.threadId,
20869
+ transportSessionId: thread.transportSessionId,
20870
+ transport: thread.transport
20871
+ }
20872
+ };
20873
+ }
20874
+ function isThreadRecord(value) {
20875
+ if (!value || typeof value !== "object")
20876
+ return false;
20877
+ const record = value;
20878
+ return typeof record.threadId === "string" && typeof record.name === "string" && typeof record.conversationId === "string" && (typeof record.transportSessionId === "string" || record.transportSessionId === null || record.transportSessionId === undefined) && typeof record.transport === "string" && typeof record.lastActiveAt === "number";
20879
+ }
20880
+ function normalizeSessionId(value) {
20881
+ const normalized = value?.trim();
20882
+ return normalized ? normalized : null;
20883
+ }
20884
+ function replaceThread(map, thread) {
20885
+ return {
20886
+ ...map,
20887
+ threads: map.threads.map((candidate) => candidate.threadId === thread.threadId ? thread : candidate)
20888
+ };
20889
+ }
20890
+ function sameThread(left, right) {
20891
+ return JSON.stringify(left) === JSON.stringify(right);
20892
+ }
20893
+
20894
+ // ../web/server/scoutbot/runner.ts
20895
+ var defaultLog = {
20896
+ info: (msg) => {
20897
+ console.log(`[scoutbot] ${msg}`);
20898
+ },
20899
+ warn: (msg) => {
20900
+ console.warn(`[scoutbot] ${msg}`);
20901
+ }
20902
+ };
20903
+ async function postScoutbotOperatorMessage(input) {
20904
+ const log2 = input.log ?? defaultLog;
20905
+ const baseUrl = input.brokerBaseUrl ?? resolveScoutBrokerUrl();
20906
+ const threadMap = input.threadMap ?? new ScoutbotThreadMapStore;
20907
+ if (input.bootstrap) {
20908
+ const prepared = await ensureScoutbotBootstrapped({
20909
+ baseUrl,
20910
+ currentDirectory: input.currentDirectory,
20911
+ threadMap,
20912
+ log: log2
20913
+ });
20914
+ if (!prepared) {
20915
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
20916
+ }
20917
+ }
20918
+ const thread = await resolvePostTargetThread({
20919
+ baseUrl,
20920
+ threadMap,
20921
+ threadId: input.threadId
20922
+ });
20923
+ if (!thread) {
20924
+ if (input.threadId?.trim()) {
20925
+ throw new Error(`Unknown scoutbot thread ${input.threadId}`);
20926
+ }
20927
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
20928
+ }
20929
+ if (input.threadId?.trim() && thread.threadId !== input.threadId.trim()) {
20930
+ throw new Error(`Unknown scoutbot thread ${input.threadId}`);
20931
+ }
20932
+ return postOperatorThreadMessage({
20933
+ baseUrl,
20934
+ thread,
20935
+ body: input.body,
20936
+ source: input.source ?? "scout-web",
20937
+ clientMessageId: input.clientMessageId,
20938
+ replyToMessageId: input.replyToMessageId,
20939
+ referenceMessageIds: input.referenceMessageIds,
20940
+ deviceId: input.deviceId,
20941
+ log: log2
20942
+ });
20943
+ }
20944
+ async function resolvePostTargetThread(input) {
20945
+ const explicitThreadId = input.threadId?.trim();
20946
+ if (explicitThreadId) {
20947
+ return input.threadMap.getThread(explicitThreadId);
20948
+ }
20949
+ const threadList = await input.threadMap.list();
20950
+ const existing = threadList.threads.find((candidate) => candidate.threadId === threadList.defaultThreadId) ?? null;
20951
+ if (existing)
20952
+ return existing;
20953
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
20954
+ if (!ctx)
20955
+ return null;
20956
+ return input.threadMap.ensureDefaultThread({
20957
+ snapshot: ctx.snapshot,
20958
+ transportSessionId: null,
20959
+ transport: "codex_app_server"
20960
+ });
20961
+ }
20962
+ async function ensureScoutbotBootstrapped(input) {
20963
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
20964
+ if (!ctx) {
20965
+ input.log.warn(`broker unreachable at ${input.baseUrl}; runner is inert`);
20966
+ return false;
20967
+ }
20968
+ await ensureScoutbotRegistered(input.baseUrl, ctx.snapshot, ctx.node.id, input.currentDirectory, input.log);
20969
+ const refreshed = await loadScoutBrokerContext(input.baseUrl);
20970
+ const snapshot = refreshed?.snapshot ?? ctx.snapshot;
20971
+ const nodeId = refreshed?.node.id ?? ctx.node.id;
20972
+ const endpoint = findScoutbotEndpoint(snapshot, nodeId) ?? buildScoutbotEndpoint(nodeId, input.currentDirectory);
20973
+ const warmedEndpoint = await ensureScoutbotEndpointSession(input.baseUrl, endpoint, input.log);
20974
+ const transportSessionId = scoutbotEndpointTransportSessionId(warmedEndpoint);
20975
+ const thread = await input.threadMap.ensureDefaultThread({
20976
+ snapshot,
20977
+ transportSessionId,
20978
+ transport: endpoint.transport ?? "codex_app_server"
20979
+ });
20980
+ const latest = await loadScoutBrokerContext(input.baseUrl);
20981
+ const conversation = buildScoutbotThreadConversation(thread, latest?.snapshot ?? snapshot, latest?.node.id ?? nodeId);
20982
+ if (conversation) {
20983
+ await postJson(input.baseUrl, "/v1/conversations", conversation);
20984
+ }
20985
+ return true;
20986
+ }
20987
+ async function ensureScoutbotRegistered(baseUrl, snapshot, nodeId, currentDirectory, log2) {
20988
+ const actor = buildScoutbotActor(nodeId);
20989
+ const agent = buildScoutbotAgent(nodeId);
20990
+ const endpoint = normalizeScoutbotEndpoint(findScoutbotEndpoint(snapshot, nodeId), buildScoutbotEndpoint(nodeId, currentDirectory));
20991
+ if (!snapshot.actors?.[SCOUTBOT_AGENT_ID]) {
20992
+ await postJson(baseUrl, "/v1/actors", actor);
20993
+ }
20994
+ const existingAgent = snapshot.agents?.[SCOUTBOT_AGENT_ID];
20995
+ if (!existingAgent || !hasScoutbotLabels(existingAgent) || !hasCurrentScoutbotAgentConfig(existingAgent)) {
20996
+ await postJson(baseUrl, "/v1/agents", agent);
20997
+ }
20998
+ const existingEndpoint = findScoutbotEndpoint(snapshot, nodeId);
20999
+ if (!existingEndpoint || existingEndpoint.state === "offline" || existingEndpoint.metadata?.source !== "scoutbot" || !hasCurrentScoutbotRuntimeConfig(existingEndpoint) || isInvalidScoutbotSessionEndpoint(existingEndpoint)) {
21000
+ await postJson(baseUrl, "/v1/endpoints", endpoint);
21001
+ log2.info(`registered endpoint ${endpoint.id}`);
21002
+ }
21003
+ }
21004
+ function buildScoutbotActor(_nodeId) {
21005
+ return {
21006
+ id: SCOUTBOT_AGENT_ID,
21007
+ kind: "agent",
21008
+ displayName: SCOUTBOT_DISPLAY_NAME,
21009
+ handle: SCOUTBOT_HANDLE,
21010
+ labels: ["assistant", "scout", "scoutbot"],
21011
+ metadata: {
21012
+ source: "scoutbot",
21013
+ role: "operator-assistant"
21014
+ }
21015
+ };
21016
+ }
21017
+ function buildScoutbotAgent(nodeId) {
21018
+ return {
21019
+ ...buildScoutbotActor(nodeId),
21020
+ kind: "agent",
21021
+ definitionId: SCOUTBOT_HANDLE,
21022
+ selector: `@${SCOUTBOT_HANDLE}`,
21023
+ defaultSelector: `@${SCOUTBOT_HANDLE}`,
21024
+ agentClass: "operator",
21025
+ capabilities: ["chat", "invoke", "deliver"],
21026
+ wakePolicy: "keep_warm",
21027
+ homeNodeId: nodeId,
21028
+ authorityNodeId: nodeId,
21029
+ advertiseScope: "local",
21030
+ metadata: {
21031
+ source: "scoutbot",
21032
+ role: "operator-assistant",
21033
+ summary: "Operator-facing fleet concierge backed by the broker's local session adapter.",
21034
+ roleConfig: SCOUTBOT_ROLE_CONFIG
21035
+ }
21036
+ };
21037
+ }
21038
+ function buildScoutbotEndpoint(nodeId, currentDirectory) {
21039
+ const reasoningEffort = SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
21040
+ return {
21041
+ id: `${SCOUTBOT_ENDPOINT_ID}.${nodeId}`,
21042
+ agentId: SCOUTBOT_AGENT_ID,
21043
+ nodeId,
21044
+ harness: "codex",
21045
+ transport: "codex_app_server",
21046
+ state: "waiting",
21047
+ cwd: currentDirectory,
21048
+ projectRoot: currentDirectory,
21049
+ metadata: {
21050
+ source: "scoutbot",
21051
+ managedByScout: true,
21052
+ sessionBacked: true,
21053
+ externalSource: "local-session",
21054
+ attachedTransport: "codex_app_server",
21055
+ agentName: SCOUTBOT_AGENT_ID,
21056
+ definitionId: SCOUTBOT_HANDLE,
21057
+ runtimeInstanceId: SCOUTBOT_RUNTIME_INSTANCE_ID,
21058
+ roleConfig: SCOUTBOT_ROLE_CONFIG,
21059
+ systemPrompt: SCOUTBOT_ROLE_CONFIG.systemPrompt,
21060
+ toolGrants: SCOUTBOT_ROLE_CONFIG.grants,
21061
+ reasoningEffort,
21062
+ launchArgs: ["--reasoning-effort", reasoningEffort],
21063
+ projectRoot: currentDirectory,
21064
+ transport: "codex_app_server",
21065
+ startedAt: String(Date.now())
21066
+ }
21067
+ };
21068
+ }
21069
+ function findScoutbotEndpoint(snapshot, nodeId) {
21070
+ const endpoints = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === SCOUTBOT_AGENT_ID && endpoint.transport === "codex_app_server");
21071
+ return endpoints.find((endpoint) => endpoint.nodeId === nodeId && endpoint.state !== "offline") ?? endpoints.find((endpoint) => endpoint.state !== "offline") ?? endpoints[0] ?? null;
21072
+ }
21073
+ function normalizeScoutbotEndpoint(existing, fallback) {
21074
+ if (!existing || isInvalidScoutbotSessionEndpoint(existing))
21075
+ return fallback;
21076
+ const existingMetadata = { ...existing.metadata ?? {} };
21077
+ delete existingMetadata.threadId;
21078
+ delete existingMetadata.externalSessionId;
21079
+ delete existingMetadata.targetSessionId;
21080
+ return {
21081
+ ...fallback,
21082
+ id: existing.id || fallback.id,
21083
+ state: existing.state === "offline" ? "waiting" : existing.state,
21084
+ metadata: {
21085
+ ...existingMetadata,
21086
+ ...fallback.metadata ?? {}
21087
+ }
21088
+ };
21089
+ }
21090
+ function isInvalidScoutbotSessionEndpoint(endpoint) {
21091
+ const lastError = String(endpoint.metadata?.lastError ?? "").toLowerCase();
21092
+ return lastError.includes("no rollout found for thread id") || lastError.includes("codex_app_server session unavailable") || lastError.includes("session unavailable");
21093
+ }
21094
+ function hasCurrentScoutbotRuntimeConfig(endpoint) {
21095
+ return metadataString5(endpoint.metadata, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt && metadataString5(endpoint.metadata, "reasoningEffort") === SCOUTBOT_ROLE_CONFIG.defaults.reasoningEffort;
21096
+ }
21097
+ function scoutbotEndpointTransportSessionId(endpoint) {
21098
+ if (isInvalidScoutbotSessionEndpoint(endpoint))
21099
+ return null;
21100
+ return metadataString5(endpoint.metadata, "threadId") ?? metadataString5(endpoint.metadata, "externalSessionId") ?? endpoint.sessionId?.trim() ?? null;
21101
+ }
21102
+ async function ensureScoutbotEndpointSession(baseUrl, endpoint, log2) {
21103
+ try {
21104
+ const result = await postJson(baseUrl, "/v1/local-sessions/ensure", { agentId: endpoint.agentId, endpointId: endpoint.id }, { timeoutMs: 2000 });
21105
+ return result.endpoint ?? endpoint;
21106
+ } catch (error) {
21107
+ log2.warn(`session warmup skipped: ${describeError(error)}`);
21108
+ return endpoint;
21109
+ }
21110
+ }
21111
+ async function postOperatorThreadMessage(input) {
21112
+ const ctx = await loadScoutBrokerContext(input.baseUrl);
21113
+ if (!ctx) {
21114
+ return { usedBroker: false, invokedTargets: [], unresolvedTargets: [] };
21115
+ }
21116
+ const conversation = ctx.snapshot.conversations[input.thread.conversationId] ?? buildScoutbotThreadConversation(input.thread, ctx.snapshot, ctx.node.id);
21117
+ if (conversation && !ctx.snapshot.conversations[input.thread.conversationId]) {
21118
+ await postJson(input.baseUrl, "/v1/conversations", conversation);
21119
+ }
21120
+ const parsed = parseScoutbotDirectives(input.body);
21121
+ const now = Date.now();
21122
+ const messageId = createId("msg");
21123
+ const transportSessionId = parsed.directives.targetSessionId ?? input.thread.transportSessionId?.trim() ?? null;
21124
+ await postJson(input.baseUrl, "/v1/messages", {
21125
+ id: messageId,
21126
+ conversationId: input.thread.conversationId,
21127
+ actorId: "operator",
21128
+ originNodeId: ctx.node.id,
21129
+ class: "agent",
21130
+ body: input.body.trim(),
21131
+ replyToMessageId: input.replyToMessageId ?? undefined,
21132
+ mentions: [{ actorId: SCOUTBOT_AGENT_ID, label: "@scoutbot" }],
21133
+ audience: { notify: [SCOUTBOT_AGENT_ID], reason: "direct_message" },
21134
+ visibility: "private",
21135
+ policy: "durable",
21136
+ createdAt: now,
21137
+ metadata: {
21138
+ source: input.source,
21139
+ ...scoutbotDirectiveMetadata(parsed),
21140
+ destinationKind: "scoutbot_thread",
21141
+ destinationId: input.thread.threadId,
21142
+ scoutbotThreadId: input.thread.threadId,
21143
+ ...transportSessionId ? { targetSessionId: transportSessionId } : {},
21144
+ referenceMessageIds: input.referenceMessageIds ?? [],
21145
+ clientMessageId: input.clientMessageId ?? null,
21146
+ ...input.deviceId ? { deviceId: input.deviceId } : {},
21147
+ relayMessageId: messageId,
21148
+ returnAddress: {
21149
+ actorId: "operator",
21150
+ conversationId: input.thread.conversationId,
21151
+ replyToMessageId: messageId,
21152
+ ...transportSessionId ? { sessionId: transportSessionId } : {}
21153
+ }
21154
+ }
21155
+ });
21156
+ return {
21157
+ usedBroker: true,
21158
+ threadId: input.thread.threadId,
21159
+ conversationId: input.thread.conversationId,
21160
+ messageId,
21161
+ invokedTargets: [SCOUTBOT_AGENT_ID],
21162
+ unresolvedTargets: []
21163
+ };
21164
+ }
21165
+ var TERMINAL_FLIGHT_STATES2 = new Set(["completed", "failed", "cancelled"]);
21166
+ async function postJson(baseUrl, path2, body, options = {}) {
21167
+ const controller = options.timeoutMs ? new AbortController : null;
21168
+ const timer = controller && options.timeoutMs ? setTimeout(() => controller.abort(), options.timeoutMs) : null;
21169
+ try {
21170
+ const res = await fetch(new URL(path2, baseUrl), {
21171
+ method: "POST",
21172
+ headers: { "content-type": "application/json" },
21173
+ body: JSON.stringify(body),
21174
+ ...controller ? { signal: controller.signal } : {}
21175
+ });
21176
+ if (!res.ok) {
21177
+ const text = await res.text().catch(() => "");
21178
+ throw new Error(`POST ${path2} ${res.status}${text ? ` - ${text}` : ""}`);
21179
+ }
21180
+ return await res.json().catch(() => ({ ok: true }));
21181
+ } finally {
21182
+ if (timer)
21183
+ clearTimeout(timer);
21184
+ }
21185
+ }
21186
+ function hasScoutbotLabels(agent) {
21187
+ const labels = new Set(agent.labels ?? []);
21188
+ return labels.has("assistant") && labels.has("scout") && labels.has("scoutbot");
21189
+ }
21190
+ function hasCurrentScoutbotAgentConfig(agent) {
21191
+ const roleConfig = metadataObject(agent.metadata, "roleConfig");
21192
+ return metadataString5(roleConfig, "systemPrompt") === SCOUTBOT_ROLE_CONFIG.systemPrompt;
21193
+ }
21194
+ function metadataObject(metadata, key) {
21195
+ const value = metadata?.[key];
21196
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
21197
+ }
21198
+ function metadataString5(metadata, key) {
21199
+ const value = metadata?.[key];
21200
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
21201
+ }
21202
+ var SCOUTBOT_REASONING_EFFORTS = new Set([
21203
+ "none",
21204
+ "minimal",
21205
+ "low",
21206
+ "medium",
21207
+ "high",
21208
+ "xhigh"
21209
+ ]);
21210
+ function createId(prefix) {
21211
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
21212
+ }
21213
+ function describeError(cause) {
21214
+ if (cause instanceof Error)
21215
+ return cause.message;
21216
+ return String(cause);
21217
+ }
21218
+
19744
21219
  // ../web/server/core/mobile/service.ts
19745
21220
  var DEFAULT_MOBILE_RECENT_TURN_LIMIT = 24;
19746
21221
  var DEFAULT_MOBILE_HISTORY_PAGE_LIMIT = 40;
@@ -19754,10 +21229,31 @@ function normalizeTimestampMs2(value) {
19754
21229
  return null;
19755
21230
  return value > 10000000000 ? Math.floor(value) : Math.floor(value * 1000);
19756
21231
  }
19757
- function metadataString5(metadata, key) {
21232
+ function metadataString6(metadata, key) {
19758
21233
  const value = metadata?.[key];
19759
21234
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
19760
21235
  }
21236
+ function metadataBoolean2(metadata, key) {
21237
+ return metadata?.[key] === true;
21238
+ }
21239
+ function isBrokerRequesterWaitTimeoutStatusMessage(message) {
21240
+ if (message.class !== "status" || metadataString6(message.metadata, "source") !== "broker") {
21241
+ return false;
21242
+ }
21243
+ return message.body.includes("Scout stopped waiting for a synchronous result") || message.body.includes("the requester stopped waiting after");
21244
+ }
21245
+ function isRequesterWaitTimeoutFlight(flight) {
21246
+ return metadataBoolean2(flight.metadata, "requesterTimedOut") || metadataString6(flight.metadata, "timeoutScope") === "requester_wait" || Boolean(flight.summary?.includes("Scout stopped waiting for a synchronous result"));
21247
+ }
21248
+ function isInactiveAgent(agent) {
21249
+ return metadataBoolean2(agent?.metadata, "retiredFromFleet") || metadataBoolean2(agent?.metadata, "staleLocalRegistration");
21250
+ }
21251
+ function isInactiveEndpoint(snapshot, endpoint) {
21252
+ if (!endpoint) {
21253
+ return true;
21254
+ }
21255
+ return metadataBoolean2(endpoint.metadata, "retiredFromFleet") || metadataBoolean2(endpoint.metadata, "staleLocalRegistration") || isInactiveAgent(snapshot.agents[endpoint.agentId]);
21256
+ }
19761
21257
  function titleCaseToken(value) {
19762
21258
  return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
19763
21259
  }
@@ -19812,6 +21308,9 @@ async function loadMobileWorkspaceInventory(currentDirectory) {
19812
21308
  function latestMessageByConversation(snapshot) {
19813
21309
  const buckets = new Map;
19814
21310
  for (const message of Object.values(snapshot.messages)) {
21311
+ if (isBrokerRequesterWaitTimeoutStatusMessage(message)) {
21312
+ continue;
21313
+ }
19815
21314
  const next = buckets.get(message.conversationId) ?? [];
19816
21315
  next.push(message);
19817
21316
  buckets.set(message.conversationId, next);
@@ -19830,14 +21329,14 @@ function agentDisplayName(snapshot, agentId) {
19830
21329
  return snapshot.agents[agentId]?.displayName ?? snapshot.actors[agentId]?.displayName ?? agentId;
19831
21330
  }
19832
21331
  function endpointForAgent(snapshot, agentId) {
19833
- return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId) ?? null;
21332
+ return Object.values(snapshot.endpoints).find((endpoint) => endpoint.agentId === agentId && !isInactiveEndpoint(snapshot, endpoint)) ?? null;
19834
21333
  }
19835
21334
  function buildMobileAgentSummary(snapshot, agent) {
19836
21335
  const endpoint = endpointForAgent(snapshot, agent.id);
19837
21336
  const flights = Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agent.id);
19838
21337
  const hasWorkingFlight = flights.some((flight) => flight.state === "running");
19839
21338
  const lastAuthoredMessageAt = Object.values(snapshot.messages).filter((message) => message.actorId === agent.id).reduce((latest, message) => {
19840
- const createdAt = normalizeTimestamp2(message.createdAt);
21339
+ const createdAt = normalizeTimestampMs2(message.createdAt);
19841
21340
  return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
19842
21341
  }, null);
19843
21342
  const state = hasWorkingFlight ? "working" : endpoint && endpoint.state !== "offline" ? "available" : "offline";
@@ -19862,7 +21361,7 @@ function buildMobileSessionSummaries(snapshot) {
19862
21361
  const latestMessage = messages2.at(-1) ?? null;
19863
21362
  const directAgentId = conversation.kind === "direct" ? conversation.participantIds.find((participantId) => participantId !== "operator") ?? null : null;
19864
21363
  const agent = directAgentId ? snapshot.agents[directAgentId] ?? null : null;
19865
- if (!directAgentId || !agent || messages2.length === 0) {
21364
+ if (!directAgentId || !agent || isInactiveAgent(agent) || messages2.length === 0) {
19866
21365
  return [];
19867
21366
  }
19868
21367
  const endpoint = endpointForAgent(snapshot, directAgentId);
@@ -19877,10 +21376,10 @@ function buildMobileSessionSummaries(snapshot) {
19877
21376
  agentId: directAgentId,
19878
21377
  agentName: directAgentId ? agentDisplayName(snapshot, directAgentId) : null,
19879
21378
  harness: endpoint?.harness ?? null,
19880
- currentBranch: metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier"),
21379
+ currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
19881
21380
  preview: latestMessage?.body ?? null,
19882
21381
  messageCount: messages2.length,
19883
- lastMessageAt: normalizeTimestamp2(latestMessage?.createdAt),
21382
+ lastMessageAt: normalizeTimestampMs2(latestMessage?.createdAt),
19884
21383
  workspaceRoot: endpoint?.projectRoot ?? endpoint?.cwd ?? null
19885
21384
  }];
19886
21385
  });
@@ -19888,7 +21387,7 @@ function buildMobileSessionSummaries(snapshot) {
19888
21387
  for (const summary of summaries) {
19889
21388
  const agent = summary.agentId ? snapshot.agents[summary.agentId] : null;
19890
21389
  const endpoint = summary.agentId ? endpointForAgent(snapshot, summary.agentId) : null;
19891
- const branchQualifier = metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier");
21390
+ const branchQualifier = metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier");
19892
21391
  const identityKey = [
19893
21392
  endpoint?.projectRoot?.trim().toLowerCase(),
19894
21393
  endpoint?.cwd?.trim().toLowerCase(),
@@ -19904,7 +21403,7 @@ function buildMobileSessionSummaries(snapshot) {
19904
21403
  return [...deduped.values()].sort((left, right) => (right.lastMessageAt ?? 0) - (left.lastMessageAt ?? 0));
19905
21404
  }
19906
21405
  function messagesForConversation(snapshot, conversationId) {
19907
- return Object.values(snapshot.messages).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
21406
+ return Object.values(snapshot.messages).filter((message) => !isBrokerRequesterWaitTimeoutStatusMessage(message)).filter((message) => message.conversationId === conversationId).sort((left, right) => (normalizeTimestamp2(left.createdAt) ?? 0) - (normalizeTimestamp2(right.createdAt) ?? 0));
19908
21407
  }
19909
21408
  function pageMessagesForConversation(snapshot, conversationId, options = {}) {
19910
21409
  const allMessages = messagesForConversation(snapshot, conversationId);
@@ -19941,7 +21440,7 @@ function pageMessagesForConversation(snapshot, conversationId, options = {}) {
19941
21440
  function latestActiveFlightForAgent(snapshot, agentId) {
19942
21441
  if (!agentId)
19943
21442
  return null;
19944
- return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId && !["completed", "failed", "cancelled"].includes(flight.state)).sort((left, right) => (normalizeTimestamp2(right.startedAt) ?? 0) - (normalizeTimestamp2(left.startedAt) ?? 0))[0] ?? null;
21443
+ return Object.values(snapshot.flights).filter((flight) => flight.targetAgentId === agentId && (flight.state === "running" || flight.state === "waiting") && !isRequesterWaitTimeoutFlight(flight)).sort((left, right) => (normalizeTimestamp2(right.startedAt) ?? 0) - (normalizeTimestamp2(left.startedAt) ?? 0))[0] ?? null;
19945
21444
  }
19946
21445
  async function loadMobileRelayState() {
19947
21446
  const broker = await loadScoutBrokerContext();
@@ -19949,7 +21448,10 @@ async function loadMobileRelayState() {
19949
21448
  return { agents: [], sessions: [] };
19950
21449
  }
19951
21450
  const snapshot = broker.snapshot;
19952
- const agents = Object.values(snapshot.agents).map((agent) => buildMobileAgentSummary(snapshot, agent)).sort((left, right) => (right.lastActiveAt ?? 0) - (left.lastActiveAt ?? 0) || left.title.localeCompare(right.title));
21451
+ const agents = Object.values(snapshot.agents).filter((agent) => !isInactiveAgent(agent)).filter((agent) => {
21452
+ const endpoints = Object.values(snapshot.endpoints).filter((endpoint) => endpoint.agentId === agent.id);
21453
+ return endpoints.length === 0 || endpoints.some((endpoint) => !isInactiveEndpoint(snapshot, endpoint));
21454
+ }).map((agent) => buildMobileAgentSummary(snapshot, agent)).sort((left, right) => (right.lastActiveAt ?? 0) - (left.lastActiveAt ?? 0) || left.title.localeCompare(right.title));
19953
21455
  return {
19954
21456
  agents,
19955
21457
  sessions: buildMobileSessionSummaries(snapshot)
@@ -20028,10 +21530,10 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
20028
21530
  const messages2 = messagePage.messages;
20029
21531
  const activeFlight = latestActiveFlightForAgent(snapshot, directAgentId);
20030
21532
  const lastAgentMessageAt = messages2.filter((message) => message.actorId === directAgentId).reduce((latest, message) => {
20031
- const createdAt = normalizeTimestamp2(message.createdAt);
21533
+ const createdAt = normalizeTimestampMs2(message.createdAt);
20032
21534
  return typeof createdAt === "number" && (!latest || createdAt > latest) ? createdAt : latest;
20033
21535
  }, null);
20034
- const shouldShowWorkingTurn = Boolean(activeFlight && (normalizeTimestamp2(activeFlight.startedAt) ?? 0) > (lastAgentMessageAt ?? 0));
21536
+ const shouldShowWorkingTurn = Boolean(activeFlight && (normalizeTimestampMs2(activeFlight.startedAt) ?? 0) > (lastAgentMessageAt ?? 0));
20035
21537
  const turns = messages2.map((message) => ({
20036
21538
  id: message.id,
20037
21539
  status: "completed",
@@ -20086,8 +21588,8 @@ async function getScoutMobileSessionSnapshot(conversationId, options = {}, curre
20086
21588
  selector: agent?.selector ?? null,
20087
21589
  defaultSelector: agent?.defaultSelector ?? null,
20088
21590
  project: directAgentId ? agentDisplayName(snapshot, directAgentId) : conversation.title,
20089
- currentBranch: metadataString5(endpoint?.metadata, "branch") ?? metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "branch") ?? metadataString5(agent?.metadata, "workspaceQualifier"),
20090
- workspaceQualifier: metadataString5(endpoint?.metadata, "workspaceQualifier") ?? metadataString5(agent?.metadata, "workspaceQualifier")
21591
+ currentBranch: metadataString6(endpoint?.metadata, "branch") ?? metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "branch") ?? metadataString6(agent?.metadata, "workspaceQualifier"),
21592
+ workspaceQualifier: metadataString6(endpoint?.metadata, "workspaceQualifier") ?? metadataString6(agent?.metadata, "workspaceQualifier")
20091
21593
  }
20092
21594
  },
20093
21595
  history: {
@@ -20105,7 +21607,7 @@ async function createScoutSession(input, currentDirectory, deviceId) {
20105
21607
  throw new Error(`Invalid workspaceId.`);
20106
21608
  }
20107
21609
  const workspaceRoot = resolve7(rawWorkspaceId);
20108
- const projectName = basename9(workspaceRoot) || workspaceRoot;
21610
+ const projectName = basename10(workspaceRoot) || workspaceRoot;
20109
21611
  const workspace = {
20110
21612
  id: workspaceRoot,
20111
21613
  title: projectName,
@@ -20180,6 +21682,24 @@ async function createScoutSession(input, currentDirectory, deviceId) {
20180
21682
  };
20181
21683
  }
20182
21684
  async function sendScoutMobileMessage(input, currentDirectory, deviceId) {
21685
+ if (input.agentId === SCOUTBOT_AGENT_ID) {
21686
+ const result = await postScoutbotOperatorMessage({
21687
+ currentDirectory: currentDirectory ?? process.cwd(),
21688
+ body: input.body,
21689
+ source: "scout-mobile",
21690
+ clientMessageId: input.clientMessageId,
21691
+ replyToMessageId: input.replyToMessageId,
21692
+ referenceMessageIds: input.referenceMessageIds,
21693
+ deviceId
21694
+ });
21695
+ if (!result.usedBroker || !result.conversationId || !result.messageId) {
21696
+ throw new Error("Scoutbot is not available.");
21697
+ }
21698
+ return {
21699
+ conversationId: result.conversationId,
21700
+ messageId: result.messageId
21701
+ };
21702
+ }
20183
21703
  return sendScoutDirectMessage({
20184
21704
  agentId: input.agentId,
20185
21705
  body: input.body,
@@ -20216,8 +21736,8 @@ async function deriveNewAgentName(projectName, branch, harness) {
20216
21736
  }
20217
21737
  async function createGitWorktree(projectRoot, agentName, requestedBranch) {
20218
21738
  const { execFileSync: execFileSync5 } = await import("child_process");
20219
- const { join: join23 } = await import("path");
20220
- const { mkdirSync: mkdirSync10, existsSync: existsSync18 } = await import("fs");
21739
+ const { join: join25 } = await import("path");
21740
+ const { mkdirSync: mkdirSync11, existsSync: existsSync19 } = await import("fs");
20221
21741
  try {
20222
21742
  execFileSync5("git", ["rev-parse", "--git-dir"], { cwd: projectRoot, stdio: "pipe" });
20223
21743
  } catch {
@@ -20225,12 +21745,12 @@ async function createGitWorktree(projectRoot, agentName, requestedBranch) {
20225
21745
  }
20226
21746
  const normalizedRequestedBranch = requestedBranch?.trim();
20227
21747
  const branchName = normalizedRequestedBranch || `scout/${agentName}`;
20228
- const worktreeDir = join23(projectRoot, ".scout-worktrees");
20229
- const worktreePath = join23(worktreeDir, agentName);
20230
- if (existsSync18(worktreePath)) {
21748
+ const worktreeDir = join25(projectRoot, ".scout-worktrees");
21749
+ const worktreePath = join25(worktreeDir, agentName);
21750
+ if (existsSync19(worktreePath)) {
20231
21751
  return { path: worktreePath, branch: branchName };
20232
21752
  }
20233
- mkdirSync10(worktreeDir, { recursive: true });
21753
+ mkdirSync11(worktreeDir, { recursive: true });
20234
21754
  try {
20235
21755
  execFileSync5("git", ["worktree", "add", "-b", branchName, worktreePath], { cwd: projectRoot, stdio: "pipe" });
20236
21756
  return { path: worktreePath, branch: branchName };
@@ -20431,9 +21951,9 @@ async function handleRPCInner(bridge, req, deviceId) {
20431
21951
  }
20432
21952
  case "session/resume": {
20433
21953
  const p = req.params;
20434
- const sessionFilename = basename10(p.sessionPath, ".jsonl");
21954
+ const sessionFilename = basename11(p.sessionPath, ".jsonl");
20435
21955
  const parentDir = p.sessionPath.substring(0, p.sessionPath.lastIndexOf("/"));
20436
- const dirName = basename10(parentDir);
21956
+ const dirName = basename11(parentDir);
20437
21957
  let cwd;
20438
21958
  if (dirName.startsWith("-")) {
20439
21959
  const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
@@ -20500,7 +22020,7 @@ async function handleRPCInner(bridge, req, deviceId) {
20500
22020
  return { id: req.id, error: { code: -32000, message: "Workspace target is not a directory" } };
20501
22021
  }
20502
22022
  const adapterType = p.adapter ?? "claude-code";
20503
- const name = p.name ?? basename10(projectPath);
22023
+ const name = p.name ?? basename11(projectPath);
20504
22024
  const session = await bridge.createSession(adapterType, {
20505
22025
  name,
20506
22026
  cwd: projectPath
@@ -20622,7 +22142,7 @@ async function handleRPCInner(bridge, req, deviceId) {
20622
22142
  return { id: req.id, error: { code: -32000, message: "Only .jsonl files can be read" } };
20623
22143
  }
20624
22144
  try {
20625
- const content = readFileSync12(p.path, "utf-8");
22145
+ const content = readFileSync13(p.path, "utf-8");
20626
22146
  const lines = content.split(`
20627
22147
  `).filter((l) => l.trim().length > 0);
20628
22148
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -20740,13 +22260,13 @@ function resolveMobileCurrentDirectory() {
20740
22260
  }
20741
22261
  }
20742
22262
  function resolveWorkspaceRoot(root) {
20743
- const expandedRoot = root.replace(/^~/, homedir21());
22263
+ const expandedRoot = root.replace(/^~/, homedir22());
20744
22264
  return realpathSync(expandedRoot);
20745
22265
  }
20746
22266
  function resolveWorkspacePath(root, requestedPath) {
20747
22267
  const normalizedRoot = resolveWorkspaceRoot(root);
20748
- const expandedPath = requestedPath?.replace(/^~/, homedir21());
20749
- const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join23(normalizedRoot, expandedPath) : normalizedRoot;
22268
+ const expandedPath = requestedPath?.replace(/^~/, homedir22());
22269
+ const candidate = expandedPath ? isAbsolute3(expandedPath) ? expandedPath : join25(normalizedRoot, expandedPath) : normalizedRoot;
20750
22270
  const resolvedCandidate = realpathSync(candidate);
20751
22271
  const rel = relative3(normalizedRoot, resolvedCandidate);
20752
22272
  if (rel === "" || !rel.startsWith("..") && !isAbsolute3(rel)) {
@@ -20776,7 +22296,7 @@ function listDirectories(dirPath) {
20776
22296
  continue;
20777
22297
  if (name === "node_modules" || name === ".build" || name === "target")
20778
22298
  continue;
20779
- const fullPath = join23(dirPath, name);
22299
+ const fullPath = join25(dirPath, name);
20780
22300
  try {
20781
22301
  const stat4 = statSync5(fullPath);
20782
22302
  if (!stat4.isDirectory())
@@ -20799,7 +22319,7 @@ function listDirectories(dirPath) {
20799
22319
  return entries.sort((a, b) => a.name.localeCompare(b.name));
20800
22320
  }
20801
22321
  async function discoverSessionFiles(maxAgeDays, limit) {
20802
- const home = homedir21();
22322
+ const home = homedir22();
20803
22323
  const results = [];
20804
22324
  const searchPaths = [
20805
22325
  { pattern: `${home}/.claude/projects`, agent: "claude-code" },
@@ -35444,7 +36964,7 @@ function planMessageDeliveries(input) {
35444
36964
  }
35445
36965
  return [...deliveries2.values()];
35446
36966
  }
35447
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/entity.js
36967
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/entity.js
35448
36968
  var entityKind = Symbol.for("drizzle:entityKind");
35449
36969
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
35450
36970
  function is(value, type) {
@@ -35469,7 +36989,7 @@ function is(value, type) {
35469
36989
  return false;
35470
36990
  }
35471
36991
 
35472
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column.js
36992
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column.js
35473
36993
  class Column {
35474
36994
  constructor(table, config2) {
35475
36995
  this.table = table;
@@ -35519,7 +37039,7 @@ class Column {
35519
37039
  }
35520
37040
  }
35521
37041
 
35522
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/column-builder.js
37042
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/column-builder.js
35523
37043
  class ColumnBuilder {
35524
37044
  static [entityKind] = "ColumnBuilder";
35525
37045
  config;
@@ -35575,20 +37095,20 @@ class ColumnBuilder {
35575
37095
  }
35576
37096
  }
35577
37097
 
35578
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.utils.js
37098
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.utils.js
35579
37099
  var TableName = Symbol.for("drizzle:Name");
35580
37100
 
35581
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing-utils.js
37101
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing-utils.js
35582
37102
  function iife(fn, ...args) {
35583
37103
  return fn(...args);
35584
37104
  }
35585
37105
 
35586
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/unique-constraint.js
37106
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/unique-constraint.js
35587
37107
  function uniqueKeyName(table, columns) {
35588
37108
  return `${table[TableName]}_${columns.join("_")}_unique`;
35589
37109
  }
35590
37110
 
35591
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/common.js
37111
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/common.js
35592
37112
  class PgColumn extends Column {
35593
37113
  constructor(table, config2) {
35594
37114
  if (!config2.uniqueName) {
@@ -35637,7 +37157,7 @@ class ExtraConfigColumn extends PgColumn {
35637
37157
  }
35638
37158
  }
35639
37159
 
35640
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/pg-core/columns/enum.js
37160
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/pg-core/columns/enum.js
35641
37161
  class PgEnumObjectColumn extends PgColumn {
35642
37162
  static [entityKind] = "PgEnumObjectColumn";
35643
37163
  enum;
@@ -35667,7 +37187,7 @@ class PgEnumColumn extends PgColumn {
35667
37187
  }
35668
37188
  }
35669
37189
 
35670
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/subquery.js
37190
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/subquery.js
35671
37191
  class Subquery {
35672
37192
  static [entityKind] = "Subquery";
35673
37193
  constructor(sql, fields, alias, isWith = false, usedTables = []) {
@@ -35682,10 +37202,10 @@ class Subquery {
35682
37202
  }
35683
37203
  }
35684
37204
 
35685
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/version.js
37205
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/version.js
35686
37206
  var version2 = "0.45.2";
35687
37207
 
35688
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/tracing.js
37208
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/tracing.js
35689
37209
  var otel;
35690
37210
  var rawTracer;
35691
37211
  var tracer = {
@@ -35712,10 +37232,10 @@ var tracer = {
35712
37232
  }
35713
37233
  };
35714
37234
 
35715
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/view-common.js
37235
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/view-common.js
35716
37236
  var ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
35717
37237
 
35718
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/table.js
37238
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/table.js
35719
37239
  var Schema = Symbol.for("drizzle:Schema");
35720
37240
  var Columns = Symbol.for("drizzle:Columns");
35721
37241
  var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns");
@@ -35753,7 +37273,7 @@ class Table {
35753
37273
  }
35754
37274
  }
35755
37275
 
35756
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sql/sql.js
37276
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sql/sql.js
35757
37277
  function isSQLWrapper(value) {
35758
37278
  return value !== null && value !== undefined && typeof value.getSQL === "function";
35759
37279
  }
@@ -36030,7 +37550,7 @@ function sql(strings, ...params) {
36030
37550
  return new SQL([new StringChunk(str)]);
36031
37551
  }
36032
37552
  sql2.raw = raw;
36033
- function join24(chunks, separator) {
37553
+ function join26(chunks, separator) {
36034
37554
  const result = [];
36035
37555
  for (const [i, chunk] of chunks.entries()) {
36036
37556
  if (i > 0 && separator !== undefined) {
@@ -36040,7 +37560,7 @@ function sql(strings, ...params) {
36040
37560
  }
36041
37561
  return new SQL(result);
36042
37562
  }
36043
- sql2.join = join24;
37563
+ sql2.join = join26;
36044
37564
  function identifier(value) {
36045
37565
  return new Name(value);
36046
37566
  }
@@ -36113,7 +37633,7 @@ Subquery.prototype.getSQL = function() {
36113
37633
  return new SQL([this]);
36114
37634
  };
36115
37635
 
36116
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/utils.js
37636
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/utils.js
36117
37637
  function getColumnNameAndConfig(a, b) {
36118
37638
  return {
36119
37639
  name: typeof a === "string" && a.length > 0 ? a : "",
@@ -36122,7 +37642,7 @@ function getColumnNameAndConfig(a, b) {
36122
37642
  }
36123
37643
  var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
36124
37644
 
36125
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
37645
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
36126
37646
  class ForeignKeyBuilder {
36127
37647
  static [entityKind] = "SQLiteForeignKeyBuilder";
36128
37648
  reference;
@@ -36176,12 +37696,12 @@ class ForeignKey {
36176
37696
  }
36177
37697
  }
36178
37698
 
36179
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
37699
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
36180
37700
  function uniqueKeyName2(table, columns) {
36181
37701
  return `${table[TableName]}_${columns.join("_")}_unique`;
36182
37702
  }
36183
37703
 
36184
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/common.js
37704
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/common.js
36185
37705
  class SQLiteColumnBuilder extends ColumnBuilder {
36186
37706
  static [entityKind] = "SQLiteColumnBuilder";
36187
37707
  foreignKeyConfigs = [];
@@ -36232,7 +37752,7 @@ class SQLiteColumn extends Column {
36232
37752
  static [entityKind] = "SQLiteColumn";
36233
37753
  }
36234
37754
 
36235
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/blob.js
37755
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/blob.js
36236
37756
  class SQLiteBigIntBuilder extends SQLiteColumnBuilder {
36237
37757
  static [entityKind] = "SQLiteBigIntBuilder";
36238
37758
  constructor(name) {
@@ -36320,7 +37840,7 @@ function blob(a, b) {
36320
37840
  return new SQLiteBlobBufferBuilder(name);
36321
37841
  }
36322
37842
 
36323
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/custom.js
37843
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/custom.js
36324
37844
  class SQLiteCustomColumnBuilder extends SQLiteColumnBuilder {
36325
37845
  static [entityKind] = "SQLiteCustomColumnBuilder";
36326
37846
  constructor(name, fieldConfig, customTypeParams) {
@@ -36361,7 +37881,7 @@ function customType(customTypeParams) {
36361
37881
  };
36362
37882
  }
36363
37883
 
36364
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/integer.js
37884
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/integer.js
36365
37885
  class SQLiteBaseIntegerBuilder extends SQLiteColumnBuilder {
36366
37886
  static [entityKind] = "SQLiteBaseIntegerBuilder";
36367
37887
  constructor(name, dataType, columnType) {
@@ -36463,7 +37983,7 @@ function integer2(a, b) {
36463
37983
  return new SQLiteIntegerBuilder(name);
36464
37984
  }
36465
37985
 
36466
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
37986
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
36467
37987
  class SQLiteNumericBuilder extends SQLiteColumnBuilder {
36468
37988
  static [entityKind] = "SQLiteNumericBuilder";
36469
37989
  constructor(name) {
@@ -36533,7 +38053,7 @@ function numeric(a, b) {
36533
38053
  return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
36534
38054
  }
36535
38055
 
36536
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/real.js
38056
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/real.js
36537
38057
  class SQLiteRealBuilder extends SQLiteColumnBuilder {
36538
38058
  static [entityKind] = "SQLiteRealBuilder";
36539
38059
  constructor(name) {
@@ -36554,7 +38074,7 @@ function real(name) {
36554
38074
  return new SQLiteRealBuilder(name ?? "");
36555
38075
  }
36556
38076
 
36557
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/text.js
38077
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/text.js
36558
38078
  class SQLiteTextBuilder extends SQLiteColumnBuilder {
36559
38079
  static [entityKind] = "SQLiteTextBuilder";
36560
38080
  constructor(name, config2) {
@@ -36609,7 +38129,7 @@ function text(a, b = {}) {
36609
38129
  return new SQLiteTextBuilder(name, config2);
36610
38130
  }
36611
38131
 
36612
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/columns/all.js
38132
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/columns/all.js
36613
38133
  function getSQLiteColumnBuilders() {
36614
38134
  return {
36615
38135
  blob,
@@ -36621,7 +38141,7 @@ function getSQLiteColumnBuilders() {
36621
38141
  };
36622
38142
  }
36623
38143
 
36624
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/table.js
38144
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/table.js
36625
38145
  var InlineForeignKeys = Symbol.for("drizzle:SQLiteInlineForeignKeys");
36626
38146
 
36627
38147
  class SQLiteTable extends Table {
@@ -36655,7 +38175,7 @@ var sqliteTable = (name, columns, extraConfig) => {
36655
38175
  return sqliteTableBase(name, columns, extraConfig);
36656
38176
  };
36657
38177
 
36658
- // ../../node_modules/.bun/drizzle-orm@0.45.2+29ab9a45addfc34e/node_modules/drizzle-orm/sqlite-core/indexes.js
38178
+ // ../../node_modules/.bun/drizzle-orm@0.45.2+e80f9ee8788b3855/node_modules/drizzle-orm/sqlite-core/indexes.js
36659
38179
  class IndexBuilderOn {
36660
38180
  constructor(name, unique2) {
36661
38181
  this.name = name;
@@ -36699,6 +38219,7 @@ function index(name) {
36699
38219
  }
36700
38220
 
36701
38221
  // ../runtime/src/drizzle-schema.ts
38222
+ var epochMsNow = sql`(CAST(strftime('%s','now') AS INTEGER) * 1000)`;
36702
38223
  var deliveriesTable = sqliteTable("deliveries", {
36703
38224
  id: text("id").primaryKey(),
36704
38225
  messageId: text("message_id"),
@@ -36714,7 +38235,7 @@ var deliveriesTable = sqliteTable("deliveries", {
36714
38235
  leaseOwner: text("lease_owner"),
36715
38236
  leaseExpiresAt: integer2("lease_expires_at"),
36716
38237
  metadataJson: text("metadata_json"),
36717
- createdAt: integer2("created_at").notNull().default(sql`(unixepoch())`)
38238
+ createdAt: integer2("created_at").notNull().default(epochMsNow)
36718
38239
  }, (table) => [
36719
38240
  index("idx_deliveries_status_transport").on(table.status, table.transport)
36720
38241
  ]);
@@ -36741,7 +38262,7 @@ var briefingsTable = sqliteTable("briefings", {
36741
38262
  snapshotJson: text("snapshot_json").notNull(),
36742
38263
  callJson: text("call_json").notNull(),
36743
38264
  markdown: text("markdown"),
36744
- createdAt: integer2("created_at").notNull().default(sql`(unixepoch())`)
38265
+ createdAt: integer2("created_at").notNull().default(epochMsNow)
36745
38266
  }, (table) => [
36746
38267
  index("idx_briefings_created_at").on(table.createdAt),
36747
38268
  index("idx_briefings_kind_created_at").on(table.kind, table.createdAt)
@@ -37442,6 +38963,11 @@ init_support_paths();
37442
38963
  // ../runtime/src/index.ts
37443
38964
  init_broker_process_manager();
37444
38965
  init_broker_api();
38966
+
38967
+ // ../runtime/src/invocation-lifecycle-read-model.ts
38968
+ init_src2();
38969
+
38970
+ // ../runtime/src/index.ts
37445
38971
  init_local_agents();
37446
38972
 
37447
38973
  // ../runtime/src/scout-broker.ts
@@ -37456,6 +38982,7 @@ init_broker_process_manager();
37456
38982
  init_codex_app_server();
37457
38983
  init_setup();
37458
38984
  init_support_paths();
38985
+ init_user_config();
37459
38986
 
37460
38987
  // ../runtime/src/thread-events.ts
37461
38988
  class ThreadWatchProtocolError extends Error {
@@ -37692,10 +39219,10 @@ class ThreadEventPlane {
37692
39219
  // ../runtime/src/mobile-push.ts
37693
39220
  init_support_paths();
37694
39221
  import { Database as Database2 } from "bun:sqlite";
37695
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync13 } from "fs";
39222
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync14 } from "fs";
37696
39223
  import { connect as connectHttp2 } from "http2";
37697
39224
  import { createPrivateKey, sign as signWithKey } from "crypto";
37698
- import { dirname as dirname11, join as join24 } from "path";
39225
+ import { dirname as dirname13, join as join26 } from "path";
37699
39226
  var MOBILE_PUSH_SCHEMA = `
37700
39227
  CREATE TABLE IF NOT EXISTS mobile_push_registrations (
37701
39228
  id TEXT PRIMARY KEY,
@@ -37723,7 +39250,7 @@ var dbHandle = null;
37723
39250
  var dbPath = null;
37724
39251
  var cachedApnsJwt = null;
37725
39252
  function resolveControlPlaneDbPath() {
37726
- return join24(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
39253
+ return join26(resolveOpenScoutSupportPaths().controlHome, "control-plane.sqlite");
37727
39254
  }
37728
39255
  function ensureMobilePushSchema(database) {
37729
39256
  database.exec("PRAGMA busy_timeout = 5000;");
@@ -37737,7 +39264,7 @@ function writeDb() {
37737
39264
  dbHandle = null;
37738
39265
  }
37739
39266
  if (!dbHandle) {
37740
- mkdirSync10(dirname11(nextPath), { recursive: true });
39267
+ mkdirSync11(dirname13(nextPath), { recursive: true });
37741
39268
  dbHandle = new Database2(nextPath, { create: true });
37742
39269
  dbPath = nextPath;
37743
39270
  ensureMobilePushSchema(dbHandle);
@@ -38029,7 +39556,7 @@ function loadApnsCredentials() {
38029
39556
  privateKeyPem = Buffer.from(inlineBase64, "base64").toString("utf8");
38030
39557
  }
38031
39558
  if (!privateKeyPem && path2) {
38032
- privateKeyPem = readFileSync13(path2, "utf8");
39559
+ privateKeyPem = readFileSync14(path2, "utf8");
38033
39560
  }
38034
39561
  if (!teamId || !keyId || !privateKeyPem) {
38035
39562
  return null;
@@ -38520,8 +40047,8 @@ function candidateFromValue(key, value, root, now) {
38520
40047
  title: firstMetadataString(root, "title", "attentionTitle"),
38521
40048
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
38522
40049
  detail: firstMetadataString(root, "detail", "statusDetail"),
38523
- turnId: metadataString6(root, "turnId"),
38524
- blockId: metadataString6(root, "blockId"),
40050
+ turnId: metadataString7(root, "turnId"),
40051
+ blockId: metadataString7(root, "blockId"),
38525
40052
  version: metadataNumber(root, "version"),
38526
40053
  updatedAt: metadataTimestamp(root, now),
38527
40054
  severity: nativeSeverity(root)
@@ -38540,8 +40067,8 @@ function candidateFromValue(key, value, root, now) {
38540
40067
  title: firstMetadataString(root, "title", "attentionTitle"),
38541
40068
  summary: firstMetadataString(root, "summary", "attentionSummary", "blockedReason", "needsInputReason", "reason", "message"),
38542
40069
  detail: firstMetadataString(root, "detail", "statusDetail"),
38543
- turnId: metadataString6(root, "turnId"),
38544
- blockId: metadataString6(root, "blockId"),
40070
+ turnId: metadataString7(root, "turnId"),
40071
+ blockId: metadataString7(root, "blockId"),
38545
40072
  version: metadataNumber(root, "version"),
38546
40073
  updatedAt: metadataTimestamp(root, now),
38547
40074
  severity: nativeSeverity(root)
@@ -38570,8 +40097,8 @@ function candidateFromRecord(key, record2, now, keyImpliesAttention) {
38570
40097
  title: firstMetadataString(record2, "title", "label"),
38571
40098
  summary: firstMetadataString(record2, "summary", "message", "reason", "description"),
38572
40099
  detail: firstMetadataString(record2, "detail", "statusDetail", "hint"),
38573
- turnId: metadataString6(record2, "turnId"),
38574
- blockId: metadataString6(record2, "blockId"),
40100
+ turnId: metadataString7(record2, "turnId"),
40101
+ blockId: metadataString7(record2, "blockId"),
38575
40102
  version: metadataNumber(record2, "version"),
38576
40103
  updatedAt: metadataTimestamp(record2, now),
38577
40104
  severity: nativeSeverity(record2)
@@ -38591,7 +40118,7 @@ function stableNativeIdPart(value) {
38591
40118
  return stable || "blocked";
38592
40119
  }
38593
40120
  function nativeSeverity(record2) {
38594
- const severity = metadataString6(record2, "severity")?.toLowerCase();
40121
+ const severity = metadataString7(record2, "severity")?.toLowerCase();
38595
40122
  if (severity === "critical" || severity === "warning" || severity === "info") {
38596
40123
  return severity;
38597
40124
  }
@@ -38600,7 +40127,7 @@ function nativeSeverity(record2) {
38600
40127
  function metadataRecord3(value) {
38601
40128
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
38602
40129
  }
38603
- function metadataString6(record2, key) {
40130
+ function metadataString7(record2, key) {
38604
40131
  const value = record2?.[key];
38605
40132
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
38606
40133
  }
@@ -38621,7 +40148,7 @@ function metadataTimestamp(record2, fallback) {
38621
40148
  }
38622
40149
  function firstMetadataString(record2, ...keys) {
38623
40150
  for (const key of keys) {
38624
- const value = metadataString6(record2, key);
40151
+ const value = metadataString7(record2, key);
38625
40152
  if (value) {
38626
40153
  return value;
38627
40154
  }
@@ -38639,10 +40166,10 @@ function compactAttentionSummary(value, max = 220) {
38639
40166
  init_src2();
38640
40167
  // ../web/server/core/pairing/runtime/bridge/router.ts
38641
40168
  init_local_agents();
38642
- import { readFileSync as readFileSync14, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
40169
+ import { readFileSync as readFileSync15, readdirSync as readdirSync6, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
38643
40170
  import { execSync as execSync4 } from "child_process";
38644
- import { basename as basename11, isAbsolute as isAbsolute4, join as join25, relative as relative4 } from "path";
38645
- import { homedir as homedir22 } from "os";
40171
+ import { basename as basename12, isAbsolute as isAbsolute4, join as join27, relative as relative4 } from "path";
40172
+ import { homedir as homedir23 } from "os";
38646
40173
  var t = initTRPC.context().create();
38647
40174
  var logged = t.middleware(async ({ path: path2, type, next }) => {
38648
40175
  const start = Date.now();
@@ -38669,13 +40196,13 @@ function resolveMobileCurrentDirectory2() {
38669
40196
  }
38670
40197
  }
38671
40198
  function resolveWorkspaceRoot2(root) {
38672
- const expandedRoot = root.replace(/^~/, homedir22());
40199
+ const expandedRoot = root.replace(/^~/, homedir23());
38673
40200
  return realpathSync2(expandedRoot);
38674
40201
  }
38675
40202
  function resolveWorkspacePath2(root, requestedPath) {
38676
40203
  const normalizedRoot = resolveWorkspaceRoot2(root);
38677
- const expandedPath = requestedPath?.replace(/^~/, homedir22());
38678
- const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join25(normalizedRoot, expandedPath) : normalizedRoot;
40204
+ const expandedPath = requestedPath?.replace(/^~/, homedir23());
40205
+ const candidate = expandedPath ? isAbsolute4(expandedPath) ? expandedPath : join27(normalizedRoot, expandedPath) : normalizedRoot;
38679
40206
  const resolvedCandidate = realpathSync2(candidate);
38680
40207
  const rel = relative4(normalizedRoot, resolvedCandidate);
38681
40208
  if (rel === "" || !rel.startsWith("..") && !isAbsolute4(rel)) {
@@ -38705,7 +40232,7 @@ function listDirectories2(dirPath) {
38705
40232
  continue;
38706
40233
  if (name === "node_modules" || name === ".build" || name === "target")
38707
40234
  continue;
38708
- const fullPath = join25(dirPath, name);
40235
+ const fullPath = join27(dirPath, name);
38709
40236
  try {
38710
40237
  const stat4 = statSync6(fullPath);
38711
40238
  if (!stat4.isDirectory())
@@ -38744,7 +40271,7 @@ function detectAgent2(filePath) {
38744
40271
  return "unknown";
38745
40272
  }
38746
40273
  async function discoverSessionFiles2(maxAgeDays, limit) {
38747
- const home = homedir22();
40274
+ const home = homedir23();
38748
40275
  const results = [];
38749
40276
  const searchPaths = [
38750
40277
  { pattern: `${home}/.claude/projects`, agent: "claude-code" },
@@ -39038,9 +40565,9 @@ var sessionRouter = t.router({
39038
40565
  adapterType: exports_external.string().optional(),
39039
40566
  name: exports_external.string().optional()
39040
40567
  })).mutation(async ({ input, ctx }) => {
39041
- const sessionFilename = basename11(input.sessionPath, ".jsonl");
40568
+ const sessionFilename = basename12(input.sessionPath, ".jsonl");
39042
40569
  const parentDir = input.sessionPath.substring(0, input.sessionPath.lastIndexOf("/"));
39043
- const dirName = basename11(parentDir);
40570
+ const dirName = basename12(parentDir);
39044
40571
  let cwd;
39045
40572
  if (dirName.startsWith("-")) {
39046
40573
  const candidate = "/" + dirName.slice(1).replace(/-/g, "/");
@@ -39340,7 +40867,7 @@ var workspaceRouter = t.router({
39340
40867
  });
39341
40868
  }
39342
40869
  const adapterType = input.adapter ?? "claude-code";
39343
- const name = input.name ?? basename11(projectPath);
40870
+ const name = input.name ?? basename12(projectPath);
39344
40871
  return ctx.bridge.createSession(adapterType, {
39345
40872
  name,
39346
40873
  cwd: projectPath
@@ -39409,7 +40936,7 @@ var historyRouter = t.router({
39409
40936
  });
39410
40937
  }
39411
40938
  try {
39412
- const content = readFileSync14(input.path, "utf-8");
40939
+ const content = readFileSync15(input.path, "utf-8");
39413
40940
  const lines = content.split(`
39414
40941
  `).filter((l) => l.trim().length > 0);
39415
40942
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -39442,7 +40969,7 @@ var historyRouter = t.router({
39442
40969
  }
39443
40970
  try {
39444
40971
  const fileStat = statSync6(input.path);
39445
- const content = readFileSync14(input.path, "utf-8");
40972
+ const content = readFileSync15(input.path, "utf-8");
39446
40973
  const replay = createHistorySessionSnapshot({
39447
40974
  path: input.path,
39448
40975
  content,
@@ -43539,14 +45066,14 @@ function parseTRPCMessage(obj, transformer) {
43539
45066
  }
43540
45067
  // ../web/server/core/pairing/runtime/bridge/server-trpc.ts
43541
45068
  import { realpathSync as realpathSync3 } from "fs";
43542
- import { homedir as homedir23 } from "os";
45069
+ import { homedir as homedir24 } from "os";
43543
45070
  function resolveCurrentDirectory() {
43544
45071
  try {
43545
45072
  const config2 = resolveConfig();
43546
45073
  const configuredRoot = config2.workspace?.root;
43547
45074
  if (!configuredRoot)
43548
45075
  return process.cwd();
43549
- const expanded = configuredRoot.replace(/^~/, homedir23());
45076
+ const expanded = configuredRoot.replace(/^~/, homedir24());
43550
45077
  return realpathSync3(expanded);
43551
45078
  } catch {
43552
45079
  return process.cwd();
@@ -44066,7 +45593,7 @@ async function autoStartConfiguredSessions(bridge, sessions3) {
44066
45593
  try {
44067
45594
  const session = await bridge.createSession(entry.adapter, {
44068
45595
  name: entry.name,
44069
- cwd: entry.cwd?.replace(/^~/, homedir24()),
45596
+ cwd: entry.cwd?.replace(/^~/, homedir25()),
44070
45597
  options: entry.options
44071
45598
  });
44072
45599
  console.log(`[bridge] session started: ${session.name} (${entry.adapter})`);
@@ -44156,6 +45683,7 @@ async function startSupervisorRuntime(state) {
44156
45683
  state.bonjour = managedRelay ? startBonjourRelayAdvertisement({
44157
45684
  port: relayPort,
44158
45685
  relayUrl: activeRelayUrl,
45686
+ fallbackRelayUrls: managedRelay.fallbackRelayUrls,
44159
45687
  publicKeyHex
44160
45688
  }) : null;
44161
45689
  state.runtime = await startPairingRuntime({
@@ -44306,6 +45834,10 @@ function startBonjourRelayAdvertisement(input) {
44306
45834
  `fp=${fingerprint}`,
44307
45835
  `scheme=${scheme}`
44308
45836
  ];
45837
+ const fallbackRelayUrls = normalizedBonjourFallbackRelayUrls(input.fallbackRelayUrls);
45838
+ if (fallbackRelayUrls.length > 0) {
45839
+ args.push(`fallbackRelays=${fallbackRelayUrls.join("|")}`);
45840
+ }
44309
45841
  let processRef = null;
44310
45842
  try {
44311
45843
  processRef = spawn4("/usr/bin/dns-sd", args, { stdio: "ignore" });
@@ -44330,6 +45862,19 @@ function startBonjourRelayAdvertisement(input) {
44330
45862
  }
44331
45863
  };
44332
45864
  }
45865
+ function normalizedBonjourFallbackRelayUrls(relayUrls) {
45866
+ const seen = new Set;
45867
+ const result = [];
45868
+ for (const relayUrl of relayUrls ?? []) {
45869
+ const trimmed = relayUrl.trim();
45870
+ if (!trimmed || seen.has(trimmed)) {
45871
+ continue;
45872
+ }
45873
+ seen.add(trimmed);
45874
+ result.push(trimmed);
45875
+ }
45876
+ return result;
45877
+ }
44333
45878
  function relayScheme(relayUrl) {
44334
45879
  try {
44335
45880
  const protocol = new URL(relayUrl).protocol;