orkestrator 2.27.0-beta → 2.28.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orkestrator",
3
- "version": "2.27.0-beta",
3
+ "version": "2.28.0-beta",
4
4
  "description": "Run the Orkestrator backend service without the desktop application",
5
5
  "homepage": "https://orkestrator.dev",
6
6
  "repository": {
@@ -34,13 +34,13 @@
34
34
  "typecheck": "bunx tsc --noEmit -p tsconfig.json"
35
35
  },
36
36
  "dependencies": {
37
- "@anthropic-ai/claude-agent-sdk": "0.3.296",
37
+ "@anthropic-ai/claude-agent-sdk": "0.3.309",
38
38
  "playwright-core": "1.64.0",
39
39
  "sharp": "0.35.5"
40
40
  },
41
41
  "devDependencies": {
42
- "@orkestrator/protocol": "2.27.0-beta",
43
- "@orkestrator/toolchain": "2.27.0-beta"
42
+ "@orkestrator/protocol": "2.28.0-beta",
43
+ "@orkestrator/toolchain": "2.28.0-beta"
44
44
  },
45
45
  "engines": {
46
46
  "bun": ">=1.4.3"
@@ -3344,6 +3344,24 @@ function createStructuredUsageRefreshCoordinator(session, queryControl) {
3344
3344
  }
3345
3345
  };
3346
3346
  }
