@openscout/scout 0.2.64 → 0.2.65

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.
@@ -5930,8 +5930,8 @@ class CodexAdapter extends BaseAdapter {
5930
5930
  const resumed = await this.request("thread/resume", {
5931
5931
  threadId: storedThreadId,
5932
5932
  cwd: options.cwd,
5933
- approvalPolicy: "never",
5934
- sandbox: "danger-full-access",
5933
+ approvalPolicy: options.approvalPolicy ?? "never",
5934
+ sandbox: options.sandbox ?? "danger-full-access",
5935
5935
  baseInstructions: options.systemPrompt,
5936
5936
  persistExtendedHistory: true
5937
5937
  });
@@ -5961,8 +5961,8 @@ class CodexAdapter extends BaseAdapter {
5961
5961
  }
5962
5962
  const started = await this.request("thread/start", {
5963
5963
  cwd: options.cwd,
5964
- approvalPolicy: "never",
5965
- sandbox: "danger-full-access",
5964
+ approvalPolicy: options.approvalPolicy ?? "never",
5965
+ sandbox: options.sandbox ?? "danger-full-access",
5966
5966
  baseInstructions: options.systemPrompt,
5967
5967
  ephemeral: false,
5968
5968
  experimentalRawEvents: false,
@@ -9099,6 +9099,40 @@ var BUILT_IN_HARNESS_CATALOG = [
9099
9099
  cwdFlag: "--cwd"
9100
9100
  },
9101
9101
  capabilities: ["chat", "invoke", "deliver", "review", "execute"]
9102
+ },
9103
+ {
9104
+ name: "cursor",
9105
+ harness: "cursor",
9106
+ label: "Cursor Agent SDK",
9107
+ description: "Programmatic agent execution via the Cursor Agent SDK",
9108
+ homepage: "https://cursor.com",
9109
+ tags: ["coding", "sdk", "cursor"],
9110
+ featured: true,
9111
+ order: 3,
9112
+ support: {
9113
+ ...DEFAULT_SUPPORT,
9114
+ workspace: true,
9115
+ collaboration: true,
9116
+ files: true
9117
+ },
9118
+ install: {
9119
+ binary: "mw",
9120
+ requires: ["bun"],
9121
+ macos: "bun install -g missionwriter",
9122
+ linux: "bun install -g missionwriter",
9123
+ windows: "bun install -g missionwriter"
9124
+ },
9125
+ readiness: {
9126
+ anyOf: [
9127
+ { kind: "env", key: "CURSOR_API_KEY" }
9128
+ ],
9129
+ notReadyMessage: "Cursor Agent SDK is available but CURSOR_API_KEY is not set."
9130
+ },
9131
+ capabilities: ["invoke", "deliver", "execute"],
9132
+ metadata: {
9133
+ sdkType: "cursor",
9134
+ invocationModel: "one_shot"
9135
+ }
9102
9136
  }
9103
9137
  ];
9104
9138
  function expandHomePath(value) {
@@ -10673,6 +10707,22 @@ function diagnoseAgentIdentity(identity, candidates) {
10673
10707
  var parseAgentSelector = parseAgentIdentity;
10674
10708
  var formatAgentSelector = formatAgentIdentity;
10675
10709
  var resolveAgentSelector = resolveAgentIdentity;
10710
+ // ../protocol/src/permission-policy.ts
10711
+ var SCOUT_PERMISSION_PROFILES = [
10712
+ "observe",
10713
+ "review",
10714
+ "workspace_write",
10715
+ "sandboxed_write",
10716
+ "trusted_local",
10717
+ "external_sandbox"
10718
+ ];
10719
+ function normalizeScoutPermissionProfile(value) {
10720
+ const normalized = value?.trim().replaceAll("-", "_");
10721
+ return SCOUT_PERMISSION_PROFILES.find((profile) => profile === normalized);
10722
+ }
10723
+ function formatScoutPermissionProfiles() {
10724
+ return SCOUT_PERMISSION_PROFILES.join("|");
10725
+ }
10676
10726
  // ../runtime/src/user-project-hints.ts
10677
10727
  import { readdir, readFile as readFile3, stat } from "fs/promises";
10678
10728
  import { homedir as homedir11 } from "os";
@@ -10998,7 +11048,7 @@ async function collectUserLevelProjectRootHints(options = {}) {
10998
11048
  // ../runtime/src/setup.ts
10999
11049
  var SCOUT_AGENT_ID = "scout";
11000
11050
  var SCOUT_PRIMARY_CONVERSATION_ID = "dm.scout.primary";
11001
- var MANAGED_AGENT_HARNESSES = ["claude", "codex"];
11051
+ var MANAGED_AGENT_HARNESSES = ["claude", "codex", "cursor"];
11002
11052
  function titleCaseWords(value) {
11003
11053
  return value.split(/[\s._-]+/).map((part) => part.trim()).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11004
11054
  }
@@ -11117,6 +11167,10 @@ var PROJECT_HARNESS_MARKERS = {
11117
11167
  "AGENTS.md",
11118
11168
  "CODEX.md",
11119
11169
  ".codex"
11170
+ ],
11171
+ cursor: [
11172
+ ".cursor",
11173
+ ".cursorrules"
11120
11174
  ]
11121
11175
  };
11122
11176
  function partitionFlatAndNestedMarkers(markers) {
@@ -11181,6 +11235,9 @@ function normalizeManagedHarness(value, fallback) {
11181
11235
  if (value === "claude") {
11182
11236
  return "claude";
11183
11237
  }
11238
+ if (value === "cursor") {
11239
+ return "cursor";
11240
+ }
11184
11241
  return fallback;
11185
11242
  }
11186
11243
  function titleCase(value) {
@@ -11354,12 +11411,15 @@ function normalizeCapabilities(value) {
11354
11411
  return normalized.length > 0 ? normalized : [...DEFAULT_CAPABILITIES];
11355
11412
  }
11356
11413
  function normalizeHarness(value, fallback) {
11357
- return value === "codex" ? "codex" : value === "claude" ? "claude" : fallback;
11414
+ return value === "codex" ? "codex" : value === "claude" ? "claude" : value === "cursor" ? "cursor" : fallback;
11358
11415
  }
11359
11416
  function normalizeTransport(value, harness, fallback) {
11360
11417
  if (harness === "codex") {
11361
11418
  return "codex_app_server";
11362
11419
  }
11420
+ if (harness === "cursor") {
11421
+ return "cursor_exec";
11422
+ }
11363
11423
  if (value === "claude_stream_json") {
11364
11424
  return "claude_stream_json";
11365
11425
  }
@@ -11369,11 +11429,13 @@ function normalizeTransport(value, harness, fallback) {
11369
11429
  return fallback === "codex_app_server" ? "claude_stream_json" : fallback;
11370
11430
  }
11371
11431
  function normalizeHarnessProfile(profile, harness, options) {
11432
+ const permissionProfile = normalizeScoutPermissionProfile(typeof profile?.permissionProfile === "string" ? profile.permissionProfile : options.fallbackPermissionProfile);
11372
11433
  return {
11373
11434
  cwd: profile?.cwd ? normalizeProjectRelativePath(options.projectRoot, profile.cwd) : options.projectRoot,
11374
11435
  transport: normalizeTransport(profile?.transport, harness, harness === "codex" ? "codex_app_server" : DEFAULT_TRANSPORT),
11375
11436
  sessionId: normalizeTmuxSessionName(profile?.sessionId, `${options.sessionKey}-${harness}`, options.sessionPrefix),
11376
- launchArgs: normalizeLaunchArgs(profile?.launchArgs ?? options.fallbackLaunchArgs)
11437
+ launchArgs: normalizeLaunchArgs(profile?.launchArgs ?? options.fallbackLaunchArgs),
11438
+ ...permissionProfile ? { permissionProfile } : {}
11377
11439
  };
11378
11440
  }
11379
11441
  function buildHarnessProfiles(input) {
@@ -11389,7 +11451,8 @@ function buildHarnessProfiles(input) {
11389
11451
  projectRoot: input.projectRoot,
11390
11452
  sessionKey: input.sessionKey,
11391
11453
  sessionPrefix: input.sessionPrefix,
11392
- fallbackLaunchArgs: input.launchArgs
11454
+ fallbackLaunchArgs: input.launchArgs,
11455
+ fallbackPermissionProfile: input.permissionProfile
11393
11456
  });
11394
11457
  }
11395
11458
  for (const harness of MANAGED_AGENT_HARNESSES) {
@@ -11402,7 +11465,8 @@ function buildHarnessProfiles(input) {
11402
11465
  projectRoot: input.projectRoot,
11403
11466
  sessionKey: input.sessionKey,
11404
11467
  sessionPrefix: input.sessionPrefix,
11405
- fallbackLaunchArgs: profiles[harness]?.launchArgs ?? input.launchArgs
11468
+ fallbackLaunchArgs: profiles[harness]?.launchArgs ?? input.launchArgs,
11469
+ fallbackPermissionProfile: profiles[harness]?.permissionProfile ?? input.permissionProfile
11406
11470
  });
11407
11471
  }
11408
11472
  }
@@ -11410,7 +11474,8 @@ function buildHarnessProfiles(input) {
11410
11474
  projectRoot: input.projectRoot,
11411
11475
  sessionKey: input.sessionKey,
11412
11476
  sessionPrefix: input.sessionPrefix,
11413
- fallbackLaunchArgs: input.launchArgs
11477
+ fallbackLaunchArgs: input.launchArgs,
11478
+ fallbackPermissionProfile: input.permissionProfile
11414
11479
  });
11415
11480
  profiles[input.defaultHarness] = ensuredHarness;
11416
11481
  return profiles;
@@ -11860,7 +11925,8 @@ async function readLegacyAgentRegistry() {
11860
11925
  async function detectHarnessMarkers(projectRoot) {
11861
11926
  const detected = {
11862
11927
  claude: [],
11863
- codex: []
11928
+ codex: [],
11929
+ cursor: []
11864
11930
  };
11865
11931
  for (const harness of MANAGED_AGENT_HARNESSES) {
11866
11932
  for (const marker of PROJECT_HARNESS_MARKERS[harness]) {
@@ -12442,7 +12508,8 @@ async function resolveManifestBackedAgent(projectRoot, config, settings, overrid
12442
12508
  defaultHarness,
12443
12509
  profiles: config.agent?.runtime?.profiles,
12444
12510
  runtime: runtimeDefaults,
12445
- launchArgs: normalizeLaunchArgs(runtimeDefaults?.launchArgs)
12511
+ launchArgs: normalizeLaunchArgs(runtimeDefaults?.launchArgs),
12512
+ permissionProfile: normalizeScoutPermissionProfile(runtimeDefaults?.permissionProfile)
12446
12513
  });
12447
12514
  const view = resolvedRuntimeView(defaultHarness, harnessProfiles);
12448
12515
  const base = {
@@ -12517,7 +12584,7 @@ async function buildProjectInventoryEntry(agent, sourceRoot) {
12517
12584
  });
12518
12585
  }
12519
12586
  }
12520
- if (manifestDefaultHarness === "claude" || manifestDefaultHarness === "codex") {
12587
+ if (manifestDefaultHarness === "claude" || manifestDefaultHarness === "codex" || manifestDefaultHarness === "cursor") {
12521
12588
  harnesses.set(manifestDefaultHarness, {
12522
12589
  harness: manifestDefaultHarness,
12523
12590
  source: "manifest",
@@ -12802,6 +12869,29 @@ function buildManagedAgentShellExports(options) {
12802
12869
  }
12803
12870
 
12804
12871
  // ../runtime/src/claude-stream-json.ts
12872
+ function resolveRequesterTimeoutMs(timeoutMs) {
12873
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
12874
+ return timeoutMs;
12875
+ }
12876
+ return null;
12877
+ }
12878
+ function waitForRequesterResult(promise, timeoutMs, label) {
12879
+ const effectiveTimeoutMs = resolveRequesterTimeoutMs(timeoutMs);
12880
+ if (effectiveTimeoutMs === null) {
12881
+ return promise;
12882
+ }
12883
+ let timer = null;
12884
+ const timeout = new Promise((_resolve, reject) => {
12885
+ timer = setTimeout(() => {
12886
+ reject(new Error(`Timed out after ${effectiveTimeoutMs}ms waiting for ${label}.`));
12887
+ }, effectiveTimeoutMs);
12888
+ });
12889
+ return Promise.race([promise, timeout]).finally(() => {
12890
+ if (timer) {
12891
+ clearTimeout(timer);
12892
+ }
12893
+ });
12894
+ }
12805
12895
  function resolveClaudeStreamJsonOutput(result, fallbackParts) {
12806
12896
  const trimmedResult = result?.trim();
12807
12897
  if (trimmedResult) {
@@ -12898,7 +12988,7 @@ class ClaudeStreamJsonSession {
12898
12988
  sessionId: this.claudeSessionId
12899
12989
  };
12900
12990
  }
12901
- async invoke(prompt, stallTimeoutMs = 10 * 60000) {
12991
+ async invoke(prompt, timeoutMs) {
12902
12992
  await this.ensureStarted();
12903
12993
  if (!this.process?.stdin) {
12904
12994
  throw new Error(`Claude stream-json session for ${this.options.agentName} is not running.`);
@@ -12910,13 +13000,10 @@ class ClaudeStreamJsonSession {
12910
13000
  const turn = {
12911
13001
  id: randomUUID(),
12912
13002
  output: [],
12913
- timer: null,
12914
- stallMs: stallTimeoutMs,
12915
13003
  resolve: resolve4,
12916
13004
  reject
12917
13005
  };
12918
13006
  this.activeTurn = turn;
12919
- this.resetTurnWatchdog(turn);
12920
13007
  });
12921
13008
  const payload = JSON.stringify({
12922
13009
  type: "user",
@@ -12929,7 +13016,7 @@ class ClaudeStreamJsonSession {
12929
13016
  }) + `
12930
13017
  `;
12931
13018
  this.process.stdin.write(payload);
12932
- const output = await outputPromise;
13019
+ const output = await waitForRequesterResult(outputPromise, timeoutMs, this.options.agentName);
12933
13020
  return {
12934
13021
  output,
12935
13022
  sessionId: this.claudeSessionId
@@ -12951,27 +13038,10 @@ class ClaudeStreamJsonSession {
12951
13038
  }) + `
12952
13039
  `);
12953
13040
  }
12954
- resetTurnWatchdog(turn) {
12955
- if (turn.timer) {
12956
- clearTimeout(turn.timer);
12957
- }
12958
- turn.timer = setTimeout(() => {
12959
- this.interrupt().catch(() => {
12960
- return;
12961
- });
12962
- if (this.activeTurn?.id === turn.id) {
12963
- this.activeTurn = null;
12964
- }
12965
- turn.reject(new Error(`${this.options.agentName} stalled \u2014 no stream event in ${turn.stallMs}ms`));
12966
- }, turn.stallMs);
12967
- }
12968
13041
  async shutdown(options = {}) {
12969
13042
  const turn = this.activeTurn;
12970
13043
  this.activeTurn = null;
12971
13044
  if (turn) {
12972
- if (turn.timer) {
12973
- clearTimeout(turn.timer);
12974
- }
12975
13045
  turn.reject(new Error(`Claude stream-json session for ${this.options.agentName} was shut down.`));
12976
13046
  }
12977
13047
  const child = this.process;
@@ -13049,9 +13119,6 @@ class ClaudeStreamJsonSession {
13049
13119
  if (this.activeTurn) {
13050
13120
  const turn = this.activeTurn;
13051
13121
  this.activeTurn = null;
13052
- if (turn.timer) {
13053
- clearTimeout(turn.timer);
13054
- }
13055
13122
  turn.reject(new Error(`Claude process error: ${error.message}`));
13056
13123
  }
13057
13124
  });
@@ -13082,9 +13149,6 @@ class ClaudeStreamJsonSession {
13082
13149
  if (code !== 0 && this.activeTurn) {
13083
13150
  const turn = this.activeTurn;
13084
13151
  this.activeTurn = null;
13085
- if (turn.timer) {
13086
- clearTimeout(turn.timer);
13087
- }
13088
13152
  turn.reject(new Error(`Claude exited with code ${code}`));
13089
13153
  }
13090
13154
  });
@@ -13106,7 +13170,6 @@ class ClaudeStreamJsonSession {
13106
13170
  if (!turn) {
13107
13171
  return;
13108
13172
  }
13109
- this.resetTurnWatchdog(turn);
13110
13173
  if (event.type === "assistant") {
13111
13174
  const content = event.message?.content ?? event.content ?? [];
13112
13175
  for (const part of content) {
@@ -13118,17 +13181,11 @@ class ClaudeStreamJsonSession {
13118
13181
  }
13119
13182
  if (event.type === "result") {
13120
13183
  this.activeTurn = null;
13121
- if (turn.timer) {
13122
- clearTimeout(turn.timer);
13123
- }
13124
13184
  turn.resolve(resolveClaudeStreamJsonOutput(event.result, turn.output));
13125
13185
  return;
13126
13186
  }
13127
13187
  if (event.type === "error") {
13128
13188
  this.activeTurn = null;
13129
- if (turn.timer) {
13130
- clearTimeout(turn.timer);
13131
- }
13132
13189
  turn.reject(new Error(event.error?.message ?? event.message ?? "Unknown Claude error"));
13133
13190
  }
13134
13191
  }
@@ -13181,10 +13238,17 @@ function normalizeCodexModelValue(value) {
13181
13238
  const trimmed = value?.trim();
13182
13239
  return trimmed ? trimmed : null;
13183
13240
  }
13241
+ function normalizeCodexReasoningEffortValue(value) {
13242
+ const trimmed = value?.trim();
13243
+ return trimmed ? trimmed : null;
13244
+ }
13184
13245
  function encodeCodexModelConfig(model) {
13185
13246
  return `model=${JSON.stringify(model)}`;
13186
13247
  }
13187
- function parseCodexModelConfig(value) {
13248
+ function encodeCodexReasoningEffortConfig(reasoningEffort) {
13249
+ return `model_reasoning_effort=${JSON.stringify(reasoningEffort)}`;
13250
+ }
13251
+ function parseCodexConfigValue(value, expectedKey) {
13188
13252
  const trimmed = value?.trim();
13189
13253
  if (!trimmed) {
13190
13254
  return null;
@@ -13194,7 +13258,7 @@ function parseCodexModelConfig(value) {
13194
13258
  return null;
13195
13259
  }
13196
13260
  const key = trimmed.slice(0, separatorIndex).trim();
13197
- if (key !== "model") {
13261
+ if (key !== expectedKey) {
13198
13262
  return null;
13199
13263
  }
13200
13264
  const rawValue = trimmed.slice(separatorIndex + 1).trim();
@@ -13206,6 +13270,12 @@ function parseCodexModelConfig(value) {
13206
13270
  }
13207
13271
  return rawValue;
13208
13272
  }
13273
+ function parseCodexModelConfig(value) {
13274
+ return parseCodexConfigValue(value, "model");
13275
+ }
13276
+ function parseCodexReasoningEffortConfig(value) {
13277
+ return parseCodexConfigValue(value, "model_reasoning_effort");
13278
+ }
13209
13279
  function normalizeCodexAppServerLaunchArgs(launchArgs) {
13210
13280
  const args = Array.isArray(launchArgs) ? launchArgs.map((entry) => String(entry).trim()).filter(Boolean) : [];
13211
13281
  const normalized = [];
@@ -13235,11 +13305,36 @@ function normalizeCodexAppServerLaunchArgs(launchArgs) {
13235
13305
  continue;
13236
13306
  }
13237
13307
  }
13308
+ if (current === "--reasoning-effort" || current === "--effort") {
13309
+ const reasoningEffort = normalizeCodexReasoningEffortValue(args[index + 1]);
13310
+ if (reasoningEffort) {
13311
+ normalized.push("-c", encodeCodexReasoningEffortConfig(reasoningEffort));
13312
+ index += 1;
13313
+ continue;
13314
+ }
13315
+ normalized.push(current);
13316
+ continue;
13317
+ }
13318
+ if (current.startsWith("--reasoning-effort=")) {
13319
+ const reasoningEffort = normalizeCodexReasoningEffortValue(current.slice("--reasoning-effort=".length));
13320
+ if (reasoningEffort) {
13321
+ normalized.push("-c", encodeCodexReasoningEffortConfig(reasoningEffort));
13322
+ continue;
13323
+ }
13324
+ }
13325
+ if (current.startsWith("--effort=")) {
13326
+ const reasoningEffort = normalizeCodexReasoningEffortValue(current.slice("--effort=".length));
13327
+ if (reasoningEffort) {
13328
+ normalized.push("-c", encodeCodexReasoningEffortConfig(reasoningEffort));
13329
+ continue;
13330
+ }
13331
+ }
13238
13332
  if (current === "-c" || current === "--config") {
13239
13333
  const next = args[index + 1];
13240
13334
  if (typeof next === "string") {
13241
13335
  const model = parseCodexModelConfig(next);
13242
- normalized.push(current === "--config" ? "--config" : "-c", model ? encodeCodexModelConfig(model) : next);
13336
+ const reasoningEffort = parseCodexReasoningEffortConfig(next);
13337
+ normalized.push(current === "--config" ? "--config" : "-c", model ? encodeCodexModelConfig(model) : reasoningEffort ? encodeCodexReasoningEffortConfig(reasoningEffort) : next);
13243
13338
  index += 1;
13244
13339
  continue;
13245
13340
  }
@@ -13247,7 +13342,8 @@ function normalizeCodexAppServerLaunchArgs(launchArgs) {
13247
13342
  if (current.startsWith("--config=")) {
13248
13343
  const value = current.slice("--config=".length);
13249
13344
  const model = parseCodexModelConfig(value);
13250
- normalized.push(model ? `--config=${encodeCodexModelConfig(model)}` : current);
13345
+ const reasoningEffort = parseCodexReasoningEffortConfig(value);
13346
+ normalized.push(model ? `--config=${encodeCodexModelConfig(model)}` : reasoningEffort ? `--config=${encodeCodexReasoningEffortConfig(reasoningEffort)}` : current);
13251
13347
  continue;
13252
13348
  }
13253
13349
  normalized.push(current);
@@ -13275,6 +13371,50 @@ function readCodexAppServerModelFromLaunchArgs(launchArgs) {
13275
13371
  }
13276
13372
  return null;
13277
13373
  }
13374
+ function readCodexAppServerReasoningEffortFromLaunchArgs(launchArgs) {
13375
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
13376
+ for (let index = 0;index < normalized.length; index += 1) {
13377
+ const current = normalized[index] ?? "";
13378
+ if (current === "-c" || current === "--config") {
13379
+ const reasoningEffort = parseCodexReasoningEffortConfig(normalized[index + 1]);
13380
+ if (reasoningEffort) {
13381
+ return reasoningEffort;
13382
+ }
13383
+ index += 1;
13384
+ continue;
13385
+ }
13386
+ if (current.startsWith("--config=")) {
13387
+ const reasoningEffort = parseCodexReasoningEffortConfig(current.slice("--config=".length));
13388
+ if (reasoningEffort) {
13389
+ return reasoningEffort;
13390
+ }
13391
+ }
13392
+ }
13393
+ return null;
13394
+ }
13395
+ function resolveRequesterTimeoutMs2(timeoutMs) {
13396
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
13397
+ return timeoutMs;
13398
+ }
13399
+ return null;
13400
+ }
13401
+ function waitForRequesterResult2(promise, timeoutMs, label) {
13402
+ const effectiveTimeoutMs = resolveRequesterTimeoutMs2(timeoutMs);
13403
+ if (effectiveTimeoutMs === null) {
13404
+ return promise;
13405
+ }
13406
+ let timer = null;
13407
+ const timeout = new Promise((_resolve, reject) => {
13408
+ timer = setTimeout(() => {
13409
+ reject(new Error(`Timed out after ${effectiveTimeoutMs}ms waiting for ${label}.`));
13410
+ }, effectiveTimeoutMs);
13411
+ });
13412
+ return Promise.race([promise, timeout]).finally(() => {
13413
+ if (timer) {
13414
+ clearTimeout(timer);
13415
+ }
13416
+ });
13417
+ }
13278
13418
  function sessionKey2(options) {
13279
13419
  return `${options.agentName}:${options.sessionId}`;
13280
13420
  }
@@ -13377,17 +13517,79 @@ async function readOptionalFile2(filePath) {
13377
13517
  return null;
13378
13518
  }
13379
13519
  }
13520
+ var SESSION_CATALOG_FILENAME2 = "session-catalog.json";
13521
+ var SESSION_CATALOG_MAX_ENTRIES2 = 64;
13522
+ async function readCodexSessionCatalog(runtimeDirectory) {
13523
+ const raw = await readOptionalFile2(join13(runtimeDirectory, SESSION_CATALOG_FILENAME2));
13524
+ if (!raw) {
13525
+ return { activeSessionId: null, sessions: [] };
13526
+ }
13527
+ const parsed = parseCodexMaybeJson(raw);
13528
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
13529
+ return { activeSessionId: null, sessions: [] };
13530
+ }
13531
+ return {
13532
+ activeSessionId: typeof parsed.activeSessionId === "string" ? parsed.activeSessionId : null,
13533
+ sessions: Array.isArray(parsed.sessions) ? parsed.sessions.filter((entry) => Boolean(entry) && typeof entry === "object" && typeof entry.id === "string" && typeof entry.startedAt === "number" && typeof entry.cwd === "string") : []
13534
+ };
13535
+ }
13536
+ async function writeCodexSessionCatalog(runtimeDirectory, catalog) {
13537
+ await mkdir5(runtimeDirectory, { recursive: true });
13538
+ await writeFile5(join13(runtimeDirectory, SESSION_CATALOG_FILENAME2), JSON.stringify(catalog, null, 2) + `
13539
+ `);
13540
+ }
13541
+ async function recordCodexSessionCatalog(runtimeDirectory, threadId, input) {
13542
+ const catalog = await readCodexSessionCatalog(runtimeDirectory);
13543
+ const now = Date.now();
13544
+ const sessions2 = catalog.sessions.map((session) => session.id === catalog.activeSessionId && session.id !== threadId && !session.endedAt ? { ...session, endedAt: now } : session);
13545
+ if (!sessions2.some((session) => session.id === threadId)) {
13546
+ sessions2.push({
13547
+ id: threadId,
13548
+ startedAt: now,
13549
+ cwd: input.cwd,
13550
+ harness: input.harness,
13551
+ transport: input.transport,
13552
+ model: input.model,
13553
+ approvalPolicy: input.approvalPolicy,
13554
+ sandbox: input.sandbox
13555
+ });
13556
+ }
13557
+ while (sessions2.length > SESSION_CATALOG_MAX_ENTRIES2) {
13558
+ sessions2.shift();
13559
+ }
13560
+ await writeCodexSessionCatalog(runtimeDirectory, {
13561
+ activeSessionId: threadId,
13562
+ sessions: sessions2
13563
+ });
13564
+ }
13565
+ async function closeCodexSessionCatalog(runtimeDirectory, threadId) {
13566
+ const catalog = await readCodexSessionCatalog(runtimeDirectory);
13567
+ const now = Date.now();
13568
+ const activeSessionId = threadId ?? catalog.activeSessionId;
13569
+ const sessions2 = catalog.sessions.map((session) => session.id === activeSessionId && !session.endedAt ? { ...session, endedAt: now } : session);
13570
+ await writeCodexSessionCatalog(runtimeDirectory, {
13571
+ activeSessionId: null,
13572
+ sessions: sessions2
13573
+ });
13574
+ }
13575
+ function parseCodexMaybeJson(value) {
13576
+ if (typeof value !== "string") {
13577
+ return value;
13578
+ }
13579
+ const trimmed = value.trim();
13580
+ if (!trimmed) {
13581
+ return value;
13582
+ }
13583
+ try {
13584
+ return JSON.parse(trimmed);
13585
+ } catch {
13586
+ return value;
13587
+ }
13588
+ }
13380
13589
  function isMissingCodexRolloutError2(error) {
13381
13590
  const message = errorMessage3(error).toLowerCase();
13382
13591
  return message.includes("no rollout found for thread id");
13383
13592
  }
13384
- function resolveCodexCompletionGraceMs() {
13385
- const parsed = Number.parseInt(process.env.OPENSCOUT_CODEX_COMPLETION_GRACE_MS ?? "", 10);
13386
- if (Number.isFinite(parsed) && parsed >= 0) {
13387
- return parsed;
13388
- }
13389
- return 60000;
13390
- }
13391
13593
 
13392
13594
  class CodexAppServerSession {
13393
13595
  options;
@@ -13436,14 +13638,14 @@ class CodexAppServerSession {
13436
13638
  threadId: this.threadId
13437
13639
  };
13438
13640
  }
13439
- async invoke(prompt, timeoutMs = 5 * 60000, replyContext) {
13641
+ async invoke(prompt, timeoutMs, replyContext) {
13440
13642
  return this.enqueue(async () => {
13441
13643
  await this.ensureStarted();
13442
13644
  if (!this.threadId) {
13443
13645
  throw new Error(`Codex app-server session for ${this.options.agentName} has no active thread.`);
13444
13646
  }
13445
- const output = await new Promise(async (resolve4, reject) => {
13446
- const turn = this.createActiveTurn(resolve4, reject, timeoutMs);
13647
+ const outputPromise = new Promise(async (resolve4, reject) => {
13648
+ const turn = this.createActiveTurn(resolve4, reject);
13447
13649
  try {
13448
13650
  await this.writeReplyContext(replyContext ?? null);
13449
13651
  const response = await this.request("turn/start", {
@@ -13465,6 +13667,7 @@ class CodexAppServerSession {
13465
13667
  reject(error instanceof Error ? error : new Error(errorMessage3(error)));
13466
13668
  }
13467
13669
  });
13670
+ const output = await waitForRequesterResult2(outputPromise, timeoutMs, this.options.agentName);
13468
13671
  return {
13469
13672
  output,
13470
13673
  threadId: this.threadId
@@ -13474,7 +13677,7 @@ class CodexAppServerSession {
13474
13677
  hasActiveTurn() {
13475
13678
  return Boolean(this.activeTurn);
13476
13679
  }
13477
- async steerAndWait(prompt, timeoutMs = 5 * 60000) {
13680
+ async steerAndWait(prompt, timeoutMs) {
13478
13681
  await this.ensureStarted();
13479
13682
  if (!this.threadId) {
13480
13683
  throw new Error(`Codex app-server session for ${this.options.agentName} has no active thread.`);
@@ -13538,9 +13741,6 @@ class CodexAppServerSession {
13538
13741
  const activeTurn = this.activeTurn;
13539
13742
  this.activeTurn = null;
13540
13743
  if (activeTurn) {
13541
- if (activeTurn.timer) {
13542
- clearTimeout(activeTurn.timer);
13543
- }
13544
13744
  activeTurn.reject(new Error(`Codex app-server session for ${this.options.agentName} was shut down.`));
13545
13745
  }
13546
13746
  for (const pending of this.pendingRequests.values()) {
@@ -13559,6 +13759,7 @@ class CodexAppServerSession {
13559
13759
  }
13560
13760
  }
13561
13761
  if (options.resetThread) {
13762
+ await closeCodexSessionCatalog(this.options.runtimeDirectory, this.threadId);
13562
13763
  this.threadId = null;
13563
13764
  this.threadPath = null;
13564
13765
  await rm4(this.threadIdPath, { force: true });
@@ -13579,6 +13780,8 @@ class CodexAppServerSession {
13579
13780
  cwd: options.cwd,
13580
13781
  sessionId: options.sessionId,
13581
13782
  systemPrompt: options.systemPrompt,
13783
+ approvalPolicy: options.approvalPolicy ?? "never",
13784
+ sandbox: options.sandbox ?? "danger-full-access",
13582
13785
  threadId: options.threadId ?? null,
13583
13786
  requireExistingThread: options.requireExistingThread === true,
13584
13787
  launchArgs: normalizeCodexAppServerLaunchArgs(options.launchArgs)
@@ -13665,14 +13868,15 @@ class CodexAppServerSession {
13665
13868
  const resumed = await this.request("thread/resume", {
13666
13869
  threadId: storedThreadId,
13667
13870
  cwd: this.options.cwd,
13668
- approvalPolicy: "never",
13669
- sandbox: "danger-full-access",
13871
+ approvalPolicy: this.options.approvalPolicy ?? "never",
13872
+ sandbox: this.options.sandbox ?? "danger-full-access",
13670
13873
  baseInstructions: this.options.systemPrompt,
13671
13874
  persistExtendedHistory: true
13672
13875
  });
13673
13876
  this.threadId = resumed.thread.id;
13674
13877
  this.threadPath = resumed.thread.path ?? null;
13675
13878
  await this.persistThreadId();
13879
+ await recordCodexSessionCatalog(this.options.runtimeDirectory, this.threadId, this.catalogRuntimeMeta());
13676
13880
  return;
13677
13881
  } catch (error) {
13678
13882
  await appendFile3(this.stderrLogPath, `[openscout] failed to resume stored Codex thread ${storedThreadId}: ${errorMessage3(error)}
@@ -13695,8 +13899,8 @@ class CodexAppServerSession {
13695
13899
  }
13696
13900
  const started = await this.request("thread/start", {
13697
13901
  cwd: this.options.cwd,
13698
- approvalPolicy: "never",
13699
- sandbox: "danger-full-access",
13902
+ approvalPolicy: this.options.approvalPolicy ?? "never",
13903
+ sandbox: this.options.sandbox ?? "danger-full-access",
13700
13904
  baseInstructions: this.options.systemPrompt,
13701
13905
  ephemeral: false,
13702
13906
  experimentalRawEvents: false,
@@ -13705,71 +13909,47 @@ class CodexAppServerSession {
13705
13909
  this.threadId = started.thread.id;
13706
13910
  this.threadPath = started.thread.path ?? null;
13707
13911
  await this.persistThreadId();
13912
+ await recordCodexSessionCatalog(this.options.runtimeDirectory, this.threadId, this.catalogRuntimeMeta());
13708
13913
  }
13709
- createActiveTurn(resolve4, reject, timeoutMs) {
13914
+ catalogRuntimeMeta() {
13915
+ return {
13916
+ cwd: this.options.cwd,
13917
+ harness: "codex",
13918
+ transport: "codex_app_server",
13919
+ model: readCodexAppServerModelFromLaunchArgs(this.options.launchArgs),
13920
+ approvalPolicy: this.options.approvalPolicy ?? "never",
13921
+ sandbox: this.options.sandbox ?? "danger-full-access"
13922
+ };
13923
+ }
13924
+ createActiveTurn(resolve4, reject) {
13710
13925
  if (this.activeTurn) {
13711
13926
  throw new Error(`Codex app-server session for ${this.options.agentName} already has an active turn.`);
13712
13927
  }
13713
13928
  const turn = {
13714
13929
  turnId: "",
13715
13930
  startedAt: Date.now(),
13716
- timer: null,
13717
- graceTimer: null,
13718
- timedOutAt: null,
13719
13931
  messageOrder: [],
13720
13932
  messageByItemId: new Map,
13721
13933
  resolve: resolve4,
13722
13934
  reject,
13723
13935
  watchers: []
13724
13936
  };
13725
- turn.timer = setTimeout(() => {
13726
- this.scheduleTurnTimeout(turn, timeoutMs);
13727
- }, timeoutMs);
13728
13937
  this.activeTurn = turn;
13729
13938
  return turn;
13730
13939
  }
13731
- buildTurnTimeoutError(timeoutMs) {
13732
- return new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`);
13733
- }
13734
- scheduleTurnTimeout(turn, timeoutMs) {
13735
- if (turn.timedOutAt !== null) {
13736
- return;
13737
- }
13738
- turn.timedOutAt = Date.now();
13739
- this.interrupt().catch(() => {
13740
- return;
13741
- });
13742
- const graceMs = resolveCodexCompletionGraceMs();
13743
- if (graceMs <= 0) {
13744
- this.rejectTimedOutTurn(turn, timeoutMs);
13745
- return;
13746
- }
13747
- turn.graceTimer = setTimeout(() => {
13748
- this.rejectTimedOutTurn(turn, timeoutMs);
13749
- }, graceMs);
13750
- }
13751
- rejectTimedOutTurn(turn, timeoutMs) {
13752
- if (this.activeTurn !== turn) {
13753
- this.clearActiveTurn(turn);
13754
- return;
13755
- }
13756
- this.clearActiveTurn(turn);
13757
- const error = this.buildTurnTimeoutError(timeoutMs);
13758
- for (const watcher of this.drainTurnWatchers(turn)) {
13759
- watcher.reject(error);
13760
- }
13761
- turn.reject(error);
13762
- }
13763
13940
  addTurnWatcher(turn, resolve4, reject, timeoutMs) {
13764
13941
  const watcher = {
13765
13942
  resolve: resolve4,
13766
13943
  reject,
13767
13944
  timer: null
13768
13945
  };
13769
- watcher.timer = setTimeout(() => {
13770
- this.removeTurnWatcher(turn, watcher);
13771
- reject(new Error(`Timed out after ${timeoutMs}ms waiting for ${this.options.agentName}.`));
13772
- }, timeoutMs);
13946
+ const effectiveTimeoutMs = resolveRequesterTimeoutMs2(timeoutMs);
13947
+ if (effectiveTimeoutMs !== null) {
13948
+ watcher.timer = setTimeout(() => {
13949
+ this.removeTurnWatcher(turn, watcher);
13950
+ reject(new Error(`Timed out after ${effectiveTimeoutMs}ms waiting for ${this.options.agentName}.`));
13951
+ }, effectiveTimeoutMs);
13952
+ }
13773
13953
  turn.watchers.push(watcher);
13774
13954
  return watcher;
13775
13955
  }
@@ -13790,12 +13970,6 @@ class CodexAppServerSession {
13790
13970
  return watchers;
13791
13971
  }
13792
13972
  clearActiveTurn(turn) {
13793
- if (turn.timer) {
13794
- clearTimeout(turn.timer);
13795
- }
13796
- if (turn.graceTimer) {
13797
- clearTimeout(turn.graceTimer);
13798
- }
13799
13973
  if (this.activeTurn === turn) {
13800
13974
  this.activeTurn = null;
13801
13975
  }
@@ -13992,9 +14166,6 @@ class CodexAppServerSession {
13992
14166
  const activeTurn = this.activeTurn;
13993
14167
  this.activeTurn = null;
13994
14168
  if (activeTurn) {
13995
- if (activeTurn.timer) {
13996
- clearTimeout(activeTurn.timer);
13997
- }
13998
14169
  for (const watcher of this.drainTurnWatchers(activeTurn)) {
13999
14170
  watcher.reject(error);
14000
14171
  }
@@ -14098,6 +14269,8 @@ async function shutdownCodexAppServerAgent(options, shutdownOptions = {}) {
14098
14269
  if (!session) {
14099
14270
  if (shutdownOptions.resetThread) {
14100
14271
  const runtimeDirectory = options.runtimeDirectory;
14272
+ const threadId = await readOptionalFile2(join13(runtimeDirectory, "codex-thread-id.txt"));
14273
+ await closeCodexSessionCatalog(runtimeDirectory, threadId);
14101
14274
  await rm4(join13(runtimeDirectory, "codex-thread-id.txt"), { force: true });
14102
14275
  }
14103
14276
  return;
@@ -14649,12 +14822,15 @@ function runtimePackageDir() {
14649
14822
  const explicit = process.env.OPENSCOUT_RUNTIME_PACKAGE_DIR?.trim();
14650
14823
  if (explicit)
14651
14824
  return explicit;
14652
- const fromGlobal = findGlobalRuntimeDir();
14653
- if (fromGlobal)
14654
- return fromGlobal;
14825
+ const fromBundledPackage = findBundledRuntimeDir();
14826
+ if (fromBundledPackage)
14827
+ return fromBundledPackage;
14655
14828
  const fromCwd = findWorkspaceRuntimeDir(process.cwd());
14656
14829
  if (fromCwd)
14657
14830
  return fromCwd;
14831
+ const fromGlobal = findGlobalRuntimeDir();
14832
+ if (fromGlobal)
14833
+ return fromGlobal;
14658
14834
  const moduleDir = dirname6(fileURLToPath4(import.meta.url));
14659
14835
  return resolve5(moduleDir, "..");
14660
14836
  }
@@ -14676,6 +14852,8 @@ function findGlobalRuntimeDir() {
14676
14852
  const scoutBin = result.stdout?.trim();
14677
14853
  if (scoutBin) {
14678
14854
  const scoutPkg = resolve5(scoutBin, "..", "..");
14855
+ if (isInstalledRuntimePackageDir(scoutPkg))
14856
+ return scoutPkg;
14679
14857
  const nested = join15(scoutPkg, "node_modules", "@openscout", "runtime");
14680
14858
  if (isInstalledRuntimePackageDir(nested))
14681
14859
  return nested;
@@ -14686,6 +14864,11 @@ function findGlobalRuntimeDir() {
14686
14864
  } catch {}
14687
14865
  return null;
14688
14866
  }
14867
+ function findBundledRuntimeDir() {
14868
+ const moduleDir = dirname6(fileURLToPath4(import.meta.url));
14869
+ const candidate = resolve5(moduleDir, "..");
14870
+ return isInstalledRuntimePackageDir(candidate) ? candidate : null;
14871
+ }
14689
14872
  function findWorkspaceRuntimeDir(startDir) {
14690
14873
  let current = resolve5(startDir);
14691
14874
  while (true) {
@@ -14724,10 +14907,21 @@ function resolveBrokerStartTimeoutMs() {
14724
14907
  return DEFAULT_BROKER_START_TIMEOUT_MS;
14725
14908
  }
14726
14909
  function resolveBrokerServiceLabel(mode) {
14727
- const explicit = process.env.OPENSCOUT_BROKER_SERVICE_LABEL?.trim();
14910
+ const explicit = process.env.OPENSCOUT_SERVICE_LABEL?.trim() || process.env.OPENSCOUT_BROKER_SERVICE_LABEL?.trim();
14728
14911
  if (explicit) {
14729
14912
  return explicit;
14730
14913
  }
14914
+ switch (mode) {
14915
+ case "prod":
14916
+ return "com.openscout";
14917
+ case "custom":
14918
+ return "com.openscout.custom";
14919
+ case "dev":
14920
+ default:
14921
+ return "dev.openscout";
14922
+ }
14923
+ }
14924
+ function legacyBrokerServiceLabel(mode) {
14731
14925
  switch (mode) {
14732
14926
  case "prod":
14733
14927
  return "com.openscout.broker";
@@ -14786,6 +14980,7 @@ function renderLaunchAgentPlist(config) {
14786
14980
  OPENSCOUT_CONTROL_HOME: config.controlHome,
14787
14981
  OPENSCOUT_BROKER_SERVICE_MODE: config.mode,
14788
14982
  OPENSCOUT_BROKER_SERVICE_LABEL: config.label,
14983
+ OPENSCOUT_SERVICE_LABEL: config.label,
14789
14984
  OPENSCOUT_ADVERTISE_SCOPE: config.advertiseScope,
14790
14985
  HOME: homedir14(),
14791
14986
  PATH: launchPath,
@@ -14814,7 +15009,7 @@ function renderLaunchAgentPlist(config) {
14814
15009
  <array>
14815
15010
  <string>${xmlEscape(config.bunExecutable)}</string>
14816
15011
  <string>${xmlEscape(join15(config.runtimePackageDir, "bin", "openscout-runtime.mjs"))}</string>
14817
- <string>broker</string>
15012
+ <string>base</string>
14818
15013
  </array>
14819
15014
  <key>WorkingDirectory</key>
14820
15015
  <string>${xmlEscape(config.runtimePackageDir)}</string>
@@ -14878,6 +15073,22 @@ function writeLaunchAgentPlist(config) {
14878
15073
  ensureServiceDirectories(config);
14879
15074
  writeFileSync5(config.launchAgentPath, renderLaunchAgentPlist(config), "utf8");
14880
15075
  }
15076
+ function bootoutCommand(config) {
15077
+ return `${launchctlPath()} bootout ${config.serviceTarget}`;
15078
+ }
15079
+ function legacyLaunchAgentPath(config) {
15080
+ return join15(homedir14(), "Library", "LaunchAgents", `${legacyBrokerServiceLabel(config.mode)}.plist`);
15081
+ }
15082
+ function legacyServiceTarget(config) {
15083
+ return `${config.domainTarget}/${legacyBrokerServiceLabel(config.mode)}`;
15084
+ }
15085
+ function bootoutLegacyBrokerService(config) {
15086
+ const legacyTarget = legacyServiceTarget(config);
15087
+ if (legacyTarget === config.serviceTarget) {
15088
+ return;
15089
+ }
15090
+ runCommand(launchctlPath(), ["bootout", legacyTarget], { allowFailure: true });
15091
+ }
14881
15092
  function runCommand(command, args, options) {
14882
15093
  const result = spawnSync(command, args, {
14883
15094
  env: {
@@ -14996,6 +15207,7 @@ async function brokerServiceStatus(config = resolveBrokerServiceConfig()) {
14996
15207
  label: config.label,
14997
15208
  mode: config.mode,
14998
15209
  launchAgentPath: config.launchAgentPath,
15210
+ bootoutCommand: bootoutCommand(config),
14999
15211
  brokerUrl: config.brokerUrl,
15000
15212
  brokerSocketPath: config.brokerSocketPath,
15001
15213
  supportDirectory: config.supportDirectory,
@@ -15015,10 +15227,12 @@ async function brokerServiceStatus(config = resolveBrokerServiceConfig()) {
15015
15227
  };
15016
15228
  }
15017
15229
  async function installBrokerService(config = resolveBrokerServiceConfig()) {
15230
+ bootoutLegacyBrokerService(config);
15018
15231
  writeLaunchAgentPlist(config);
15019
15232
  return brokerServiceStatus(config);
15020
15233
  }
15021
15234
  async function startBrokerService(config = resolveBrokerServiceConfig()) {
15235
+ bootoutLegacyBrokerService(config);
15022
15236
  writeLaunchAgentPlist(config);
15023
15237
  runCommand(launchctlPath(), ["bootout", config.serviceTarget], { allowFailure: true });
15024
15238
  runCommand(launchctlPath(), ["bootstrap", config.domainTarget, config.launchAgentPath], { allowFailure: true });
@@ -15057,6 +15271,11 @@ async function uninstallBrokerService(config = resolveBrokerServiceConfig()) {
15057
15271
  if (existsSync13(config.launchAgentPath)) {
15058
15272
  rmSync2(config.launchAgentPath, { force: true });
15059
15273
  }
15274
+ const legacyPath = legacyLaunchAgentPath(config);
15275
+ bootoutLegacyBrokerService(config);
15276
+ if (existsSync13(legacyPath)) {
15277
+ rmSync2(legacyPath, { force: true });
15278
+ }
15060
15279
  return brokerServiceStatus(config);
15061
15280
  }
15062
15281
  async function main() {
@@ -15098,6 +15317,7 @@ function formatBrokerServiceStatus(status) {
15098
15317
  `label: ${status.label}`,
15099
15318
  `mode: ${status.mode}`,
15100
15319
  `launch agent: ${status.installed ? status.launchAgentPath : "not installed"}`,
15320
+ `bootout: ${status.bootoutCommand}`,
15101
15321
  `loaded: ${status.loaded ? "yes" : "no"}`,
15102
15322
  `pid: ${status.pid ?? "\u2014"}`,
15103
15323
  `launchd state: ${status.launchdState ?? "\u2014"}`,
@@ -15127,6 +15347,58 @@ var LOCAL_AGENT_SYSTEM_PROMPT_TEMPLATE_HINT = [
15127
15347
  "{{relay_hub}}, {{broker_url}}, {{relay_command}}, {{openscout_root}}, {{relay_agent_comms_skill}}, and {{env.NAME}} variables."
15128
15348
  ].join("");
15129
15349
 
15350
+ // ../runtime/src/permission-policy.ts
15351
+ function parseScoutPermissionProfile(value) {
15352
+ const profile = normalizeScoutPermissionProfile(value);
15353
+ if (value && !profile) {
15354
+ throw new Error(`Unknown permission profile "${value}". Expected one of: ${formatScoutPermissionProfiles()}.`);
15355
+ }
15356
+ return profile;
15357
+ }
15358
+ function compileCodexPermissionProfile(profileInput) {
15359
+ const profile = profileInput ? parseScoutPermissionProfile(profileInput) ?? "trusted_local" : "trusted_local";
15360
+ switch (profile) {
15361
+ case "observe":
15362
+ case "review":
15363
+ return {
15364
+ profile,
15365
+ sandbox: "read-only",
15366
+ approvalPolicy: "on-request",
15367
+ enforcement: "native"
15368
+ };
15369
+ case "workspace_write":
15370
+ return {
15371
+ profile,
15372
+ sandbox: "workspace-write",
15373
+ approvalPolicy: "on-request",
15374
+ enforcement: "native"
15375
+ };
15376
+ case "sandboxed_write":
15377
+ return {
15378
+ profile,
15379
+ sandbox: "workspace-write",
15380
+ approvalPolicy: "on-request",
15381
+ enforcement: "advisory",
15382
+ note: "External sandbox placement is not wired yet; Codex receives its native workspace-write sandbox."
15383
+ };
15384
+ case "external_sandbox":
15385
+ return {
15386
+ profile,
15387
+ sandbox: "workspace-write",
15388
+ approvalPolicy: "on-request",
15389
+ enforcement: "advisory",
15390
+ note: "External sandbox placement is not wired yet; Codex receives its native workspace-write sandbox."
15391
+ };
15392
+ case "trusted_local":
15393
+ return {
15394
+ profile,
15395
+ sandbox: "danger-full-access",
15396
+ approvalPolicy: "never",
15397
+ enforcement: "native"
15398
+ };
15399
+ }
15400
+ }
15401
+
15130
15402
  // ../runtime/src/local-agents.ts
15131
15403
  var MODULE_DIRECTORY = dirname7(fileURLToPath5(import.meta.url));
15132
15404
  var OPENSCOUT_REPO_ROOT = resolve6(MODULE_DIRECTORY, "..", "..", "..");
@@ -15576,6 +15848,46 @@ function buildLaunchArgsForRequestedModel(harness, model) {
15576
15848
  }
15577
15849
  return [];
15578
15850
  }
15851
+ function normalizeRequestedReasoningEffort(reasoningEffort) {
15852
+ const trimmed = reasoningEffort?.trim();
15853
+ return trimmed ? trimmed : undefined;
15854
+ }
15855
+ function stripLaunchReasoningEffortForHarness(harness, launchArgs) {
15856
+ if (harness !== "codex") {
15857
+ return normalizeLaunchArgsForHarness(harness, launchArgs);
15858
+ }
15859
+ const next = [];
15860
+ const normalized = normalizeCodexAppServerLaunchArgs(launchArgs);
15861
+ for (let index = 0;index < normalized.length; index += 1) {
15862
+ const current = normalized[index] ?? "";
15863
+ if (current === "-c" || current === "--config") {
15864
+ const value = normalized[index + 1];
15865
+ if (readCodexAppServerReasoningEffortFromLaunchArgs(value ? [current, value] : [current])) {
15866
+ index += 1;
15867
+ continue;
15868
+ }
15869
+ next.push(current);
15870
+ if (value) {
15871
+ next.push(value);
15872
+ index += 1;
15873
+ }
15874
+ continue;
15875
+ }
15876
+ if (current.startsWith("--config=")) {
15877
+ if (readCodexAppServerReasoningEffortFromLaunchArgs([current])) {
15878
+ continue;
15879
+ }
15880
+ }
15881
+ next.push(current);
15882
+ }
15883
+ return next;
15884
+ }
15885
+ function buildLaunchArgsForRequestedReasoningEffort(harness, reasoningEffort) {
15886
+ if (harness === "codex") {
15887
+ return normalizeCodexAppServerLaunchArgs(["--reasoning-effort", reasoningEffort]);
15888
+ }
15889
+ return [];
15890
+ }
15579
15891
  function applyRequestedModelToLaunchArgs(harness, launchArgs, model) {
15580
15892
  const normalized = normalizeLaunchArgsForHarness(harness, launchArgs);
15581
15893
  const requestedModel = normalizeRequestedModel(model);
@@ -15587,6 +15899,17 @@ function applyRequestedModelToLaunchArgs(harness, launchArgs, model) {
15587
15899
  ...buildLaunchArgsForRequestedModel(harness, requestedModel)
15588
15900
  ];
15589
15901
  }
15902
+ function applyRequestedRuntimeOptionsToLaunchArgs(harness, launchArgs, options) {
15903
+ const withModel = applyRequestedModelToLaunchArgs(harness, launchArgs, options.model);
15904
+ const requestedReasoningEffort = normalizeRequestedReasoningEffort(options.reasoningEffort);
15905
+ if (!requestedReasoningEffort) {
15906
+ return withModel;
15907
+ }
15908
+ return [
15909
+ ...stripLaunchReasoningEffortForHarness(harness, withModel),
15910
+ ...buildLaunchArgsForRequestedReasoningEffort(harness, requestedReasoningEffort)
15911
+ ];
15912
+ }
15590
15913
  function defaultHarnessForOverride(override, fallback = "claude") {
15591
15914
  return normalizeManagedHarness2(override.defaultHarness ?? override.runtime?.harness, fallback);
15592
15915
  }
@@ -15606,16 +15929,17 @@ function overrideHarnessProfile(override, harness) {
15606
15929
  cwd: normalizeProjectPath(profile?.cwd || override.runtime?.cwd || override.projectRoot || process.cwd()),
15607
15930
  transport: normalizeLocalAgentTransport(profile?.transport ?? override.runtime?.transport, harness),
15608
15931
  sessionId: normalizeTmuxSessionName2(profile?.sessionId, `${override.agentId}-${harness}`),
15609
- launchArgs: launchArgsForOverrideHarness(override, harness)
15932
+ launchArgs: launchArgsForOverrideHarness(override, harness),
15933
+ ...profile?.permissionProfile ? { permissionProfile: profile.permissionProfile } : {}
15610
15934
  };
15611
15935
  }
15612
15936
  function normalizeManagedHarness2(value, fallback) {
15613
- return value === "codex" ? "codex" : value === "claude" ? "claude" : fallback;
15937
+ return value === "codex" ? "codex" : value === "claude" ? "claude" : value === "cursor" ? "cursor" : fallback;
15614
15938
  }
15615
15939
  function normalizeLocalHarnessProfiles(agentId, record) {
15616
15940
  const defaultHarness = normalizeManagedHarness2(typeof record.defaultHarness === "string" ? record.defaultHarness : record.harness, "claude");
15617
15941
  const nextProfiles = {};
15618
- for (const harness of ["claude", "codex"]) {
15942
+ for (const harness of ["claude", "codex", "cursor"]) {
15619
15943
  const profile = record.harnessProfiles?.[harness];
15620
15944
  if (!profile) {
15621
15945
  continue;
@@ -15624,7 +15948,8 @@ function normalizeLocalHarnessProfiles(agentId, record) {
15624
15948
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
15625
15949
  transport: normalizeLocalAgentTransport(profile.transport, harness),
15626
15950
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
15627
- launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
15951
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs),
15952
+ ...profile.permissionProfile ? { permissionProfile: profile.permissionProfile } : {}
15628
15953
  };
15629
15954
  }
15630
15955
  const runtimeHarness = normalizeManagedHarness2(record.harness, defaultHarness);
@@ -15633,7 +15958,8 @@ function normalizeLocalHarnessProfiles(agentId, record) {
15633
15958
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
15634
15959
  transport: normalizeLocalAgentTransport(record.transport, runtimeHarness),
15635
15960
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${runtimeHarness}`),
15636
- launchArgs: normalizeLaunchArgsForHarness(runtimeHarness, record.launchArgs)
15961
+ launchArgs: normalizeLaunchArgsForHarness(runtimeHarness, record.launchArgs),
15962
+ ...record.permissionProfile ? { permissionProfile: record.permissionProfile } : {}
15637
15963
  };
15638
15964
  }
15639
15965
  if (!nextProfiles[defaultHarness]) {
@@ -15641,10 +15967,11 @@ function normalizeLocalHarnessProfiles(agentId, record) {
15641
15967
  cwd: normalizeProjectPath(record.cwd || process.cwd()),
15642
15968
  transport: normalizeLocalAgentTransport(record.transport, defaultHarness),
15643
15969
  sessionId: normalizeTmuxSessionName2(record.tmuxSession, `${agentId}-${defaultHarness}`),
15644
- launchArgs: normalizeLaunchArgsForHarness(defaultHarness, record.launchArgs)
15970
+ launchArgs: normalizeLaunchArgsForHarness(defaultHarness, record.launchArgs),
15971
+ ...record.permissionProfile ? { permissionProfile: record.permissionProfile } : {}
15645
15972
  };
15646
15973
  }
15647
- for (const harness of ["claude", "codex"]) {
15974
+ for (const harness of ["claude", "codex", "cursor"]) {
15648
15975
  const profile = nextProfiles[harness];
15649
15976
  if (!profile) {
15650
15977
  continue;
@@ -15653,7 +15980,8 @@ function normalizeLocalHarnessProfiles(agentId, record) {
15653
15980
  cwd: normalizeProjectPath(profile.cwd || record.cwd || process.cwd()),
15654
15981
  transport: normalizeLocalAgentTransport(profile.transport, harness),
15655
15982
  sessionId: normalizeTmuxSessionName2(profile.sessionId, `${agentId}-${harness}`),
15656
- launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs)
15983
+ launchArgs: normalizeLaunchArgsForHarness(harness, profile.launchArgs),
15984
+ ...profile.permissionProfile ? { permissionProfile: profile.permissionProfile } : {}
15657
15985
  };
15658
15986
  }
15659
15987
  return nextProfiles;
@@ -15678,13 +16006,15 @@ function recordForHarness(record, harnessOverride) {
15678
16006
  cwd: fallbackCwd,
15679
16007
  transport: fallbackTransport,
15680
16008
  launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs),
16009
+ ...normalized.permissionProfile ? { permissionProfile: normalized.permissionProfile } : {},
15681
16010
  harnessProfiles: {
15682
16011
  ...normalized.harnessProfiles,
15683
16012
  [selectedHarness]: {
15684
16013
  cwd: fallbackCwd,
15685
16014
  transport: fallbackTransport,
15686
16015
  sessionId: fallbackSessionId,
15687
- launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs)
16016
+ launchArgs: normalizeLaunchArgsForHarness(selectedHarness, normalized.launchArgs),
16017
+ ...normalized.permissionProfile ? { permissionProfile: normalized.permissionProfile } : {}
15688
16018
  }
15689
16019
  }
15690
16020
  };
@@ -15696,7 +16026,8 @@ function recordForHarness(record, harnessOverride) {
15696
16026
  tmuxSession: profile.sessionId,
15697
16027
  cwd: profile.cwd,
15698
16028
  transport: profile.transport,
15699
- launchArgs: profile.launchArgs
16029
+ launchArgs: profile.launchArgs,
16030
+ permissionProfile: profile.permissionProfile
15700
16031
  };
15701
16032
  }
15702
16033
  function normalizeLocalAgentRecord(agentId, record) {
@@ -15727,6 +16058,7 @@ function normalizeLocalAgentRecord(agentId, record) {
15727
16058
  transport: activeProfile?.transport ?? normalizeLocalAgentTransport(record.transport, harness),
15728
16059
  capabilities: normalizeLocalAgentCapabilities(record.capabilities),
15729
16060
  launchArgs: activeProfile?.launchArgs ?? normalizeLaunchArgsForHarness(harness, record.launchArgs),
16061
+ permissionProfile: activeProfile?.permissionProfile ?? record.permissionProfile,
15730
16062
  registrationSource: record.registrationSource
15731
16063
  };
15732
16064
  }
@@ -15812,6 +16144,7 @@ function relayAgentOverrideFromLocalAgentRecord(agentId, record, existing) {
15812
16144
  };
15813
16145
  }
15814
16146
  function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
16147
+ const permissionPosture = compileCodexPermissionProfile(record.permissionProfile);
15815
16148
  return {
15816
16149
  agentName,
15817
16150
  sessionId: record.tmuxSession,
@@ -15819,7 +16152,10 @@ function buildCodexAgentSessionOptions(agentName, record, systemPrompt) {
15819
16152
  systemPrompt: systemPrompt ?? buildLocalAgentSystemPrompt(agentName, record.project, record.cwd, { transport: "codex_app_server" }),
15820
16153
  runtimeDirectory: relayAgentRuntimeDirectory(agentName),
15821
16154
  logsDirectory: relayAgentLogsDirectory(agentName),
15822
- launchArgs: normalizeLaunchArgsForHarness("codex", record.launchArgs)
16155
+ launchArgs: normalizeLaunchArgsForHarness("codex", record.launchArgs),
16156
+ permissionProfile: permissionPosture.profile,
16157
+ approvalPolicy: permissionPosture.approvalPolicy,
16158
+ sandbox: permissionPosture.sandbox
15823
16159
  };
15824
16160
  }
15825
16161
  function buildClaudeAgentSessionOptions(agentName, record, systemPrompt) {
@@ -16076,6 +16412,7 @@ async function startLocalAgent(input) {
16076
16412
  const currentDirectory = input.currentDirectory ?? projectPath;
16077
16413
  const effectiveCwd = input.cwdOverride ? normalizeProjectPath(input.cwdOverride) : undefined;
16078
16414
  const shouldEnsureOnline = input.ensureOnline !== false;
16415
+ const requestedPermissionProfile = parseScoutPermissionProfile(input.permissionProfile);
16079
16416
  const requestedDefinitionId = input.agentName?.trim() ? normalizeAgentSelectorSegment(input.agentName.trim()) : "";
16080
16417
  if (input.agentName?.trim() && !requestedDefinitionId) {
16081
16418
  throw new Error(`Invalid agent name "${input.agentName}".`);
@@ -16131,7 +16468,10 @@ async function startLocalAgent(input) {
16131
16468
  const effectiveHarness = normalizeManagedHarness2(preferredHarness ?? configDefaultHarness, "claude");
16132
16469
  const transport = normalizeLocalAgentTransport(undefined, effectiveHarness);
16133
16470
  const sessionId = normalizeTmuxSessionName2(undefined, `${instance.id}-${effectiveHarness}`);
16134
- const launchArgs = applyRequestedModelToLaunchArgs(effectiveHarness, [], input.model);
16471
+ const launchArgs = applyRequestedRuntimeOptionsToLaunchArgs(effectiveHarness, [], {
16472
+ model: input.model,
16473
+ reasoningEffort: input.reasoningEffort
16474
+ });
16135
16475
  const existingForInstance = overrides[instance.id];
16136
16476
  if (existingForInstance && normalizeProjectPath(existingForInstance.projectRoot) !== projectRoot) {
16137
16477
  throw new Error(`Another agent is already registered as ${instance.id} at ${existingForInstance.projectRoot}. ` + `Two clones of the same project on the same branch cannot both register here; ` + `rename one of the checkouts or switch its branch before running scout up.`);
@@ -16152,7 +16492,8 @@ async function startLocalAgent(input) {
16152
16492
  cwd: effectiveCwd ?? projectRoot,
16153
16493
  transport,
16154
16494
  sessionId,
16155
- launchArgs
16495
+ launchArgs,
16496
+ ...requestedPermissionProfile ? { permissionProfile: requestedPermissionProfile } : {}
16156
16497
  }
16157
16498
  },
16158
16499
  runtime: {
@@ -16176,7 +16517,10 @@ async function startLocalAgent(input) {
16176
16517
  const nextHarness = normalizeManagedHarness2(resolvedHarness, "claude");
16177
16518
  const nextDefaultHarness = preferredHarness ? normalizeManagedHarness2(preferredHarness, "claude") : defaultHarnessForOverride(matchingOverride, "claude");
16178
16519
  const nextSessionId = normalizeTmuxSessionName2(undefined, `${instance.id}-${nextHarness}`);
16179
- const nextLaunchArgs = applyRequestedModelToLaunchArgs(nextHarness, launchArgsForOverrideHarness(matchingOverride, nextHarness), input.model);
16520
+ const nextLaunchArgs = applyRequestedRuntimeOptionsToLaunchArgs(nextHarness, launchArgsForOverrideHarness(matchingOverride, nextHarness), {
16521
+ model: input.model,
16522
+ reasoningEffort: input.reasoningEffort
16523
+ });
16180
16524
  overrides[instance.id] = {
16181
16525
  agentId: instance.id,
16182
16526
  definitionId: requestedDefinitionId,
@@ -16194,7 +16538,8 @@ async function startLocalAgent(input) {
16194
16538
  [nextHarness]: {
16195
16539
  ...overrideHarnessProfile(matchingOverride, nextHarness),
16196
16540
  sessionId: nextSessionId,
16197
- launchArgs: nextLaunchArgs
16541
+ launchArgs: nextLaunchArgs,
16542
+ ...requestedPermissionProfile ? { permissionProfile: requestedPermissionProfile } : {}
16198
16543
  }
16199
16544
  },
16200
16545
  runtime: {
@@ -16208,9 +16553,12 @@ async function startLocalAgent(input) {
16208
16553
  await writeRelayAgentOverrides(overrides);
16209
16554
  targetAgentId = instance.id;
16210
16555
  } else {
16211
- if (input.model) {
16556
+ if (input.model || input.reasoningEffort || requestedPermissionProfile) {
16212
16557
  const existingHarness = normalizeManagedHarness2(preferredHarness ?? matchingOverride.defaultHarness ?? matchingOverride.runtime?.harness, "claude");
16213
- const nextLaunchArgs = applyRequestedModelToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), input.model);
16558
+ const nextLaunchArgs = applyRequestedRuntimeOptionsToLaunchArgs(existingHarness, launchArgsForOverrideHarness(matchingOverride, existingHarness), {
16559
+ model: input.model,
16560
+ reasoningEffort: input.reasoningEffort
16561
+ });
16214
16562
  overrides[matchingAgentId] = {
16215
16563
  ...matchingOverride,
16216
16564
  launchArgs: existingHarness === defaultHarnessForOverride(matchingOverride, existingHarness) ? nextLaunchArgs : matchingOverride.launchArgs,
@@ -16218,7 +16566,8 @@ async function startLocalAgent(input) {
16218
16566
  ...matchingOverride.harnessProfiles ?? {},
16219
16567
  [existingHarness]: {
16220
16568
  ...overrideHarnessProfile(matchingOverride, existingHarness),
16221
- launchArgs: nextLaunchArgs
16569
+ launchArgs: nextLaunchArgs,
16570
+ ...requestedPermissionProfile ? { permissionProfile: requestedPermissionProfile } : {}
16222
16571
  }
16223
16572
  }
16224
16573
  };
@@ -16322,6 +16671,7 @@ function readPersistedClaudeSessionId(agentName) {
16322
16671
  function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
16323
16672
  const normalizedRecord = normalizeLocalAgentRecord(agentId, record);
16324
16673
  const configuredModel = readLaunchModelForHarness(normalizeLocalAgentHarness(normalizedRecord.harness), normalizedRecord.launchArgs);
16674
+ const codexPermissionPosture = normalizeLocalAgentHarness(normalizedRecord.harness) === "codex" ? compileCodexPermissionProfile(normalizedRecord.permissionProfile) : null;
16325
16675
  const definitionId = normalizedRecord.definitionId ?? agentId;
16326
16676
  const displayName = titleCaseLocalAgentName(definitionId);
16327
16677
  const projectRoot = normalizedRecord.projectRoot ?? normalizedRecord.cwd;
@@ -16348,7 +16698,13 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
16348
16698
  nodeQualifier: instance.nodeQualifier,
16349
16699
  workspaceQualifier: instance.workspaceQualifier,
16350
16700
  branch: instance.branch,
16351
- ...configuredModel ? { model: configuredModel } : {}
16701
+ ...configuredModel ? { model: configuredModel } : {},
16702
+ ...codexPermissionPosture ? {
16703
+ permissionProfile: codexPermissionPosture.profile,
16704
+ approvalPolicy: codexPermissionPosture.approvalPolicy,
16705
+ sandbox: codexPermissionPosture.sandbox,
16706
+ permissionEnforcement: codexPermissionPosture.enforcement
16707
+ } : {}
16352
16708
  }
16353
16709
  },
16354
16710
  agent: {
@@ -16377,7 +16733,13 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
16377
16733
  nodeQualifier: instance.nodeQualifier,
16378
16734
  workspaceQualifier: instance.workspaceQualifier,
16379
16735
  branch: instance.branch,
16380
- ...configuredModel ? { model: configuredModel } : {}
16736
+ ...configuredModel ? { model: configuredModel } : {},
16737
+ ...codexPermissionPosture ? {
16738
+ permissionProfile: codexPermissionPosture.profile,
16739
+ approvalPolicy: codexPermissionPosture.approvalPolicy,
16740
+ sandbox: codexPermissionPosture.sandbox,
16741
+ permissionEnforcement: codexPermissionPosture.enforcement
16742
+ } : {}
16381
16743
  },
16382
16744
  agentClass: "general",
16383
16745
  capabilities: normalizeLocalAgentCapabilities(normalizedRecord.capabilities),
@@ -16412,6 +16774,12 @@ function buildLocalAgentBinding(agentId, record, alive, nodeId, source) {
16412
16774
  workspaceQualifier: instance.workspaceQualifier,
16413
16775
  branch: instance.branch,
16414
16776
  ...configuredModel ? { model: configuredModel } : {},
16777
+ ...codexPermissionPosture ? {
16778
+ permissionProfile: codexPermissionPosture.profile,
16779
+ approvalPolicy: codexPermissionPosture.approvalPolicy,
16780
+ sandbox: codexPermissionPosture.sandbox,
16781
+ permissionEnforcement: codexPermissionPosture.enforcement
16782
+ } : {},
16415
16783
  ...externalSessionId ? { externalSessionId } : {}
16416
16784
  }
16417
16785
  }
@@ -40091,6 +40459,7 @@ async function startSupervisorRuntime(state) {
40091
40459
  relay: activeRelayUrl,
40092
40460
  pairing: {
40093
40461
  relay: payload.relay,
40462
+ ...payload.fallbackRelays?.length ? { fallbackRelays: payload.fallbackRelays } : {},
40094
40463
  room: payload.room,
40095
40464
  publicKey: payload.publicKey,
40096
40465
  expiresAt: payload.expiresAt,