3347
+ var CLAUDE_CONTEXT_CATEGORY_IDS = {
3348
+ system_prompt: "system-prompt",
3349
+ system_tools: "tools",
3350
+ system_tools_deferred: "deferred-tools",
3351
+ mcp_tools: "mcp-tools",
3352
+ mcp_tools_deferred: "deferred-mcp-tools",
3353
+ mcp_instructions: "mcp-instructions",
3354
+ custom_agents: "agents",
3355
+ memory_files: "memory",
3356
+ skills: "skills",
3357
+ messages: "messages",
3358
+ autocompact_buffer: "reserved",
3359
+ compact_buffer: "reserved",
3360
+ free_space: "free-space"
3361
+ };
3362
+ function claudeContextCategoryId(value) {
3363
+ return typeof value === "string" && Object.hasOwn(CLAUDE_CONTEXT_CATEGORY_IDS, value) ? CLAUDE_CONTEXT_CATEGORY_IDS[value] : undefined;
3364
+ }
3347
3365
  function parseClaudeContextUsage(raw) {
3348
3366
  if (!raw || typeof raw !== "object")
3349
3367
  return;
@@ -3361,8 +3379,10 @@ function parseClaudeContextUsage(raw) {
3361
3379
  const tokens = parseTokenValue(item.tokens);
3362
3380
  if (!name || tokens === undefined)
3363
3381
  return [];
3382
+ const id = claudeContextCategoryId(item.id);
3364
3383
  return [
3365
3384
  {
3385
+ ...id ? { id } : {},
3366
3386
  name,
3367
3387
  tokens,
3368
3388
  color: typeof item.color === "string" ? item.color : undefined
@@ -3502,6 +3522,7 @@ async function buildClaudeUsageSnapshot(session, result, queryControl, fallbackM
3502
3522
  const turnId = typeof result.user_message_uuid === "string" && result.user_message_uuid.trim() || typeof result.uuid === "string" && result.uuid.trim() || `claude-turn-${session.latestTurnGeneration ?? updatedAt}`;
3503
3523
  const turn = {
3504
3524
  turnId,
3525
+ at: updatedAt,
3505
3526
  costUsd: totals.cost,
3506
3527
  inputTokens: totals.input,
3507
3528
  outputTokens: totals.output,
@@ -4392,6 +4413,15 @@ var OPTIONAL_PROJECTION_FIELD_SET = new Set([
4392
4413
  "slashCommandCatalogue"
4393
4414
  ]);
4394
4415
 
4416
+ // ../../bridges/claude-bridge/src/services/mcp-tool-prefix.ts
4417
+ function claudeMcpToolPrefix(name) {
4418
+ let normalized = name.replace(/[^a-zA-Z0-9_-]/g, "_");
4419
+ if (name.startsWith("claude.ai ")) {
4420
+ normalized = normalized.replace(/_+/g, "_").replace(/^_|_$/g, "");
4421
+ }
4422
+ return `mcp__${normalized}__`;
4423
+ }
4424
+
4395
4425
  // ../../bridges/claude-bridge/src/services/read-only-policy.ts
4396
4426
  var CAPABILITY_TOOLS = new Map([
4397
4427
  ["file.write", ["Write", "NotebookEdit"]],
@@ -4417,8 +4447,7 @@ var WAITING_TOOLS = new Set([
4417
4447
  "CronCreate",
4418
4448
  "CronList",
4419
4449
  "CronDelete",
4420
- "SendMessage",
4421
- "TaskOutput"
4450
+ "SendMessage"
4422
4451
  ]);
4423
4452
  var READ_ONLY_COMMANDS = Object.freeze(new Set([
4424
4453
  "awk",
@@ -4607,8 +4636,8 @@ function claudeReadOnlyAllowedTools(options) {
4607
4636
  return [
4608
4637
  ...READ_ONLY_TOOLS,
4609
4638
  ...options.policy && !nativeAgentPolicyDeniesWebSearch(options.policy) ? ["WebSearch"] : [],
4610
- ...(options.agentMcpServerNames ?? []).map((name) => `mcp__${name}__*`),
4611
- ...(options.researchMcpServerNames ?? []).map((name) => `mcp__${name}__*`)
4639
+ ...(options.agentMcpServerNames ?? []).map((name) => `${claudeMcpToolPrefix(name)}*`),
4640
+ ...(options.researchMcpServerNames ?? []).map((name) => `${claudeMcpToolPrefix(name)}*`)
4612
4641
  ];
4613
4642
  }
4614
4643
  function claudeReadOnlySandbox(policy) {
@@ -4679,8 +4708,8 @@ function deny(reason) {
4679
4708
  function createCoordinatorReadOnlyHook(policy, researchMcpServerNames = coordinatorResearchMcpServersFromEnv(process.env).map((server) => server.name)) {
4680
4709
  const deniedTools = new Set(claudeDeniedTools(policy));
4681
4710
  const allowedMcpPrefixes = [
4682
- "mcp__orkestrator__",
4683
- ...researchMcpServerNames.map((name) => `mcp__${name}__`)
4711
+ claudeMcpToolPrefix("orkestrator"),
4712
+ ...researchMcpServerNames.map(claudeMcpToolPrefix)
4684
4713
  ];
4685
4714
  return async (input) => {
4686
4715
  if (input.hook_event_name !== "PreToolUse")
@@ -13651,9 +13680,6 @@ function reportedStaleServers(mcp) {
13651
13680
  return;
13652
13681
  return Array.from(mcp.stale).sort().slice(0, MAX_REPORTED_STALE_SERVERS);
13653
13682
  }
13654
- function claudeMcpToolPrefix(name) {
13655
- return `mcp__${name.replace(/[^a-zA-Z0-9_-]/g, "_")}__`;
13656
- }
13657
13683
  function staleMcpServerFor(mcp, toolName, toolInput) {
13658
13684
  if (!mcp.stale.size)
13659
13685
  return;
@@ -10826,6 +10826,7 @@ var STRONG_REFUSAL_MARKERS = [
10826
10826
  "read-only file system"
10827
10827
  ];
10828
10828
  var SCRIPT_FAILED = "script failed";
10829
+ var INTERRUPTED_CELL = /^aborted by user\b/i;
10829
10830
  var MAX_PENDING_CELLS = 256;
10830
10831
  var MAX_CELL_TEXT_CHARS = 64 * 1024;
10831
10832
  var ROLLOUT_POLL_INTERVAL_MS = 200;
@@ -10857,7 +10858,12 @@ function reportsRefusal(output) {
10857
10858
  }
10858
10859
  function codeModeCellOutcome(output) {
10859
10860
  const refused = reportsRefusal(output);
10860
- return { refused, failed: refused || output.trimStart().toLowerCase().startsWith(SCRIPT_FAILED) };
10861
+ const trimmed = output.trimStart();
10862
+ return {
10863
+ refused,
10864
+ failed: refused || trimmed.toLowerCase().startsWith(SCRIPT_FAILED),
10865
+ interrupted: !refused && INTERRUPTED_CELL.test(trimmed)
10866
+ };
10861
10867
  }
10862
10868
  function defaultWatch(_path, onChange) {
10863
10869
  const timer = setInterval(onChange, ROLLOUT_POLL_INTERVAL_MS);
@@ -10986,7 +10992,9 @@ class RefusedCellTail {
10986
10992
  this.pending.delete(callId);
10987
10993
  const output = cellOutputText(payload.output);
10988
10994
  const completedAtMs = recordTimeMs(record, payload);
10989
- const { refused, failed } = codeModeCellOutcome(output);
10995
+ const { refused, failed, interrupted } = codeModeCellOutcome(output);
10996
+ if (interrupted)
10997
+ return;
10990
10998
  const unseen = !Number.isFinite(cell.startedAtMs) || !Number.isFinite(completedAtMs) || !this.options.itemStartedBetween(cell.startedAtMs, completedAtMs);
10991
10999
  if (!refused && !unseen)
10992
11000
  return;
@@ -11003,6 +11011,7 @@ class RefusedCellTail {
11003
11011
  }
11004
11012
 
11005
11013
  // ../../bridges/codex-bridge/src/app-server/item-adapter.ts
11014
+ import { isAbsolute as isAbsolute5 } from "node:path";
11006
11015
  function isRecord5(value) {
11007
11016
  return !!value && typeof value === "object" && !Array.isArray(value);
11008
11017
  }
@@ -11015,6 +11024,20 @@ function durationMs(value) {
11015
11024
  function strArray(value) {
11016
11025
  return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
11017
11026
  }
11027
+ var MAX_IMAGE_GENERATION_INPUTS = 8;
11028
+ function imageGenerationInputPaths(value) {
11029
+ if (!Array.isArray(value))
11030
+ return [];
11031
+ const paths = new Set;
11032
+ for (const entry of value) {
11033
+ if (paths.size >= MAX_IMAGE_GENERATION_INPUTS)
11034
+ break;
11035
+ const path = isRecord5(entry) ? str(entry.path) : undefined;
11036
+ if (path && isAbsolute5(path))
11037
+ paths.add(path);
11038
+ }
11039
+ return [...paths];
11040
+ }
11018
11041
  var MAX_ASYNC_QUESTIONS = 16;
11019
11042
  var MAX_ASYNC_OPTIONS = 32;
11020
11043
  var MAX_ASYNC_TEXT_LENGTH = 16384;
@@ -11276,13 +11299,15 @@ function adaptAppServerItem(raw) {
11276
11299
  }
11277
11300
  case "imageGeneration": {
11278
11301
  const path = str(raw.savedPath);
11302
+ const inputPaths = imageGenerationInputPaths(raw.inputImages);
11279
11303
  return {
11280
11304
  item: {
11281
11305
  id,
11282
11306
  type: "image",
11283
11307
  text: str(raw.revisedPrompt) ?? "Generated image",
11284
11308
  ...path ? { path } : {},
11285
- source: "generated"
11309
+ source: "generated",
11310
+ ...inputPaths.length > 0 ? { input_paths: inputPaths } : {}
11286
11311
  }
11287
11312
  };
11288
11313
  }
@@ -12085,6 +12110,13 @@ async function itemToParts(item, cwd, fileChangeContext) {
12085
12110
  return [];
12086
12111
  case "image":
12087
12112
  return [
12113
+ ...(item.input_paths ?? []).map((path) => ({
12114
+ type: "image",
12115
+ content: path.split(/[\\/]/).pop() || "Image",
12116
+ fileUrl: `file://${path}`,
12117
+ filename: path,
12118
+ imageSource: "viewed"
12119
+ })),
12088
12120
  {
12089
12121
  type: "image",
12090
12122
  content: item.text,
@@ -12381,11 +12413,8 @@ async function walkJsonlFiles(dir) {
12381
12413
  }
12382
12414
  return files;
12383
12415
  }
12384
- async function listTranscriptPaths() {
12385
- const searchRoots = [
12386
- join8(getCodexHomeDir(), "sessions"),
12387
- join8(getCodexHomeDir(), "archived_sessions")
12388
- ];
12416
+ async function listTranscriptPaths(codexHome = getCodexHomeDir()) {
12417
+ const searchRoots = [join8(codexHome, "sessions"), join8(codexHome, "archived_sessions")];
12389
12418
  return (await Promise.all(searchRoots.map((root) => walkJsonlFiles(root)))).flat();
12390
12419
  }
12391
12420
  var cachedTranscriptPaths = new Map;
@@ -13010,6 +13039,18 @@ async function replayPersistedRollout(threadId, snapshot, meta, structuredTurns)
13010
13039
  const settleCodeModeCell = (cell, output) => {
13011
13040
  openCells.delete(cell.callId);
13012
13041
  const outcome = output === undefined ? undefined : codeModeCellOutcome(cellOutputText(output));
13042
+ if (outcome?.interrupted) {
13043
+ const { toolState: _claimed, ...stopped } = cell.message.parts[cell.execPartIndex];
13044
+ cell.message.parts[cell.execPartIndex] = stopped;
13045
+ if (!cell.keepExecRow) {
13046
+ dropPart(cell.message, cell.execPartIndex);
13047
+ for (const call of cell.sourceCalls) {
13048
+ if (!call.replaced)
13049
+ dropPart(cell.message, call.partIndex);
13050
+ }
13051
+ return;
13052
+ }
13053
+ }
13013
13054
  if (cell.sawMcpRecord) {
13014
13055
  for (const call of cell.sourceCalls) {
13015
13056
  if (!call.replaced)
@@ -14098,9 +14139,9 @@ var ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME = "orkestrator-artifacts";
14098
14139
  var ORKESTRATOR_ARTIFACT_MCP_PATH = "/artifact-mcp";
14099
14140
 
14100
14141
  // ../../bridges/codex-bridge/src/engine/app-server-engine.ts
14101
- import { open as open5 } from "node:fs/promises";
14142
+ import { open as open6 } from "node:fs/promises";
14102
14143
  import { createHash as createHash8 } from "node:crypto";
14103
- import { isAbsolute as isAbsolute6, join as join13 } from "node:path";
14144
+ import { isAbsolute as isAbsolute7, join as join13 } from "node:path";
14104
14145
 
14105
14146
  // ../../bridges/codex-bridge/src/app-server/process-supervisor.ts
14106
14147
  import { execFile as execFile3, spawn as spawn3 } from "node:child_process";
@@ -16735,6 +16776,7 @@ var IGNORED_METHODS = new Set([
16735
16776
  "thread/project/updated",
16736
16777
  "thread/queue/changed",
16737
16778
  "thread/prediction/updated",
16779
+ "thread/readState/changed",
16738
16780
  "autoApprovalReview/strictReviewRequired",
16739
16781
  "mcpServer/event/stream/notification",
16740
16782
  "thread/reverted",
@@ -16839,6 +16881,7 @@ function reduceNotification(notification, generation, handle) {
16839
16881
  turns: [
16840
16882
  {
16841
16883
  turnId,
16884
+ at: new Date().toISOString(),
16842
16885
  inputTokens: num(last.inputTokens),
16843
16886
  outputTokens: num(last.outputTokens),
16844
16887
  cacheReadTokens: num(last.cachedInputTokens),
@@ -18030,7 +18073,7 @@ function coordinatorCapabilityPolicy(mode) {
18030
18073
 
18031
18074
  // ../../bridges/codex-bridge/src/codex-config.ts
18032
18075
  import { realpathSync } from "node:fs";
18033
- import { isAbsolute as isAbsolute5 } from "node:path";
18076
+ import { isAbsolute as isAbsolute6 } from "node:path";
18034
18077
  var CODEX_MAX_CONCURRENT_THREADS_ENV = "CODEX_MAX_CONCURRENT_THREADS_PER_SESSION";
18035
18078
  var DEFAULT_CODEX_MAX_CONCURRENT_THREADS = 5;
18036
18079
  var ORKESTRATOR_AGENT_MCP_URL_ENV = "ORKESTRATOR_AGENT_MCP_URL";
@@ -18050,7 +18093,7 @@ function codexNetworkDeniedConfig(policy) {
18050
18093
  };
18051
18094
  }
18052
18095
  function codexProjectTrustConfig(policy, cwd) {
18053
- if (!policy || policy.projectResources || !cwd || !isAbsolute5(cwd))
18096
+ if (!policy || policy.projectResources || !cwd || !isAbsolute6(cwd))
18054
18097
  return {};
18055
18098
  const roots = new Set([cwd]);
18056
18099
  try {
@@ -18073,7 +18116,7 @@ function codexProjectSkillsConfig(listed) {
18073
18116
  const { path, scope, enabled } = skill ?? {};
18074
18117
  if (enabled === true && typeof scope === "string" && KEPT_SKILL_SCOPES.has(scope))
18075
18118
  continue;
18076
- if (typeof path !== "string" || !isAbsolute5(path)) {
18119
+ if (typeof path !== "string" || !isAbsolute6(path)) {
18077
18120
  throw new Error("Codex listed a project skill without a path it can be disabled by");
18078
18121
  }
18079
18122
  disabled.add(path);
@@ -18100,6 +18143,14 @@ function codexReadOnlyTurnPolicy(policy) {
18100
18143
  const denied = base.capabilityPolicy?.deny ?? [];
18101
18144
  return { ...base, capabilityPolicy: { deny: [...denied, "network"] } };
18102
18145
  }
18146
+ function codexToolHistoryGaps(config) {
18147
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
18148
+ return ["config/read returned no configuration"];
18149
+ }
18150
+ const features = config.features;
18151
+ const value = features && typeof features === "object" && !Array.isArray(features) ? features.incremental_tools : undefined;
18152
+ return value === false ? [] : [`features.incremental_tools is ${JSON.stringify(value ?? null)}, not false`];
18153
+ }
18103
18154
  function codexHostedNetworkLockGaps(config, expected = "disabled") {
18104
18155
  if (!config || typeof config !== "object" || Array.isArray(config)) {
18105
18156
  return ["config/read returned no configuration"];
@@ -18113,6 +18164,7 @@ function codexHostedNetworkLockGaps(config, expected = "disabled") {
18113
18164
  if (features && typeof features === "object" && features.standalone_web_search === true) {
18114
18165
  gaps.push("features.standalone_web_search is on");
18115
18166
  }
18167
+ gaps.push(...codexToolHistoryGaps(config));
18116
18168
  return gaps;
18117
18169
  }
18118
18170
  function isCoordinatorReadOnlyEnvironment(env) {
@@ -18139,7 +18191,8 @@ function codexAppServerConfigOverrides(env = process.env) {
18139
18191
  const childLimit = resolveCodexMaxConcurrentThreads(env[CODEX_MAX_CONCURRENT_THREADS_ENV]);
18140
18192
  const overrides = {
18141
18193
  "agents.max_concurrent_threads_per_session": String(childLimit),
18142
- "features.multi_agent_v2.max_concurrent_threads_per_session": String(childLimit + 1)
18194
+ "features.multi_agent_v2.max_concurrent_threads_per_session": String(childLimit + 1),
18195
+ "features.incremental_tools": "false"
18143
18196
  };
18144
18197
  const rawUrl = env[ORKESTRATOR_AGENT_MCP_URL_ENV]?.trim();
18145
18198
  const token = env[ORKESTRATOR_AGENT_MCP_TOKEN_ENV]?.trim();
@@ -18148,11 +18201,11 @@ function codexAppServerConfigOverrides(env = process.env) {
18148
18201
  const readableRuntimeRoot = env.CODEX_BRIDGE_READABLE_RUNTIME_ROOT?.trim();
18149
18202
  const projectRoot = env.CWD?.trim();
18150
18203
  const attachmentRoot = env[BRIDGE_ATTACHMENT_ROOT_ENV]?.trim();
18151
- if (!permissionProfile || !/^[A-Za-z0-9_-]+$/.test(permissionProfile) || !readableRuntimeRoot || !isAbsolute5(readableRuntimeRoot) || !projectRoot || !isAbsolute5(projectRoot)) {
18204
+ if (!permissionProfile || !/^[A-Za-z0-9_-]+$/.test(permissionProfile) || !readableRuntimeRoot || !isAbsolute6(readableRuntimeRoot) || !projectRoot || !isAbsolute6(projectRoot)) {
18152
18205
  throw new Error("Coordinator Codex permission profile configuration is invalid");
18153
18206
  }
18154
18207
  overrides.default_permissions = JSON.stringify(permissionProfile);
18155
- overrides[`permissions.${permissionProfile}.filesystem`] = `{ ":root" = "deny", ":minimal" = "read", ":tmpdir" = "deny", ` + `":slash_tmp" = "deny", ${JSON.stringify(readableRuntimeRoot)} = "read", ` + (attachmentRoot && isAbsolute5(attachmentRoot) ? `${JSON.stringify(attachmentRoot)} = "read", ` : "") + `":workspace_roots" = { "." = "read" } }`;
18208
+ overrides[`permissions.${permissionProfile}.filesystem`] = `{ ":root" = "deny", ":minimal" = "read", ":tmpdir" = "deny", ` + `":slash_tmp" = "deny", ${JSON.stringify(readableRuntimeRoot)} = "read", ` + (attachmentRoot && isAbsolute6(attachmentRoot) ? `${JSON.stringify(attachmentRoot)} = "read", ` : "") + `":workspace_roots" = { "." = "read" } }`;
18156
18209
  overrides[`permissions.${permissionProfile}.network.enabled`] = "false";
18157
18210
  overrides.web_search = JSON.stringify(codexNetworkDeniedSearchMode(codexCoordinatorPolicy(env)));
18158
18211
  overrides["features.standalone_web_search"] = "false";
@@ -18218,6 +18271,106 @@ function codexAppServerConfigOverrides(env = process.env) {
18218
18271
  return overrides;
18219
18272
  }
18220
18273
 
18274
+ // ../../bridges/codex-bridge/src/sessions/tool-history-guard.ts
18275
+ import { open as open5 } from "node:fs/promises";
18276
+ var TOOL_HISTORY_MARKER = '"type":"additional_tools"';
18277
+ var MAX_TOOL_HISTORY_LINEAGE_DEPTH = 32;
18278
+ async function rolloutDeclaresToolHistory(path, end) {
18279
+ const handle = await open5(path, "r");
18280
+ try {
18281
+ const size = (await handle.stat()).size;
18282
+ const limit = end === undefined ? size : Math.min(end, size);
18283
+ const marker = Buffer.from(TOOL_HISTORY_MARKER);
18284
+ const buffer = Buffer.allocUnsafe(ROLLOUT_CHUNK_BYTES + marker.length);
18285
+ let carried = 0;
18286
+ for (let position = 0;position < limit; ) {
18287
+ const length = Math.min(ROLLOUT_CHUNK_BYTES, limit - position);
18288
+ const { bytesRead } = await handle.read(buffer, carried, length, position);
18289
+ if (bytesRead <= 0)
18290
+ break;
18291
+ const filled = carried + bytesRead;
18292
+ if (buffer.subarray(0, filled).includes(marker))
18293
+ return true;
18294
+ position += bytesRead;
18295
+ carried = Math.min(marker.length - 1, filled);
18296
+ buffer.copy(buffer, 0, filled - carried, filled);
18297
+ }
18298
+ return false;
18299
+ } finally {
18300
+ await handle.close();
18301
+ }
18302
+ }
18303
+ async function historyBase(path) {
18304
+ const handle = await open5(path, "r");
18305
+ try {
18306
+ const chunks = [];
18307
+ let filled = 0;
18308
+ let complete = false;
18309
+ while (filled < MAX_ROLLOUT_RECORD_BYTES) {
18310
+ const chunk = Buffer.allocUnsafe(ROLLOUT_CHUNK_BYTES);
18311
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, filled);
18312
+ if (bytesRead <= 0) {
18313
+ complete = true;
18314
+ break;
18315
+ }
18316
+ const newline = chunk.subarray(0, bytesRead).indexOf(10);
18317
+ chunks.push(chunk.subarray(0, newline >= 0 ? newline : bytesRead));
18318
+ filled += bytesRead;
18319
+ if (newline >= 0) {
18320
+ complete = true;
18321
+ break;
18322
+ }
18323
+ }
18324
+ if (!complete)
18325
+ return "unreadable";
18326
+ const line = Buffer.concat(chunks).toString("utf8");
18327
+ if (!line.trim())
18328
+ return null;
18329
+ let record;
18330
+ try {
18331
+ record = JSON.parse(line);
18332
+ } catch {
18333
+ return "unreadable";
18334
+ }
18335
+ if (record.type !== "session_meta" || !record.payload)
18336
+ return null;
18337
+ const base = record.payload.history_base;
18338
+ if (base && typeof base === "object") {
18339
+ if (typeof base.thread_id !== "string")
18340
+ return "unreadable";
18341
+ return {
18342
+ threadId: base.thread_id,
18343
+ ...typeof base.end_byte_offset === "number" ? { endByte: base.end_byte_offset } : {}
18344
+ };
18345
+ }
18346
+ return null;
18347
+ } finally {
18348
+ await handle.close();
18349
+ }
18350
+ }
18351
+ async function threadToolHistoryVerdict(rolloutPath, resolveRollout) {
18352
+ let path = rolloutPath;
18353
+ let end;
18354
+ for (let depth = 0;depth <= MAX_TOOL_HISTORY_LINEAGE_DEPTH; depth += 1) {
18355
+ if (!path)
18356
+ return "unknown";
18357
+ try {
18358
+ if (await rolloutDeclaresToolHistory(path, end))
18359
+ return "declares";
18360
+ const base = await historyBase(path);
18361
+ if (base === "unreadable")
18362
+ return "unknown";
18363
+ if (!base)
18364
+ return "clean";
18365
+ end = base.endByte;
18366
+ path = await resolveRollout(base.threadId);
18367
+ } catch {
18368
+ return "unknown";
18369
+ }
18370
+ }
18371
+ return "unknown";
18372
+ }
18373
+
18221
18374
  // ../../bridges/codex-bridge/src/engine/mcp-tool-availability.ts
18222
18375
  var MCP_TOOL_AVAILABILITY_TIMEOUT_MS = 5000;
18223
18376
  var MCP_TOOL_AVAILABILITY_POLL_MS = 100;
@@ -18638,7 +18791,7 @@ function signedOutAccountRead(error) {
18638
18791
  }
18639
18792
  async function readStoredLoginFingerprint(codexHome) {
18640
18793
  try {
18641
- const file = await open5(join13(codexHome, "auth.json"), "r");
18794
+ const file = await open6(join13(codexHome, "auth.json"), "r");
18642
18795
  let raw;
18643
18796
  try {
18644
18797
  const bytes = Buffer.alloc(65537);
@@ -18687,6 +18840,9 @@ var MAX_UNKNOWN_METHODS = 16;
18687
18840
  var MAX_UNKNOWN_METHOD_LENGTH = 128;
18688
18841
  var MAX_ACCOUNT_PROOF_TURNS = 1024;
18689
18842
  var MAX_ACCOUNT_PROOF_ID_LENGTH = 256;
18843
+ function deniesHostedSearch(config) {
18844
+ return Object.keys(codexNetworkDeniedConfig(config.policy)).length > 0 && codexNetworkDeniedSearchMode(config.policy) === "disabled";
18845
+ }
18690
18846
 
18691
18847
  class AppServerEngine {
18692
18848
  options;
@@ -18713,6 +18869,7 @@ class AppServerEngine {
18713
18869
  accountProofInvalidatedSequence = 0;
18714
18870
  accountProofTurns = new Map;
18715
18871
  hostedNetworkLockVerifiedGeneration = null;
18872
+ toolHistoryLockVerifiedGeneration = null;
18716
18873
  accountReloadFingerprint;
18717
18874
  pendingAccountReloadFingerprint;
18718
18875
  accountCheckSequence = 0;
@@ -18753,6 +18910,7 @@ class AppServerEngine {
18753
18910
  this.clearPendingSignIn();
18754
18911
  this.loginSequence++;
18755
18912
  this.hostedNetworkLockVerifiedGeneration = null;
18913
+ this.toolHistoryLockVerifiedGeneration = null;
18756
18914
  this.rejectAllWaiters();
18757
18915
  this.router.abandonGeneration(generation);
18758
18916
  }
@@ -19190,8 +19348,47 @@ class AppServerEngine {
19190
19348
  this.hostedNetworkLockVerifiedGeneration = generation;
19191
19349
  return generation;
19192
19350
  }
19351
+ async assertToolHistoryLock(generation, config) {
19352
+ if (!deniesHostedSearch(config) || config.permissionProfile)
19353
+ return;
19354
+ if (this.toolHistoryLockVerifiedGeneration === generation)
19355
+ return;
19356
+ const result = await this.supervisor.requestOnGeneration(generation, "config/read", { cwd: config.cwd ?? this.options.cwd });
19357
+ const gaps = codexToolHistoryGaps(result?.config);
19358
+ if (gaps.length > 0) {
19359
+ throw new Error(`Codex did not confirm that tool declarations stay out of thread history (${gaps.join("; ")}), so this session, which has web search off, was refused`);
19360
+ }
19361
+ if (!this.supervisor.isReady() || this.supervisor.getGeneration() !== generation) {
19362
+ throw new Error("Codex exited during tool history verification");
19363
+ }
19364
+ this.toolHistoryLockVerifiedGeneration = generation;
19365
+ }
19366
+ async assertNoToolHistory(threadId, config, release) {
19367
+ if (!deniesHostedSearch(config))
19368
+ return;
19369
+ const verdict = await (this.options.toolHistoryVerdict ?? ((id) => this.readToolHistory(id)))(threadId).catch(() => "unknown");
19370
+ if (verdict === "clean")
19371
+ return;
19372
+ if (release) {
19373
+ for (const [handle, binding] of this.bindings) {
19374
+ if (binding.threadId === threadId)
19375
+ this.bindings.delete(handle);
19376
+ }
19377
+ await this.supervisor.request("thread/unsubscribe", { threadId }).catch(() => {
19378
+ return;
19379
+ });
19380
+ }
19381
+ throw new Error(verdict === "declares" ? "This Codex thread was offered web search in an earlier turn and still declares it in its history, so a turn with web search off was refused. Start a new session for it." : "Could not confirm that this Codex thread's history declares no web search, so a turn with web search off was refused.");
19382
+ }
19383
+ async readToolHistory(threadId) {
19384
+ const codexHome = this.options.codexHome;
19385
+ const resolve = async (id) => this.rolloutPaths.get(id) ?? findTranscriptPath(id, () => listTranscriptPaths(codexHome), { allowNegativeCache: false });
19386
+ const path = await resolve(threadId);
19387
+ return path ? threadToolHistoryVerdict(path, resolve) : "unknown";
19388
+ }
19193
19389
  async startThread(options) {
19194
19390
  const generation = await this.assertHostedNetworkLock(options.config);
19391
+ await this.assertToolHistoryLock(generation, options.config);
19195
19392
  const extra = await this.projectResourceThreadConfig(generation, options.config);
19196
19393
  const response = await this.supervisor.requestOnGeneration(generation, "thread/start", this.toThreadParams(options.config, extra));
19197
19394
  this.assertPermissionProfile(response.activePermissionProfile, options.config);
@@ -19199,10 +19396,12 @@ class AppServerEngine {
19199
19396
  }
19200
19397
  async resumeThread(threadId, options) {
19201
19398
  const generation = await this.assertHostedNetworkLock(options.config);
19399
+ await this.assertToolHistoryLock(generation, options.config);
19202
19400
  const extra = await this.projectResourceThreadConfig(generation, options.config);
19203
19401
  const response = await this.supervisor.requestOnGeneration(generation, "thread/resume", { threadId, ...this.toThreadParams(options.config, extra) });
19204
19402
  this.assertPermissionProfile(response.activePermissionProfile, options.config);
19205
19403
  const thread = this.bindThread(response.thread, options.config, response.model);
19404
+ await this.assertNoToolHistory(thread.id ?? threadId, options.config, true);
19206
19405
  thread.turns = this.extractTurns(response.thread);
19207
19406
  return thread;
19208
19407
  }
@@ -19218,6 +19417,8 @@ class AppServerEngine {
19218
19417
  }
19219
19418
  async forkThread(threadId, config, lastTurnId) {
19220
19419
  const generation = await this.assertHostedNetworkLock(config);
19420
+ await this.assertToolHistoryLock(generation, config);
19421
+ await this.assertNoToolHistory(threadId, config, false);
19221
19422
  const extra = await this.projectResourceThreadConfig(generation, config);
19222
19423
  const response = await this.supervisor.requestOnGeneration(generation, "thread/fork", {
19223
19424
  threadId,
@@ -19495,7 +19696,7 @@ class AppServerEngine {
19495
19696
  thread.model = confirmedModel.trim();
19496
19697
  }
19497
19698
  if (thread.id) {
19498
- if (typeof raw.path === "string" && isAbsolute6(raw.path)) {
19699
+ if (typeof raw.path === "string" && isAbsolute7(raw.path)) {
19499
19700
  this.rolloutPaths.set(thread.id, raw.path);
19500
19701
  }
19501
19702
  this.bindings.set(thread.id, {
@@ -21453,7 +21654,7 @@ function parseCommandToken(text, sigils = ["/"]) {
21453
21654
 
21454
21655
  // ../../bridges/codex-bridge/src/prompts/template-discovery.ts
21455
21656
  import { createHash as createHash10 } from "node:crypto";
21456
- import { open as open6, readdir as readdir3, stat as stat7 } from "node:fs/promises";
21657
+ import { open as open7, readdir as readdir3, stat as stat7 } from "node:fs/promises";
21457
21658
  import { join as join14 } from "node:path";
21458
21659
 
21459
21660
  // ../../bridges/codex-bridge/src/prompts/template-format.ts
@@ -21608,7 +21809,7 @@ function isReservedName(name) {
21608
21809
  var metadataCache = new Map;
21609
21810
  var METADATA_CACHE_MAX = 1024;
21610
21811
  async function readBounded(absolutePath, maxBytes) {
21611
- const handle = await open6(absolutePath, "r");
21812
+ const handle = await open7(absolutePath, "r");
21612
21813
  try {
21613
21814
  const info = await handle.stat();
21614
21815
  if (!info.isFile())
@@ -21855,7 +22056,7 @@ async function loadTemplateBody(entry, limits = TEMPLATE_LIMITS) {
21855
22056
  }
21856
22057
 
21857
22058
  // ../../bridges/codex-bridge/src/commands/skill-inventory.ts
21858
- import { isAbsolute as isAbsolute7 } from "node:path";
22059
+ import { isAbsolute as isAbsolute8 } from "node:path";
21859
22060
  var SKILL_INVENTORY_LIMITS = Object.freeze({
21860
22061
  maxSkills: 512,
21861
22062
  maxDescriptionBytes: 1000,
@@ -21905,7 +22106,7 @@ function normalizeSkillsList(raw, context) {
21905
22106
  }
21906
22107
  const name = nonEmpty(candidate.name);
21907
22108
  const path = typeof candidate.path === "string" ? candidate.path : undefined;
21908
- if (!name || !usableSkillName(name) || !path || !isAbsolute7(path)) {
22109
+ if (!name || !usableSkillName(name) || !path || !isAbsolute8(path)) {
21909
22110
  truncated = true;
21910
22111
  continue;
21911
22112
  }