remote-codex 0.11.49 → 0.11.51

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.
Files changed (38) hide show
  1. package/README.md +38 -5
  2. package/apps/supervisor-api/dist/index.js +2137 -422
  3. package/apps/supervisor-web/dist/assets/index-7LiZQ3aJ.js +22 -0
  4. package/apps/supervisor-web/dist/assets/{index-Dy8PgXgw.css → index-Bg8FdhS1.css} +1 -1
  5. package/apps/supervisor-web/dist/assets/{thread-ui-gcslNXur.js → thread-ui-BbtcUwps.js} +25 -19
  6. package/apps/supervisor-web/dist/index.html +3 -3
  7. package/bin/remote-codex.mjs +4 -2
  8. package/package.json +7 -1
  9. package/packages/acp/src/agent-catalog.test.ts +25 -0
  10. package/packages/acp/src/agent-catalog.ts +3 -1
  11. package/packages/acp/src/capabilities.ts +139 -0
  12. package/packages/acp/src/capability-parity.test.ts +99 -0
  13. package/packages/acp/src/catalog-runtime.test.ts +200 -6
  14. package/packages/acp/src/catalog-runtime.ts +239 -31
  15. package/packages/acp/src/extension-registry.test.ts +198 -0
  16. package/packages/acp/src/extension-registry.ts +285 -0
  17. package/packages/acp/src/extensions.test.ts +45 -0
  18. package/packages/acp/src/extensions.ts +103 -0
  19. package/packages/acp/src/harness-contract.test.ts +81 -0
  20. package/packages/acp/src/harness-contract.ts +62 -0
  21. package/packages/acp/src/index.ts +7 -0
  22. package/packages/acp/src/item-mapper.ts +28 -2
  23. package/packages/acp/src/prompt-content.test.ts +90 -0
  24. package/packages/acp/src/prompt-content.ts +99 -0
  25. package/packages/acp/src/runtimeAdapter.test.ts +458 -5
  26. package/packages/acp/src/runtimeAdapter.ts +791 -60
  27. package/packages/acp/src/session-hydrator.test.ts +135 -0
  28. package/packages/acp/src/session-hydrator.ts +147 -0
  29. package/packages/acp/src/terminal-service.test.ts +21 -2
  30. package/packages/acp/src/terminal-service.ts +23 -3
  31. package/packages/acp/src/test/fixtures/fake-acp-agent.mjs +514 -0
  32. package/packages/acp/src/workspace-boundary.test.ts +32 -0
  33. package/packages/acp/src/workspace-boundary.ts +47 -0
  34. package/packages/agent-runtime/src/types.ts +61 -1
  35. package/packages/db/src/repositories.ts +3 -2
  36. package/packages/shared/src/index.ts +32 -1
  37. package/scripts/service-manager.mjs +13 -0
  38. package/apps/supervisor-web/dist/assets/index-DZI1aSXo.js +0 -22
@@ -5,15 +5,15 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import fs31 from "fs";
8
+ import fs33 from "fs";
9
9
 
10
10
  // src/app.ts
11
11
  import Fastify from "fastify";
12
12
  import multipart from "@fastify/multipart";
13
13
  import websocket from "@fastify/websocket";
14
14
  import { spawn } from "child_process";
15
- import fs29 from "fs";
16
- import path31 from "path";
15
+ import fs31 from "fs";
16
+ import path33 from "path";
17
17
  import { ZodError } from "zod";
18
18
 
19
19
  // ../../packages/config/src/index.ts
@@ -176,7 +176,7 @@ function loadRuntimeConfig(env = process.env, platform = process.platform) {
176
176
  nodeEnv === "production"
177
177
  );
178
178
  const enabledProviders = new Set(
179
- (parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? (platform === "win32" ? "codex" : "codex,claude,opencode")).split(",").map((provider2) => provider2.trim().toLowerCase()).filter(Boolean)
179
+ (parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? (platform === "win32" ? "codex,acp" : "codex,claude,opencode,acp")).split(",").map((provider2) => provider2.trim().toLowerCase()).filter(Boolean)
180
180
  );
181
181
  const defaultAgentHomeRoot = os.homedir();
182
182
  const codexHome = parsed.CODEX_HOME?.trim() ? path.resolve(parsed.CODEX_HOME) : path.join(defaultAgentHomeRoot, agentBackendMetadata.codex.defaultHomeDir);
@@ -2336,7 +2336,7 @@ Subquery.prototype.getSQL = function() {
2336
2336
  function mapResultRow(columns, row, joinsNotNullableMap) {
2337
2337
  const nullifyMap = {};
2338
2338
  const result = columns.reduce(
2339
- (result2, { path: path33, field }, columnIndex) => {
2339
+ (result2, { path: path35, field }, columnIndex) => {
2340
2340
  let decoder;
2341
2341
  if (is(field, Column)) {
2342
2342
  decoder = field;
@@ -2346,8 +2346,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2346
2346
  decoder = field.sql.decoder;
2347
2347
  }
2348
2348
  let node = result2;
2349
- for (const [pathChunkIndex, pathChunk] of path33.entries()) {
2350
- if (pathChunkIndex < path33.length - 1) {
2349
+ for (const [pathChunkIndex, pathChunk] of path35.entries()) {
2350
+ if (pathChunkIndex < path35.length - 1) {
2351
2351
  if (!(pathChunk in node)) {
2352
2352
  node[pathChunk] = {};
2353
2353
  }
@@ -2355,8 +2355,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2355
2355
  } else {
2356
2356
  const rawValue = row[columnIndex];
2357
2357
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
2358
- if (joinsNotNullableMap && is(field, Column) && path33.length === 2) {
2359
- const objectName = path33[0];
2358
+ if (joinsNotNullableMap && is(field, Column) && path35.length === 2) {
2359
+ const objectName = path35[0];
2360
2360
  if (!(objectName in nullifyMap)) {
2361
2361
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
2362
2362
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -6846,7 +6846,7 @@ function upsertThreadTurnMetadata(db, input) {
6846
6846
  pricingTierKey: input.pricingTierKey ?? null,
6847
6847
  tokenUsageJson: input.tokenUsageJson ?? null,
6848
6848
  displayPrompt: input.displayPrompt ?? null,
6849
- createdAt: now,
6849
+ createdAt: input.createdAt ?? now,
6850
6850
  updatedAt: now
6851
6851
  }).run();
6852
6852
  }
@@ -9139,7 +9139,7 @@ function extractFileChangeEntries(item) {
9139
9139
  isRecord4(entry.summary) ? entry.summary : null,
9140
9140
  isRecord4(entry.diff) ? entry.diff : null
9141
9141
  ].filter((candidate) => Boolean(candidate));
9142
- const path33 = uniqueStrings([
9142
+ const path35 = uniqueStrings([
9143
9143
  stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
9144
9144
  stringOrNull(
9145
9145
  valueFromRecords(nestedRecords, [
@@ -9186,7 +9186,7 @@ function extractFileChangeEntries(item) {
9186
9186
  const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
9187
9187
  const additions = explicitAdditions || diffStats?.additions || 0;
9188
9188
  const deletions = explicitDeletions || diffStats?.deletions || 0;
9189
- const normalizedPath = path33 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9189
+ const normalizedPath = path35 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9190
9190
  if (!normalizedPath && additions === 0 && deletions === 0) {
9191
9191
  return null;
9192
9192
  }
@@ -14187,14 +14187,14 @@ function displayPath(pathValue, options) {
14187
14187
  }
14188
14188
  return relativePath.split(path12.sep).join("/");
14189
14189
  }
14190
- function toolIsLowInformationPatch(normalized, state, input, patchText, path33, metadataStats) {
14190
+ function toolIsLowInformationPatch(normalized, state, input, patchText, path35, metadataStats) {
14191
14191
  if (normalized !== "applypatch" && normalized !== "patch") {
14192
14192
  return false;
14193
14193
  }
14194
14194
  if (toolStateStatus(state) !== "running") {
14195
14195
  return false;
14196
14196
  }
14197
- if (path33 || patchText || metadataStats || stringValue2(state.output)) {
14197
+ if (path35 || patchText || metadataStats || stringValue2(state.output)) {
14198
14198
  return false;
14199
14199
  }
14200
14200
  return !isRecord9(input) || Object.keys(input).length === 0;
@@ -14237,7 +14237,7 @@ function fileChangeStatsFromMetadata(metadata) {
14237
14237
  if (files.length === 0) {
14238
14238
  return null;
14239
14239
  }
14240
- const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path33) => Boolean(path33));
14240
+ const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path35) => Boolean(path35));
14241
14241
  const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
14242
14242
  const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
14243
14243
  return {
@@ -14356,8 +14356,8 @@ function normalizeLegacyMessage(message) {
14356
14356
  finish: stringValue2(info.finish) ?? void 0
14357
14357
  };
14358
14358
  }
14359
- function mapAssistantTool(messageId2, tool, options) {
14360
- const id = stringValue2(tool.id) ?? `${messageId2}:tool`;
14359
+ function mapAssistantTool(messageId3, tool, options) {
14360
+ const id = stringValue2(tool.id) ?? `${messageId3}:tool`;
14361
14361
  const name = stringValue2(tool.name) ?? stringValue2(tool.tool) ?? "Tool";
14362
14362
  const state = isRecord9(tool.state) ? tool.state : {};
14363
14363
  const input = state.input;
@@ -14385,28 +14385,28 @@ function mapAssistantTool(messageId2, tool, options) {
14385
14385
  ].includes(normalized)) {
14386
14386
  const metadataStats = fileChangeStatsFromMetadata(state.metadata);
14387
14387
  const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
14388
- const path33 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
14389
- if (toolIsLowInformationPatch(normalized, state, input, patchText, path33, metadataStats)) {
14388
+ const path35 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
14389
+ if (toolIsLowInformationPatch(normalized, state, input, patchText, path35, metadataStats)) {
14390
14390
  return null;
14391
14391
  }
14392
14392
  const output = stringValue2(state.output);
14393
14393
  const diffStats = countUnifiedDiffStats2(patchText);
14394
- const displayFilePath = displayPath(path33, options);
14394
+ const displayFilePath = displayPath(path35, options);
14395
14395
  return {
14396
14396
  id,
14397
14397
  kind: "fileChange",
14398
14398
  text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
14399
14399
  previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
14400
14400
  detailText,
14401
- changedFiles: metadataStats?.changedFiles ?? (path33 ? 1 : null),
14401
+ changedFiles: metadataStats?.changedFiles ?? (path35 ? 1 : null),
14402
14402
  addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
14403
14403
  removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
14404
14404
  status: toolStateStatus(state)
14405
14405
  };
14406
14406
  }
14407
14407
  if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
14408
- const path33 = filePathFromInput(input);
14409
- const text2 = displayPath(path33, options) ?? summary ?? name;
14408
+ const path35 = filePathFromInput(input);
14409
+ const text2 = displayPath(path35, options) ?? summary ?? name;
14410
14410
  return {
14411
14411
  id,
14412
14412
  kind: "fileRead",
@@ -14485,13 +14485,13 @@ function openCodeMessagesToPlanUpdate(messages) {
14485
14485
  }
14486
14486
  return null;
14487
14487
  }
14488
- function mapAssistantPart(messageId2, part, index, options) {
14489
- const partId = stringValue2(part.id) ?? `${messageId2}:${stringValue2(part.type) ?? "part"}:${index}`;
14488
+ function mapAssistantPart(messageId3, part, index, options) {
14489
+ const partId = stringValue2(part.id) ?? `${messageId3}:${stringValue2(part.type) ?? "part"}:${index}`;
14490
14490
  const partType = stringValue2(part.type);
14491
14491
  if (partType === "text") {
14492
14492
  const text2 = stringValue2(part.text);
14493
14493
  return text2 ? {
14494
- id: `${messageId2}:text:${index}`,
14494
+ id: `${messageId3}:text:${index}`,
14495
14495
  kind: "agentMessage",
14496
14496
  text: text2
14497
14497
  } : null;
@@ -14505,7 +14505,7 @@ function mapAssistantPart(messageId2, part, index, options) {
14505
14505
  } : null;
14506
14506
  }
14507
14507
  if (partType === "tool") {
14508
- return mapAssistantTool(messageId2, part, options);
14508
+ return mapAssistantTool(messageId3, part, options);
14509
14509
  }
14510
14510
  if (partType === "file") {
14511
14511
  const sourcePath = isRecord9(part.source) ? stringValue2(part.source.path) : null;
@@ -15935,7 +15935,8 @@ var builtinAcpAgents = [
15935
15935
  baseProbeCommand: "opencode --version",
15936
15936
  serverCommand: "opencode acp",
15937
15937
  serverProbeCommand: "opencode acp --help",
15938
- installCommand: null
15938
+ installCommand: null,
15939
+ modelListCommand: "opencode models"
15939
15940
  },
15940
15941
  {
15941
15942
  id: "deepseek",
@@ -16079,7 +16080,7 @@ var AcpAgentCatalog = class {
16079
16080
  ${result.stderr}`;
16080
16081
  const defaultModel = output.match(/^Default model:\s*(\S+)/m)?.[1] ?? null;
16081
16082
  const models = output.split(/\r?\n/).flatMap((line) => {
16082
- const match = line.match(/^\s*[-*]\s+(\S+?)(?:\s+\(default\))?\s*$/);
16083
+ const match = line.match(/^\s*[-*]\s+(\S+?)(?:\s+\(default\))?\s*$/) ?? line.match(/^\s*([A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+)\s*$/);
16083
16084
  if (!match?.[1]) {
16084
16085
  return [];
16085
16086
  }
@@ -16162,13 +16163,125 @@ function acpAgentMetadata(entry) {
16162
16163
  };
16163
16164
  }
16164
16165
 
16166
+ // ../../packages/acp/src/extensions.ts
16167
+ var REMOTE_CODEX_HARNESS_EXTENSION_META_KEY = "remoteCodex.harnessExtensions";
16168
+ var REMOTE_CODEX_HARNESS_EXTENSION_PROTOCOL = "remote-codex.harness-extension/v1";
16169
+ var REMOTE_CODEX_HARNESS_EXTENSION_EVENT_METHOD = "remoteCodex/harness-extension/event";
16170
+ var extensionSegmentPattern = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
16171
+ function assertExtensionSegment(value, label) {
16172
+ if (!extensionSegmentPattern.test(value)) {
16173
+ throw new Error(`${label} must be a lowercase extension identifier.`);
16174
+ }
16175
+ }
16176
+ function harnessExtensionMethodName(extensionId, version2, method) {
16177
+ assertExtensionSegment(extensionId, "Extension id");
16178
+ assertExtensionSegment(method, "Extension method");
16179
+ if (!Number.isInteger(version2) || version2 < 1) {
16180
+ throw new Error("Extension version must be a positive integer.");
16181
+ }
16182
+ return `remoteCodex/${extensionId}/v${version2}/${method}`;
16183
+ }
16184
+ function createHarnessExtensionCall(input) {
16185
+ harnessExtensionMethodName(
16186
+ input.extensionId,
16187
+ input.extensionVersion,
16188
+ input.method
16189
+ );
16190
+ if (!input.operationId.trim()) {
16191
+ throw new Error("Extension operation id is required.");
16192
+ }
16193
+ if (!input.idempotencyKey.trim()) {
16194
+ throw new Error("Extension idempotency key is required.");
16195
+ }
16196
+ return {
16197
+ protocol: REMOTE_CODEX_HARNESS_EXTENSION_PROTOCOL,
16198
+ extensionId: input.extensionId,
16199
+ extensionVersion: input.extensionVersion,
16200
+ method: input.method,
16201
+ operationId: input.operationId,
16202
+ idempotencyKey: input.idempotencyKey,
16203
+ params: input.params
16204
+ };
16205
+ }
16206
+
16207
+ // ../../packages/acp/src/capabilities.ts
16208
+ function isRecord11(value) {
16209
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16210
+ }
16211
+ function stringValue4(value) {
16212
+ return typeof value === "string" && value.trim() ? value.trim() : null;
16213
+ }
16214
+ function harnessExtensions(value) {
16215
+ if (!Array.isArray(value)) {
16216
+ return [];
16217
+ }
16218
+ return value.flatMap((candidate) => {
16219
+ if (!isRecord11(candidate)) {
16220
+ return [];
16221
+ }
16222
+ const id = stringValue4(candidate.id);
16223
+ const version2 = candidate.version;
16224
+ const stability = candidate.stability;
16225
+ const methods2 = Array.isArray(candidate.methods) ? candidate.methods.filter((item) => typeof item === "string") : [];
16226
+ const events = Array.isArray(candidate.events) ? candidate.events.filter((item) => typeof item === "string") : [];
16227
+ if (!id || !Number.isInteger(version2) || Number(version2) < 1 || stability !== "experimental" && stability !== "stable") {
16228
+ return [];
16229
+ }
16230
+ return [{
16231
+ id,
16232
+ version: Number(version2),
16233
+ stability,
16234
+ methods: methods2,
16235
+ events
16236
+ }];
16237
+ });
16238
+ }
16239
+ function legacyExtensions(meta) {
16240
+ const steering = isRecord11(meta.steering) ? { supported: meta.steering.supported === true } : null;
16241
+ const goal = isRecord11(meta.goal) ? {
16242
+ version: typeof meta.goal.version === "string" || typeof meta.goal.version === "number" ? meta.goal.version : null,
16243
+ controlMethod: stringValue4(meta.goal.controlMethod),
16244
+ actions: Array.isArray(meta.goal.actions) ? meta.goal.actions.filter((item) => typeof item === "string") : []
16245
+ } : null;
16246
+ return { steering, goal };
16247
+ }
16248
+ function snapshotAcpInitializeResponse(response) {
16249
+ if (!response) {
16250
+ return null;
16251
+ }
16252
+ const meta = isRecord11(response._meta) ? response._meta : {};
16253
+ return {
16254
+ protocolVersion: response.protocolVersion,
16255
+ agentInfo: response.agentInfo ? {
16256
+ name: response.agentInfo.name,
16257
+ title: response.agentInfo.title ?? null,
16258
+ version: response.agentInfo.version ?? null
16259
+ } : null,
16260
+ agentCapabilities: structuredClone(response.agentCapabilities ?? {}),
16261
+ harnessExtensions: harnessExtensions(
16262
+ meta[REMOTE_CODEX_HARNESS_EXTENSION_META_KEY]
16263
+ ),
16264
+ legacyExtensions: legacyExtensions(meta)
16265
+ };
16266
+ }
16267
+ function snapshotAcpAgentCapabilities(input) {
16268
+ const ready = input.availability === "ready";
16269
+ return {
16270
+ provider: "acp",
16271
+ agentId: input.agentId,
16272
+ availability: input.availability,
16273
+ negotiated: ready ? input.negotiated ?? null : null,
16274
+ effectiveCapabilities: ready ? structuredClone(input.effectiveCapabilities) : null
16275
+ };
16276
+ }
16277
+
16165
16278
  // ../../packages/acp/src/catalog-runtime.ts
16166
- import { EventEmitter as EventEmitter8 } from "events";
16279
+ import { EventEmitter as EventEmitter9 } from "events";
16167
16280
 
16168
16281
  // ../../packages/acp/src/runtimeAdapter.ts
16169
- import { EventEmitter as EventEmitter7 } from "events";
16170
- import fs11 from "fs/promises";
16171
- import path14 from "path";
16282
+ import { EventEmitter as EventEmitter8 } from "events";
16283
+ import fs13 from "fs/promises";
16284
+ import path16 from "path";
16172
16285
  import { randomUUID as randomUUID4 } from "crypto";
16173
16286
  import { Readable, Writable } from "stream";
16174
16287
 
@@ -17919,17 +18032,17 @@ function isResponseMessage(value) {
17919
18032
  function isNotificationMessage(value) {
17920
18033
  return isJsonRpcEnvelope(value) && !("id" in value) && typeof value["method"] === "string";
17921
18034
  }
17922
- function isRecord11(value) {
18035
+ function isRecord12(value) {
17923
18036
  return typeof value === "object" && value !== null;
17924
18037
  }
17925
18038
  function isJsonRpcEnvelope(value) {
17926
- return isRecord11(value) && value["jsonrpc"] === "2.0";
18039
+ return isRecord12(value) && value["jsonrpc"] === "2.0";
17927
18040
  }
17928
18041
  function isJsonRpcId(value) {
17929
18042
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
17930
18043
  }
17931
18044
  function isResponseShapedMessage(value) {
17932
- return isRecord11(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
18045
+ return isRecord12(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
17933
18046
  }
17934
18047
  function isResponseBatch(batch) {
17935
18048
  let hasValidCall = false;
@@ -17939,7 +18052,7 @@ function isResponseBatch(batch) {
17939
18052
  for (const entry of batch) {
17940
18053
  hasValidCall ||= isRequestMessage(entry) || isNotificationMessage(entry);
17941
18054
  hasValidResponse ||= isResponseMessage(entry);
17942
- if (!isRecord11(entry)) {
18055
+ if (!isRecord12(entry)) {
17943
18056
  continue;
17944
18057
  }
17945
18058
  hasCallShape ||= "method" in entry;
@@ -17954,13 +18067,13 @@ function isResponseBatch(batch) {
17954
18067
  return hasResponseShape && !hasCallShape;
17955
18068
  }
17956
18069
  function cancelRequestId(params) {
17957
- if (!isRecord11(params) || !isJsonRpcId(params["requestId"])) {
18070
+ if (!isRecord12(params) || !isJsonRpcId(params["requestId"])) {
17958
18071
  return void 0;
17959
18072
  }
17960
18073
  return params["requestId"];
17961
18074
  }
17962
18075
  function isErrorResponse(value) {
17963
- return isRecord11(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
18076
+ return isRecord12(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
17964
18077
  }
17965
18078
  var Handled = {
17966
18079
  /**
@@ -18505,7 +18618,7 @@ var Connection = class {
18505
18618
  if (this.abortController.signal.aborted) {
18506
18619
  return Promise.resolve();
18507
18620
  }
18508
- if (!isRecord11(message)) {
18621
+ if (!isRecord12(message)) {
18509
18622
  console.error("Invalid message", { message });
18510
18623
  return Promise.resolve();
18511
18624
  }
@@ -18908,7 +19021,7 @@ function ndJsonStream(output, input) {
18908
19021
  await writeJson(protocolErrorResponse(RequestError.parseError()));
18909
19022
  return;
18910
19023
  }
18911
- if (isRecord11(message) || Array.isArray(message)) {
19024
+ if (isRecord12(message) || Array.isArray(message)) {
18912
19025
  controller.enqueue(message);
18913
19026
  } else {
18914
19027
  await writeJson(protocolErrorResponse(RequestError.invalidRequest(message)));
@@ -20063,13 +20176,15 @@ function mappedPlan(entries) {
20063
20176
  };
20064
20177
  }
20065
20178
  var AcpTurnItemMapper = class {
20066
- constructor(turnId, initialItems = []) {
20179
+ constructor(turnId, initialItems = [], mode = "live") {
20067
20180
  this.turnId = turnId;
20181
+ this.mode = mode;
20068
20182
  for (const item of initialItems) {
20069
20183
  this.upsert(item);
20070
20184
  }
20071
20185
  }
20072
20186
  turnId;
20187
+ mode;
20073
20188
  items = /* @__PURE__ */ new Map();
20074
20189
  order = [];
20075
20190
  tools = /* @__PURE__ */ new Map();
@@ -20077,6 +20192,16 @@ var AcpTurnItemMapper = class {
20077
20192
  thoughtIndex = 0;
20078
20193
  currentAgentMessageId = null;
20079
20194
  currentThoughtId = null;
20195
+ appendUserMessage(content, itemId) {
20196
+ const current = this.items.get(itemId);
20197
+ const item = {
20198
+ ...current ?? { id: itemId, kind: "userMessage" },
20199
+ text: `${current?.text ?? ""}${acpContentBlockText(content)}`,
20200
+ status: modeStatus(this.mode)
20201
+ };
20202
+ this.upsert(item);
20203
+ return item;
20204
+ }
20080
20205
  turn(status = "inProgress", error = null) {
20081
20206
  return {
20082
20207
  providerTurnId: this.turnId,
@@ -20099,7 +20224,7 @@ var AcpTurnItemMapper = class {
20099
20224
  case "agent_message_chunk": {
20100
20225
  result.itemUpdates.push(...this.finishThought());
20101
20226
  const delta = acpContentBlockText(update.content);
20102
- const itemId = this.currentAgentMessageId ?? `${this.turnId}:agent:${++this.agentMessageIndex}`;
20227
+ const itemId = this.currentAgentMessageId ?? messageId2(update) ?? `${this.turnId}:agent:${++this.agentMessageIndex}`;
20103
20228
  this.currentAgentMessageId = itemId;
20104
20229
  const current = this.items.get(itemId);
20105
20230
  const item = {
@@ -20114,7 +20239,7 @@ var AcpTurnItemMapper = class {
20114
20239
  case "agent_thought_chunk": {
20115
20240
  result.itemUpdates.push(...this.finishAgentMessage());
20116
20241
  const delta = acpContentBlockText(update.content);
20117
- const itemId = this.currentThoughtId ?? `${this.turnId}:thought:${++this.thoughtIndex}`;
20242
+ const itemId = this.currentThoughtId ?? messageId2(update) ?? `${this.turnId}:thought:${++this.thoughtIndex}`;
20118
20243
  this.currentThoughtId = itemId;
20119
20244
  const current = this.items.get(itemId);
20120
20245
  const item = {
@@ -20298,6 +20423,449 @@ var AcpTurnItemMapper = class {
20298
20423
  return [...this.finishAgentMessage(), ...this.finishThought()];
20299
20424
  }
20300
20425
  };
20426
+ function messageId2(update) {
20427
+ return typeof update.messageId === "string" && update.messageId.trim() ? update.messageId.trim() : null;
20428
+ }
20429
+ function modeStatus(mode) {
20430
+ return mode === "hydrate" ? "completed" : "running";
20431
+ }
20432
+
20433
+ // ../../packages/acp/src/prompt-content.ts
20434
+ import fs12 from "fs/promises";
20435
+ import path15 from "path";
20436
+ import { pathToFileURL as pathToFileURL3 } from "url";
20437
+
20438
+ // ../../packages/acp/src/workspace-boundary.ts
20439
+ import fs11 from "fs/promises";
20440
+ import path14 from "path";
20441
+ function isInside(root, candidate) {
20442
+ const relative = path14.relative(root, candidate);
20443
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path14.sep}`) && !path14.isAbsolute(relative);
20444
+ }
20445
+ async function resolveAcpWorkspacePath(workspacePath, candidatePath) {
20446
+ if (!path14.isAbsolute(candidatePath)) {
20447
+ throw new Error("ACP workspace paths must be absolute.");
20448
+ }
20449
+ const rootPath = path14.resolve(workspacePath);
20450
+ const requestedPath = path14.resolve(candidatePath);
20451
+ if (!isInside(rootPath, requestedPath)) {
20452
+ throw new Error("ACP path must stay inside the session workspace.");
20453
+ }
20454
+ const rootRealPath = await fs11.realpath(rootPath);
20455
+ let existingPath = requestedPath;
20456
+ const missingSegments = [];
20457
+ while (true) {
20458
+ try {
20459
+ const existingRealPath = await fs11.realpath(existingPath);
20460
+ if (!isInside(rootRealPath, existingRealPath)) {
20461
+ throw new Error("ACP path resolves outside the session workspace.");
20462
+ }
20463
+ return path14.join(existingRealPath, ...missingSegments);
20464
+ } catch (error) {
20465
+ if (error.code !== "ENOENT") {
20466
+ throw error;
20467
+ }
20468
+ const parent = path14.dirname(existingPath);
20469
+ if (parent === existingPath) {
20470
+ throw new Error("ACP workspace path has no existing parent.");
20471
+ }
20472
+ missingSegments.unshift(path14.basename(existingPath));
20473
+ existingPath = parent;
20474
+ }
20475
+ }
20476
+ }
20477
+
20478
+ // ../../packages/acp/src/prompt-content.ts
20479
+ var attachmentTokenPattern = /\[(PHOTO|FILE)\s+([^\]]+)\]/g;
20480
+ var maxEmbeddedImageBytes = 20 * 1024 * 1024;
20481
+ function imageMimeType(filePath) {
20482
+ switch (path15.extname(filePath).toLowerCase()) {
20483
+ case ".jpg":
20484
+ case ".jpeg":
20485
+ return "image/jpeg";
20486
+ case ".gif":
20487
+ return "image/gif";
20488
+ case ".webp":
20489
+ return "image/webp";
20490
+ case ".png":
20491
+ default:
20492
+ return "image/png";
20493
+ }
20494
+ }
20495
+ async function buildAcpPromptContent(input) {
20496
+ if (input.content) {
20497
+ return input.content.map((block) => {
20498
+ if (block.type === "image" && input.promptCapabilities?.image !== true) {
20499
+ throw new Error("The selected ACP agent does not support image prompts.");
20500
+ }
20501
+ if (block.type === "audio" && input.promptCapabilities?.audio !== true) {
20502
+ throw new Error("The selected ACP agent does not support audio prompts.");
20503
+ }
20504
+ if (block.type === "resource" && input.promptCapabilities?.embeddedContext !== true) {
20505
+ throw new Error("The selected ACP agent does not support embedded context.");
20506
+ }
20507
+ return structuredClone(block);
20508
+ });
20509
+ }
20510
+ const matches = [...input.prompt.matchAll(attachmentTokenPattern)];
20511
+ if (matches.length === 0) {
20512
+ return [{ type: "text", text: input.prompt }];
20513
+ }
20514
+ const blocks = [];
20515
+ let cursor = 0;
20516
+ for (const match of matches) {
20517
+ const start = match.index ?? 0;
20518
+ const preceding = input.prompt.slice(cursor, start);
20519
+ if (preceding) {
20520
+ blocks.push({ type: "text", text: preceding });
20521
+ }
20522
+ const kind = match[1];
20523
+ const requestedPath = match[2]?.trim() ?? "";
20524
+ const requestedAssetPath = path15.isAbsolute(requestedPath) ? requestedPath : path15.resolve(input.workspacePath, requestedPath);
20525
+ const assetPath = await resolveAcpWorkspacePath(
20526
+ input.workspacePath,
20527
+ requestedAssetPath
20528
+ );
20529
+ const uri = pathToFileURL3(assetPath).toString();
20530
+ if (kind === "PHOTO") {
20531
+ if (input.promptCapabilities?.image !== true) {
20532
+ throw new Error("The selected ACP agent does not support image prompts.");
20533
+ }
20534
+ const stat = await fs12.stat(assetPath);
20535
+ if (!stat.isFile() || stat.size > maxEmbeddedImageBytes) {
20536
+ throw new Error("ACP image attachment is missing or exceeds 20 MiB.");
20537
+ }
20538
+ blocks.push({
20539
+ type: "image",
20540
+ data: (await fs12.readFile(assetPath)).toString("base64"),
20541
+ mimeType: imageMimeType(assetPath),
20542
+ uri
20543
+ });
20544
+ } else {
20545
+ await fs12.access(assetPath);
20546
+ blocks.push({
20547
+ type: "resource_link",
20548
+ name: path15.basename(assetPath),
20549
+ uri
20550
+ });
20551
+ }
20552
+ cursor = start + match[0].length;
20553
+ }
20554
+ const trailing = input.prompt.slice(cursor);
20555
+ if (trailing) {
20556
+ blocks.push({ type: "text", text: trailing });
20557
+ }
20558
+ return blocks;
20559
+ }
20560
+
20561
+ // ../../packages/acp/src/extension-registry.ts
20562
+ import { EventEmitter as EventEmitter7 } from "events";
20563
+ var HarnessExtensionInvocationError = class extends Error {
20564
+ constructor(payload) {
20565
+ super(payload.message);
20566
+ this.payload = payload;
20567
+ this.name = "HarnessExtensionInvocationError";
20568
+ }
20569
+ payload;
20570
+ };
20571
+ function extensionKey(extensionId, version2) {
20572
+ return `${extensionId}@${version2}`;
20573
+ }
20574
+ function operationFingerprint(input) {
20575
+ return JSON.stringify(input);
20576
+ }
20577
+ var HarnessExtensionRegistry = class extends EventEmitter7 {
20578
+ extensions = /* @__PURE__ */ new Map();
20579
+ operations = /* @__PURE__ */ new Map();
20580
+ eventSequences = /* @__PURE__ */ new Set();
20581
+ register(input) {
20582
+ const key = extensionKey(input.descriptor.id, input.descriptor.version);
20583
+ const existing = this.extensions.get(key);
20584
+ if (existing && existing.ownerId !== input.ownerId) {
20585
+ throw new Error(
20586
+ `Harness extension ${key} is already owned by ${existing.ownerId}.`
20587
+ );
20588
+ }
20589
+ if (new Set(input.descriptor.methods).size !== input.descriptor.methods.length) {
20590
+ throw new Error(`Harness extension ${key} declares duplicate methods.`);
20591
+ }
20592
+ if (new Set(input.descriptor.events).size !== input.descriptor.events.length) {
20593
+ throw new Error(`Harness extension ${key} declares duplicate events.`);
20594
+ }
20595
+ this.extensions.set(key, {
20596
+ ownerId: input.ownerId,
20597
+ descriptor: structuredClone(input.descriptor),
20598
+ transport: input.transport,
20599
+ wireMethods: { ...input.wireMethods ?? {} },
20600
+ capabilityPatch: input.capabilityPatch ? structuredClone(input.capabilityPatch) : null,
20601
+ paramMappers: { ...input.paramMappers ?? {} }
20602
+ });
20603
+ }
20604
+ unregisterOwner(ownerId) {
20605
+ for (const [key, extension] of this.extensions) {
20606
+ if (extension.ownerId === ownerId) {
20607
+ this.extensions.delete(key);
20608
+ }
20609
+ }
20610
+ }
20611
+ list() {
20612
+ return [...this.extensions.values()].map((extension) => ({
20613
+ ownerId: extension.ownerId,
20614
+ descriptor: structuredClone(extension.descriptor)
20615
+ }));
20616
+ }
20617
+ supports(extensionId, version2, method) {
20618
+ return this.extensions.get(extensionKey(extensionId, version2))?.descriptor.methods.includes(method) ?? false;
20619
+ }
20620
+ effectiveCapabilities(base) {
20621
+ const effective = structuredClone(base);
20622
+ for (const extension of this.extensions.values()) {
20623
+ if (!extension.capabilityPatch) continue;
20624
+ for (const [section, patch] of Object.entries(extension.capabilityPatch)) {
20625
+ Object.assign(
20626
+ effective[section],
20627
+ patch
20628
+ );
20629
+ }
20630
+ }
20631
+ return effective;
20632
+ }
20633
+ invoke(input) {
20634
+ const key = extensionKey(input.extensionId, input.extensionVersion);
20635
+ const extension = this.extensions.get(key);
20636
+ if (!extension || !extension.descriptor.methods.includes(input.method)) {
20637
+ return Promise.reject(this.error(input, {
20638
+ code: "extension_method_unavailable",
20639
+ message: `Harness extension method is unavailable: ${key}/${input.method}`,
20640
+ retryable: false
20641
+ }));
20642
+ }
20643
+ if (input.signal?.aborted) {
20644
+ return Promise.reject(this.error(input, {
20645
+ code: "extension_cancelled",
20646
+ message: "Harness extension request was cancelled before dispatch.",
20647
+ retryable: true
20648
+ }));
20649
+ }
20650
+ const fingerprint = operationFingerprint(input);
20651
+ const cached = this.operations.get(input.idempotencyKey);
20652
+ if (cached) {
20653
+ if (cached.fingerprint !== fingerprint) {
20654
+ return Promise.reject(this.error(input, {
20655
+ code: "idempotency_conflict",
20656
+ message: "Harness extension idempotency key was reused for another operation.",
20657
+ retryable: false
20658
+ }));
20659
+ }
20660
+ return cached.promise;
20661
+ }
20662
+ const envelope = createHarnessExtensionCall(input);
20663
+ const controller = new AbortController();
20664
+ const timeoutMs = input.timeoutMs ?? 3e4;
20665
+ let timer = null;
20666
+ let abort = null;
20667
+ const request = new Promise((resolve, reject) => {
20668
+ abort = () => {
20669
+ controller.abort(input.signal?.reason);
20670
+ reject(this.error(input, {
20671
+ code: "extension_cancelled",
20672
+ message: "Harness extension request was cancelled.",
20673
+ retryable: true
20674
+ }));
20675
+ };
20676
+ input.signal?.addEventListener("abort", abort, { once: true });
20677
+ timer = setTimeout(() => {
20678
+ controller.abort(new Error("Harness extension request timed out."));
20679
+ reject(this.error(input, {
20680
+ code: "extension_timeout",
20681
+ message: `Harness extension request timed out after ${timeoutMs}ms.`,
20682
+ retryable: true
20683
+ }));
20684
+ }, timeoutMs);
20685
+ void Promise.resolve().then(() => extension.transport.request(
20686
+ extension.wireMethods[input.method] ?? harnessExtensionMethodName(
20687
+ input.extensionId,
20688
+ input.extensionVersion,
20689
+ input.method
20690
+ ),
20691
+ extension.paramMappers[input.method]?.(envelope) ?? envelope,
20692
+ controller.signal
20693
+ )).then((value) => resolve(value), (cause) => reject(
20694
+ cause instanceof HarnessExtensionInvocationError ? cause : this.error(input, {
20695
+ code: "extension_request_failed",
20696
+ message: cause instanceof Error ? cause.message : String(cause),
20697
+ retryable: false
20698
+ })
20699
+ ));
20700
+ }).finally(() => {
20701
+ if (timer) clearTimeout(timer);
20702
+ if (abort) input.signal?.removeEventListener("abort", abort);
20703
+ });
20704
+ this.operations.set(input.idempotencyKey, { fingerprint, promise: request });
20705
+ request.catch(() => {
20706
+ if (this.operations.get(input.idempotencyKey)?.promise === request) {
20707
+ this.operations.delete(input.idempotencyKey);
20708
+ }
20709
+ });
20710
+ while (this.operations.size > 256) {
20711
+ this.operations.delete(this.operations.keys().next().value);
20712
+ }
20713
+ return request;
20714
+ }
20715
+ handleEvent(ownerId, event) {
20716
+ if (event.protocol !== REMOTE_CODEX_HARNESS_EXTENSION_PROTOCOL) {
20717
+ throw new Error("Harness extension event protocol is unsupported.");
20718
+ }
20719
+ const extension = this.extensions.get(
20720
+ extensionKey(event.extensionId, event.extensionVersion)
20721
+ );
20722
+ if (!extension || extension.ownerId !== ownerId) {
20723
+ throw new Error("Harness extension event owner does not match registration.");
20724
+ }
20725
+ if (!extension.descriptor.events.includes(event.event)) {
20726
+ throw new Error(`Harness extension event is not declared: ${event.event}`);
20727
+ }
20728
+ if (event.sequence !== null) {
20729
+ const sequenceKey = [
20730
+ ownerId,
20731
+ event.extensionId,
20732
+ event.extensionVersion,
20733
+ event.providerSessionId,
20734
+ event.event,
20735
+ event.sequence
20736
+ ].join("\0");
20737
+ if (this.eventSequences.has(sequenceKey)) {
20738
+ return false;
20739
+ }
20740
+ this.eventSequences.add(sequenceKey);
20741
+ }
20742
+ this.emit("event", structuredClone(event));
20743
+ return true;
20744
+ }
20745
+ error(input, error) {
20746
+ return new HarnessExtensionInvocationError({
20747
+ protocol: REMOTE_CODEX_HARNESS_EXTENSION_PROTOCOL,
20748
+ extensionId: input.extensionId,
20749
+ extensionVersion: input.extensionVersion,
20750
+ method: input.method,
20751
+ operationId: input.operationId,
20752
+ ...error
20753
+ });
20754
+ }
20755
+ };
20756
+
20757
+ // ../../packages/acp/src/session-hydrator.ts
20758
+ function updateMessageId(update) {
20759
+ return typeof update.messageId === "string" && update.messageId.trim() ? update.messageId.trim() : null;
20760
+ }
20761
+ var AcpSessionHydrator = class {
20762
+ constructor(providerSessionId) {
20763
+ this.providerSessionId = providerSessionId;
20764
+ }
20765
+ providerSessionId;
20766
+ turns = [];
20767
+ leadingUpdates = [];
20768
+ current = null;
20769
+ turnIndex = 0;
20770
+ providerIdentifiedTurnCount = 0;
20771
+ receivedAt = Date.now();
20772
+ apply(update) {
20773
+ if (update.sessionUpdate === "user_message_chunk") {
20774
+ this.applyUserMessage(update);
20775
+ return;
20776
+ }
20777
+ if (update.sessionUpdate === "config_option_update" || update.sessionUpdate === "current_mode_update" || update.sessionUpdate === "available_commands_update" || update.sessionUpdate === "session_info_update" || update.sessionUpdate === "usage_update") {
20778
+ return;
20779
+ }
20780
+ if (!this.current) {
20781
+ this.leadingUpdates.push(update);
20782
+ return;
20783
+ }
20784
+ const current = this.current;
20785
+ this.flushLeadingUpdates(current);
20786
+ current.sawNonUserUpdate = true;
20787
+ current.mapper.apply(update);
20788
+ }
20789
+ complete() {
20790
+ this.finishCurrent();
20791
+ return [...this.turns];
20792
+ }
20793
+ coverage() {
20794
+ this.finishCurrent();
20795
+ return {
20796
+ source: "providerReplay",
20797
+ // ACP v1 has no authoritative history total; absence cannot prove completeness.
20798
+ completeness: "unknown",
20799
+ replayedTurnCount: this.turns.length,
20800
+ replayedItemCount: this.turns.reduce(
20801
+ (count, turn) => count + turn.items.length,
20802
+ 0
20803
+ ),
20804
+ providerIdentifiedTurnCount: this.providerIdentifiedTurnCount
20805
+ };
20806
+ }
20807
+ applyUserMessage(update) {
20808
+ const messageId3 = updateMessageId(update);
20809
+ const shouldStartTurn = !this.current || this.current.sawNonUserUpdate || Boolean(
20810
+ messageId3 && this.current.userMessageId && messageId3 !== this.current.userMessageId
20811
+ );
20812
+ if (shouldStartTurn) {
20813
+ if (this.current) {
20814
+ this.finishCurrent();
20815
+ }
20816
+ this.startTurn(messageId3);
20817
+ }
20818
+ const current = this.current;
20819
+ if (messageId3 && !current.userMessageId) {
20820
+ current.userMessageId = messageId3;
20821
+ }
20822
+ current.mapper.appendUserMessage(
20823
+ update.content,
20824
+ current.userMessageId ?? `${current.mapper.turnId}:user`
20825
+ );
20826
+ }
20827
+ startTurn(userMessageId) {
20828
+ const index = this.turnIndex++;
20829
+ const stableSource = userMessageId ?? `${this.providerSessionId}:${index}`;
20830
+ const builder = {
20831
+ userMessageId,
20832
+ mapper: new AcpTurnItemMapper(
20833
+ `acp-hydrated:${stableSource}`,
20834
+ [],
20835
+ "hydrate"
20836
+ ),
20837
+ startedAt: new Date(this.receivedAt + index).toISOString(),
20838
+ sawNonUserUpdate: false
20839
+ };
20840
+ if (userMessageId) {
20841
+ this.providerIdentifiedTurnCount += 1;
20842
+ }
20843
+ this.current = builder;
20844
+ return builder;
20845
+ }
20846
+ finishCurrent() {
20847
+ if (!this.current && this.leadingUpdates.length > 0) {
20848
+ this.startTurn(null);
20849
+ }
20850
+ if (!this.current) {
20851
+ return;
20852
+ }
20853
+ this.flushLeadingUpdates(this.current);
20854
+ const completed = this.current.mapper.complete("completed").turn;
20855
+ if (completed.items.length > 0) {
20856
+ this.turns.push({
20857
+ ...completed,
20858
+ startedAt: this.current.startedAt
20859
+ });
20860
+ }
20861
+ this.current = null;
20862
+ }
20863
+ flushLeadingUpdates(current) {
20864
+ for (const update of this.leadingUpdates.splice(0)) {
20865
+ current.mapper.apply(update);
20866
+ }
20867
+ }
20868
+ };
20301
20869
 
20302
20870
  // ../../packages/acp/src/terminal-service.ts
20303
20871
  import { randomUUID as randomUUID3 } from "crypto";
@@ -20317,14 +20885,28 @@ function retainedOutput(state) {
20317
20885
  };
20318
20886
  }
20319
20887
  var AcpTerminalService = class {
20320
- constructor(sessionCwd) {
20888
+ constructor(sessionCwd, onOperation = () => void 0) {
20321
20889
  this.sessionCwd = sessionCwd;
20890
+ this.onOperation = onOperation;
20322
20891
  }
20323
20892
  sessionCwd;
20893
+ onOperation;
20324
20894
  terminals = /* @__PURE__ */ new Map();
20325
- create(params) {
20895
+ async create(params) {
20326
20896
  const terminalId = randomUUID3();
20327
- const cwd = params.cwd ?? this.sessionCwd(params.sessionId) ?? process.cwd();
20897
+ const sessionCwd = this.sessionCwd(params.sessionId);
20898
+ if (!sessionCwd) {
20899
+ throw new Error(`ACP session workspace not found: ${params.sessionId}`);
20900
+ }
20901
+ const cwd = await resolveAcpWorkspacePath(
20902
+ sessionCwd,
20903
+ params.cwd ?? sessionCwd
20904
+ );
20905
+ this.onOperation({
20906
+ operation: "terminal.create",
20907
+ sessionId: params.sessionId,
20908
+ path: cwd
20909
+ });
20328
20910
  const parsed = params.args && params.args.length > 0 ? { command: params.command, args: params.args } : parseCommandLine(params.command);
20329
20911
  const child = spawnProcess({
20330
20912
  command: parsed.command,
@@ -20412,10 +20994,13 @@ var AcpTerminalService = class {
20412
20994
  // ../../packages/acp/src/runtimeAdapter.ts
20413
20995
  var acpCapabilities = {
20414
20996
  sessions: {
20415
- list: true,
20997
+ list: false,
20416
20998
  read: true,
20417
- resume: true,
20418
- importLocal: false
20999
+ resume: false,
21000
+ importLocal: false,
21001
+ load: false,
21002
+ close: false,
21003
+ delete: false
20419
21004
  },
20420
21005
  turns: {
20421
21006
  start: true,
@@ -20467,7 +21052,19 @@ function errorMessage3(error) {
20467
21052
  function cloneCapabilities() {
20468
21053
  return structuredClone(acpCapabilities);
20469
21054
  }
20470
- function isRecord12(value) {
21055
+ function applyNegotiatedAcpCapabilities(target, capabilities) {
21056
+ target.sessions.list = Boolean(capabilities?.sessionCapabilities?.list);
21057
+ target.sessions.load = capabilities?.loadSession === true;
21058
+ target.sessions.resume = Boolean(
21059
+ capabilities?.loadSession || capabilities?.sessionCapabilities?.resume
21060
+ );
21061
+ target.sessions.close = Boolean(capabilities?.sessionCapabilities?.close);
21062
+ target.sessions.delete = Boolean(capabilities?.sessionCapabilities?.delete);
21063
+ target.sessions.importLocal = false;
21064
+ target.branching.fork = Boolean(capabilities?.sessionCapabilities?.fork);
21065
+ return target;
21066
+ }
21067
+ function isRecord13(value) {
20471
21068
  return typeof value === "object" && value !== null && !Array.isArray(value);
20472
21069
  }
20473
21070
  function allSelectOptions(option) {
@@ -20563,8 +21160,12 @@ function sessionDetail(state) {
20563
21160
  createdAt: state.createdAt,
20564
21161
  updatedAt: state.updatedAt,
20565
21162
  status: state.status,
20566
- turns: state.turns,
20567
- totalTurnCount: state.turns.length
21163
+ turns: state.turns.map((turn) => ({
21164
+ ...turn,
21165
+ items: turn.items.map((item) => ({ ...item }))
21166
+ })),
21167
+ totalTurnCount: state.turns.length,
21168
+ ...state.hydrationCoverage ? { historyCoverage: { ...state.hydrationCoverage } } : {}
20568
21169
  };
20569
21170
  }
20570
21171
  function sessionSummaryFromInfo2(info) {
@@ -20580,12 +21181,29 @@ function sessionSummaryFromInfo2(info) {
20580
21181
  rawSession: info
20581
21182
  };
20582
21183
  }
20583
- var AcpRuntimeAdapter = class extends EventEmitter7 {
21184
+ var AcpRuntimeAdapter = class extends EventEmitter8 {
20584
21185
  constructor(options) {
20585
21186
  super();
20586
21187
  this.options = options;
21188
+ this.extensionRegistry.on(
21189
+ "event",
21190
+ (event) => this.emitRuntimeEvent({
21191
+ type: "harness.extension",
21192
+ provider: "acp",
21193
+ providerSessionId: event.providerSessionId,
21194
+ providerTurnId: event.providerTurnId,
21195
+ providerItemId: event.providerItemId,
21196
+ extensionId: event.extensionId,
21197
+ extensionVersion: event.extensionVersion,
21198
+ event: event.event,
21199
+ operationId: event.operationId,
21200
+ sequence: event.sequence,
21201
+ payload: event.payload
21202
+ })
21203
+ );
20587
21204
  this.terminalService = new AcpTerminalService(
20588
- (sessionId) => this.sessions.get(sessionId)?.cwd ?? null
21205
+ (sessionId) => this.sessions.get(sessionId)?.cwd ?? null,
21206
+ (operation) => this.emit("fs-operation", operation)
20589
21207
  );
20590
21208
  }
20591
21209
  options;
@@ -20607,6 +21225,8 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20607
21225
  sessions = /* @__PURE__ */ new Map();
20608
21226
  knownSessions = /* @__PURE__ */ new Map();
20609
21227
  pendingPermissions = /* @__PURE__ */ new Map();
21228
+ hydrators = /* @__PURE__ */ new Map();
21229
+ extensionRegistry = new HarnessExtensionRegistry();
20610
21230
  terminalService;
20611
21231
  child = null;
20612
21232
  connection = null;
@@ -20625,6 +21245,9 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20625
21245
  getStatus() {
20626
21246
  return { ...this.status };
20627
21247
  }
21248
+ getProtocolSnapshot() {
21249
+ return snapshotAcpInitializeResponse(this.initializeResponse);
21250
+ }
20628
21251
  async start() {
20629
21252
  if (this.status.state === "ready") {
20630
21253
  return;
@@ -20675,7 +21298,16 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20675
21298
  Writable.toWeb(child.stdin),
20676
21299
  Readable.toWeb(child.stdout)
20677
21300
  );
20678
- const app2 = client({ name: this.options.clientInfo?.name ?? "remote-codex-supervisor" }).onRequest(methods.client.session.requestPermission, (request) => this.requestPermission(request.params)).onNotification(methods.client.session.update, (notification) => this.handleSessionUpdate(notification.params)).onRequest(methods.client.fs.readTextFile, (request) => this.readTextFile(request.params)).onRequest(methods.client.fs.writeTextFile, (request) => this.writeTextFile(request.params)).onRequest(methods.client.terminal.create, (request) => this.terminalService.create(request.params)).onRequest(methods.client.terminal.output, (request) => this.terminalService.output(request.params)).onRequest(methods.client.terminal.waitForExit, (request) => this.terminalService.waitForExit(request.params)).onRequest(methods.client.terminal.kill, (request) => this.terminalService.kill(request.params)).onRequest(methods.client.terminal.release, (request) => this.terminalService.release(request.params));
21301
+ const app2 = client({ name: this.options.clientInfo?.name ?? "remote-codex-supervisor" }).onRequest(methods.client.session.requestPermission, (request) => this.requestPermission(request.params)).onNotification(methods.client.session.update, (notification) => this.handleSessionUpdate(notification.params)).onNotification(
21302
+ REMOTE_CODEX_HARNESS_EXTENSION_EVENT_METHOD,
21303
+ (params) => params,
21304
+ (notification) => {
21305
+ this.extensionRegistry.handleEvent(
21306
+ "acp-agent",
21307
+ notification.params
21308
+ );
21309
+ }
21310
+ ).onRequest(methods.client.fs.readTextFile, (request) => this.readTextFile(request.params)).onRequest(methods.client.fs.writeTextFile, (request) => this.writeTextFile(request.params)).onRequest(methods.client.terminal.create, (request) => this.terminalService.create(request.params)).onRequest(methods.client.terminal.output, (request) => this.terminalService.output(request.params)).onRequest(methods.client.terminal.waitForExit, (request) => this.terminalService.waitForExit(request.params)).onRequest(methods.client.terminal.kill, (request) => this.terminalService.kill(request.params)).onRequest(methods.client.terminal.release, (request) => this.terminalService.release(request.params));
20679
21311
  this.connection = app2.connect(stream);
20680
21312
  this.context = this.connection.agent;
20681
21313
  const initialize = this.context.request(methods.agent.initialize, {
@@ -20697,7 +21329,9 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20697
21329
  });
20698
21330
  const initializeResponse = await this.withStartupTimeout(initialize);
20699
21331
  this.initializeResponse = initializeResponse;
21332
+ this.resetCapabilities();
20700
21333
  this.applyAgentCapabilities(initializeResponse.agentCapabilities);
21334
+ this.registerNegotiatedExtensions(initializeResponse);
20701
21335
  this.status = {
20702
21336
  ...this.status,
20703
21337
  state: "ready",
@@ -20727,11 +21361,11 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20727
21361
  async stop() {
20728
21362
  this.stopping = true;
20729
21363
  this.terminalService.stop();
20730
- for (const [id, permission] of this.pendingPermissions) {
20731
- clearTimeout(permission.timer);
20732
- permission.resolve(cancelledPermission());
20733
- this.pendingPermissions.delete(id);
20734
- }
21364
+ this.settleActiveTurns("interrupted", "ACP runtime stopped.");
21365
+ this.cancelPendingPermissions();
21366
+ this.hydrators.clear();
21367
+ await this.closeLoadedSessionsBeforeStop();
21368
+ this.extensionRegistry.unregisterOwner("acp-agent");
20735
21369
  this.connection?.close();
20736
21370
  this.child?.kill("SIGTERM");
20737
21371
  this.connection = null;
@@ -20746,6 +21380,21 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20746
21380
  };
20747
21381
  this.emit("status", this.getStatus());
20748
21382
  }
21383
+ async closeLoadedSessionsBeforeStop() {
21384
+ if (!this.context || !this.initializeResponse?.agentCapabilities?.sessionCapabilities?.close) {
21385
+ return;
21386
+ }
21387
+ const sessionIds = [...this.sessions.keys()];
21388
+ const results = await Promise.allSettled(sessionIds.map((sessionId) => this.context.request(methods.agent.session.close, { sessionId })));
21389
+ results.forEach((result, index) => {
21390
+ if (result.status === "rejected") {
21391
+ this.emit(
21392
+ "warning",
21393
+ `Unable to close ACP session ${sessionIds[index] ?? "unknown"} before stop: ${errorMessage3(result.reason)}`
21394
+ );
21395
+ }
21396
+ });
21397
+ }
20749
21398
  async listModels() {
20750
21399
  return [{
20751
21400
  id: "default",
@@ -20759,13 +21408,21 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20759
21408
  }];
20760
21409
  }
20761
21410
  async inspectModelOptions(cwd) {
21411
+ const sessionCapabilities = this.initializeResponse?.agentCapabilities?.sessionCapabilities;
21412
+ if (!sessionCapabilities?.delete) {
21413
+ return this.listModels();
21414
+ }
20762
21415
  const context = await this.requireContext();
20763
21416
  const response = await context.request(methods.agent.session.new, {
20764
- cwd: path14.resolve(cwd),
21417
+ cwd: path16.resolve(cwd),
20765
21418
  mcpServers: [],
20766
21419
  _meta: { yoloMode: false, remoteCodexCapabilityProbe: true }
20767
21420
  });
20768
21421
  let configOptions = response.configOptions ?? [];
21422
+ this.updateCapabilitiesFromConfigOptions(configOptions);
21423
+ const supportsPerformanceMode = configOptions.some(
21424
+ (option) => option.id === "fast-mode" || option.category === "model_config"
21425
+ );
20769
21426
  try {
20770
21427
  const modelOption = configOptionByCategory(configOptions, "model");
20771
21428
  if (!modelOption || modelOption.type !== "select") {
@@ -20777,6 +21434,7 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20777
21434
  description: "Use the model configured by the ACP agent.",
20778
21435
  isDefault: true,
20779
21436
  hidden: false,
21437
+ supportsPerformanceMode,
20780
21438
  supportedReasoningEfforts: reasoning.efforts,
20781
21439
  defaultReasoningEffort: reasoning.defaultEffort,
20782
21440
  selectionKind: "model"
@@ -20804,6 +21462,7 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20804
21462
  description: option.description ?? "",
20805
21463
  isDefault: option.value === modelOption.currentValue,
20806
21464
  hidden: false,
21465
+ supportsPerformanceMode,
20807
21466
  supportedReasoningEfforts: reasoning.efforts,
20808
21467
  defaultReasoningEffort: reasoning.defaultEffort,
20809
21468
  selectionKind: "model"
@@ -20811,11 +21470,9 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20811
21470
  }
20812
21471
  return models;
20813
21472
  } finally {
20814
- if (this.initializeResponse?.agentCapabilities?.sessionCapabilities?.close) {
20815
- await context.request(methods.agent.session.close, {
20816
- sessionId: response.sessionId
20817
- }).catch(() => void 0);
20818
- }
21473
+ await context.request(methods.agent.session.delete, {
21474
+ sessionId: response.sessionId
21475
+ }).catch(() => void 0);
20819
21476
  }
20820
21477
  }
20821
21478
  async listSessions() {
@@ -20847,22 +21504,170 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20847
21504
  async listLoadedSessions() {
20848
21505
  return [...this.sessions.keys()];
20849
21506
  }
20850
- async readSession(providerSessionId) {
20851
- const state = this.sessions.get(providerSessionId);
20852
- if (state) {
20853
- return sessionDetail(state);
21507
+ async closeSession(providerSessionId) {
21508
+ if (!this.initializeResponse?.agentCapabilities?.sessionCapabilities?.close) {
21509
+ throw new AgentRuntimeError(
21510
+ "ACP agent does not support session/close.",
21511
+ "acp",
21512
+ "request_failed"
21513
+ );
20854
21514
  }
20855
- throw new AgentRuntimeError(
20856
- "ACP session history is owned by the Remote Codex supervisor and is not materialized in this runtime process.",
20857
- "acp",
20858
- "request_failed",
20859
- { historyUnavailable: true, providerSessionId }
21515
+ await (await this.requireContext()).request(methods.agent.session.close, {
21516
+ sessionId: providerSessionId
21517
+ });
21518
+ this.sessions.delete(providerSessionId);
21519
+ }
21520
+ async deleteSession(providerSessionId) {
21521
+ if (!this.initializeResponse?.agentCapabilities?.sessionCapabilities?.delete) {
21522
+ throw new AgentRuntimeError(
21523
+ "ACP agent does not support session/delete.",
21524
+ "acp",
21525
+ "request_failed"
21526
+ );
21527
+ }
21528
+ await (await this.requireContext()).request(methods.agent.session.delete, {
21529
+ sessionId: providerSessionId
21530
+ });
21531
+ this.sessions.delete(providerSessionId);
21532
+ this.knownSessions.delete(providerSessionId);
21533
+ }
21534
+ async sendInput(input) {
21535
+ const state = this.sessions.get(input.providerSessionId);
21536
+ if (!state?.activeMapper || state.activeMapper.turnId !== input.providerTurnId) {
21537
+ return null;
21538
+ }
21539
+ if (!this.extensionRegistry.supports("acp.steering", 1, "steer")) {
21540
+ throw new AgentRuntimeError(
21541
+ "The selected ACP agent does not support running-turn steering.",
21542
+ "acp",
21543
+ "request_failed"
21544
+ );
21545
+ }
21546
+ const prompt = await buildAcpPromptContent({
21547
+ prompt: input.prompt,
21548
+ workspacePath: input.workspacePath ?? state.cwd,
21549
+ promptCapabilities: this.initializeResponse?.agentCapabilities?.promptCapabilities
21550
+ });
21551
+ const operationId = randomUUID4();
21552
+ await this.extensionRegistry.invoke({
21553
+ extensionId: "acp.steering",
21554
+ extensionVersion: 1,
21555
+ method: "steer",
21556
+ operationId,
21557
+ idempotencyKey: `${input.providerSessionId}:${input.providerTurnId}:steer:${operationId}`,
21558
+ params: {
21559
+ providerSessionId: input.providerSessionId,
21560
+ prompt
21561
+ }
21562
+ });
21563
+ return state.activeMapper.turn();
21564
+ }
21565
+ async compactSession(providerSessionId) {
21566
+ const operationId = randomUUID4();
21567
+ await this.extensionRegistry.invoke({
21568
+ extensionId: "codex.control",
21569
+ extensionVersion: 1,
21570
+ method: "compact",
21571
+ operationId,
21572
+ idempotencyKey: `${providerSessionId}:compact:${operationId}`,
21573
+ params: { providerSessionId },
21574
+ timeoutMs: 18e4
21575
+ });
21576
+ }
21577
+ async forkSession(input) {
21578
+ if (!this.initializeResponse?.agentCapabilities?.sessionCapabilities?.fork) {
21579
+ throw new AgentRuntimeError(
21580
+ "The selected ACP agent does not support session/fork.",
21581
+ "acp",
21582
+ "request_failed"
21583
+ );
21584
+ }
21585
+ const source = this.sessions.get(input.providerSessionId) ?? await this.restoreSession(input.providerSessionId);
21586
+ const response = await (await this.requireContext()).request(
21587
+ methods.agent.session.fork,
21588
+ {
21589
+ sessionId: source.providerSessionId,
21590
+ cwd: source.cwd,
21591
+ mcpServers: []
21592
+ }
20860
21593
  );
21594
+ const now = (/* @__PURE__ */ new Date()).toISOString();
21595
+ this.knownSessions.set(response.sessionId, {
21596
+ provider: "acp",
21597
+ providerSessionId: response.sessionId,
21598
+ cwd: source.cwd,
21599
+ title: source.title,
21600
+ preview: source.turns.at(-1)?.items.filter((item) => item.kind === "agentMessage").map((item) => item.text).join("\n") || null,
21601
+ createdAt: now,
21602
+ updatedAt: now,
21603
+ status: "not_loaded"
21604
+ });
21605
+ const forked = await this.restoreSession(response.sessionId);
21606
+ if (forked.turns.length === 0 && source.turns.length > 0) {
21607
+ forked.turns = structuredClone(source.turns);
21608
+ forked.hydrationCoverage = null;
21609
+ }
21610
+ forked.modes = response.modes ?? forked.modes;
21611
+ forked.configOptions = response.configOptions ?? forked.configOptions;
21612
+ this.syncStateFromConfigOptions(forked);
21613
+ return sessionDetail(forked);
21614
+ }
21615
+ async getGoal(providerSessionId) {
21616
+ return this.sessions.get(providerSessionId)?.goal ?? null;
21617
+ }
21618
+ async setGoal(input) {
21619
+ const state = this.sessions.get(input.providerSessionId);
21620
+ if (!state) {
21621
+ throw new AgentRuntimeError("ACP session is not loaded.", "acp", "request_failed");
21622
+ }
21623
+ if (input.tokenBudget !== void 0 && input.tokenBudget !== null) {
21624
+ throw new AgentRuntimeError(
21625
+ "Codex ACP goal control does not expose token budgets.",
21626
+ "acp",
21627
+ "request_failed"
21628
+ );
21629
+ }
21630
+ const action = input.objective?.trim() ? { action: "set", objective: input.objective.trim() } : input.status === "paused" ? { action: "pause" } : input.status === "active" ? { action: "resume" } : null;
21631
+ if (!action) {
21632
+ throw new AgentRuntimeError(
21633
+ "Codex ACP goal update requires an objective, pause, or resume action.",
21634
+ "acp",
21635
+ "request_failed"
21636
+ );
21637
+ }
21638
+ await this.invokeAcpGoal(input.providerSessionId, action);
21639
+ if (!state.goal) {
21640
+ throw new AgentRuntimeError(
21641
+ "Codex ACP completed goal control without publishing a goal snapshot.",
21642
+ "acp",
21643
+ "invalid_response"
21644
+ );
21645
+ }
21646
+ return state.goal;
21647
+ }
21648
+ async clearGoal(providerSessionId) {
21649
+ const state = this.sessions.get(providerSessionId);
21650
+ if (!state) {
21651
+ return false;
21652
+ }
21653
+ const existed = Boolean(state.goal);
21654
+ await this.invokeAcpGoal(providerSessionId, { action: "clear" });
21655
+ return existed;
21656
+ }
21657
+ listHarnessExtensions() {
21658
+ return this.extensionRegistry.list();
21659
+ }
21660
+ invokeHarnessExtension(input) {
21661
+ return this.extensionRegistry.invoke(input);
21662
+ }
21663
+ async readSession(providerSessionId) {
21664
+ const state = this.sessions.get(providerSessionId) ?? await this.restoreSession(providerSessionId);
21665
+ return sessionDetail(state);
20861
21666
  }
20862
21667
  async startSession(input) {
20863
21668
  const context = await this.requireContext();
20864
21669
  const response = await context.request(methods.agent.session.new, {
20865
- cwd: path14.resolve(input.cwd),
21670
+ cwd: path16.resolve(input.cwd),
20866
21671
  mcpServers: [],
20867
21672
  _meta: {
20868
21673
  yoloMode: input.approvalMode === "yolo"
@@ -20871,23 +21676,33 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20871
21676
  const now = (/* @__PURE__ */ new Date()).toISOString();
20872
21677
  const state = {
20873
21678
  providerSessionId: response.sessionId,
20874
- cwd: path14.resolve(input.cwd),
21679
+ cwd: path16.resolve(input.cwd),
20875
21680
  title: null,
20876
21681
  createdAt: now,
20877
21682
  updatedAt: now,
20878
- model: input.model === "default" ? null : input.model,
20879
- reasoningEffort: input.reasoningEffort ?? null,
21683
+ model: null,
21684
+ reasoningEffort: null,
20880
21685
  sandboxMode: input.sandboxMode ?? null,
20881
21686
  status: "idle",
20882
21687
  turns: [],
20883
21688
  activeMapper: null,
20884
21689
  modes: response.modes ?? null,
20885
21690
  configOptions: response.configOptions ?? [],
20886
- availableCommands: []
21691
+ availableCommands: [],
21692
+ hydrationCoverage: null,
21693
+ goal: null
20887
21694
  };
20888
21695
  this.sessions.set(state.providerSessionId, state);
21696
+ this.syncStateFromConfigOptions(state);
20889
21697
  this.knownSessions.set(state.providerSessionId, sessionDetail(state));
20890
- await this.applySessionSettings(state, input.model, input.reasoningEffort, input.sandboxMode);
21698
+ await this.applySessionSettings(
21699
+ state,
21700
+ input.model,
21701
+ input.reasoningEffort,
21702
+ input.sandboxMode,
21703
+ void 0,
21704
+ input.performanceMode
21705
+ );
20891
21706
  const session = sessionDetail(state);
20892
21707
  return {
20893
21708
  provider: "acp",
@@ -20903,7 +21718,14 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20903
21718
  async resumeSession(input) {
20904
21719
  const existing = this.sessions.get(input.providerSessionId);
20905
21720
  const state = existing ?? await this.restoreSession(input.providerSessionId);
20906
- await this.applySessionSettings(state, input.model, void 0, input.sandboxMode);
21721
+ await this.applySessionSettings(
21722
+ state,
21723
+ input.model,
21724
+ void 0,
21725
+ input.sandboxMode,
21726
+ void 0,
21727
+ input.performanceMode
21728
+ );
20907
21729
  state.status = "idle";
20908
21730
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
20909
21731
  const session = sessionDetail(state);
@@ -20933,7 +21755,8 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20933
21755
  input.model,
20934
21756
  input.reasoningEffort,
20935
21757
  input.sandboxMode,
20936
- input.collaborationMode
21758
+ input.collaborationMode,
21759
+ input.performanceMode
20937
21760
  );
20938
21761
  const turnId = input.displayTurnId ?? randomUUID4();
20939
21762
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -20955,13 +21778,21 @@ var AcpRuntimeAdapter = class extends EventEmitter7 {
20955
21778
  providerSessionId: state.providerSessionId,
20956
21779
  turn: startedTurn
20957
21780
  });
20958
- const prompt = input.developerInstructions?.trim() ? `${input.developerInstructions.trim()}
21781
+ const prompt = await buildAcpPromptContent({
21782
+ prompt: input.prompt,
21783
+ workspacePath: input.workspacePath ?? state.cwd,
21784
+ promptCapabilities: this.initializeResponse?.agentCapabilities?.promptCapabilities,
21785
+ ...input.content ? { content: input.content } : {}
21786
+ });
21787
+ if (input.developerInstructions?.trim()) {
21788
+ prompt.unshift({ type: "text", text: `${input.developerInstructions.trim()}
20959
21789
 
20960
- ${input.prompt}` : input.prompt;
21790
+ ` });
21791
+ }
20961
21792
  const context = await this.requireContext();
20962
21793
  void context.request(methods.agent.session.prompt, {
20963
21794
  sessionId: state.providerSessionId,
20964
- prompt: [{ type: "text", text: prompt }]
21795
+ prompt
20965
21796
  }).then(
20966
21797
  (response) => this.completePrompt(state, mapper, response),
20967
21798
  (error) => this.failPrompt(state, mapper, error)
@@ -20980,7 +21811,7 @@ ${input.prompt}` : input.prompt;
20980
21811
  return state.activeMapper.turn("interrupted");
20981
21812
  }
20982
21813
  mapProviderRequest(request, options) {
20983
- if (request.method !== methods.client.session.requestPermission || !isRecord12(request.params)) {
21814
+ if (request.method !== methods.client.session.requestPermission || !isRecord13(request.params)) {
20984
21815
  return null;
20985
21816
  }
20986
21817
  const params = request.params;
@@ -21027,7 +21858,7 @@ ${input.prompt}` : input.prompt;
21027
21858
  };
21028
21859
  }
21029
21860
  buildProviderRequestResponse(pending, input) {
21030
- const options = Array.isArray(pending.responsePayload?.options) ? pending.responsePayload.options.filter(isRecord12) : [];
21861
+ const options = Array.isArray(pending.responsePayload?.options) ? pending.responsePayload.options.filter(isRecord13) : [];
21031
21862
  const answer = Object.values(input.answers).flatMap((entry) => entry.answers)[0] ?? "";
21032
21863
  const selected = options.find((option) => option.name === answer || option.id === answer);
21033
21864
  return selected && typeof selected.id === "string" ? selectedPermission(selected.id) : cancelledPermission();
@@ -21072,19 +21903,30 @@ ${input.prompt}` : input.prompt;
21072
21903
  activeMapper: null,
21073
21904
  modes: null,
21074
21905
  configOptions: [],
21075
- availableCommands: []
21906
+ availableCommands: [],
21907
+ hydrationCoverage: null,
21908
+ goal: null
21076
21909
  };
21077
21910
  this.sessions.set(providerSessionId, state);
21078
21911
  try {
21079
21912
  const capabilities = this.initializeResponse?.agentCapabilities;
21080
21913
  if (capabilities?.loadSession) {
21081
- const response = await context.request(methods.agent.session.load, {
21082
- sessionId: providerSessionId,
21083
- cwd: summary.cwd,
21084
- mcpServers: []
21085
- });
21086
- state.modes = response.modes ?? null;
21087
- state.configOptions = response.configOptions ?? [];
21914
+ const hydrator = new AcpSessionHydrator(providerSessionId);
21915
+ this.hydrators.set(providerSessionId, hydrator);
21916
+ try {
21917
+ const response = await context.request(methods.agent.session.load, {
21918
+ sessionId: providerSessionId,
21919
+ cwd: summary.cwd,
21920
+ mcpServers: []
21921
+ });
21922
+ state.modes = response.modes ?? null;
21923
+ state.configOptions = response.configOptions ?? [];
21924
+ state.turns = hydrator.complete();
21925
+ state.hydrationCoverage = hydrator.coverage();
21926
+ this.syncStateFromConfigOptions(state);
21927
+ } finally {
21928
+ this.hydrators.delete(providerSessionId);
21929
+ }
21088
21930
  } else if (capabilities?.sessionCapabilities?.resume) {
21089
21931
  const response = await context.request(methods.agent.session.resume, {
21090
21932
  sessionId: providerSessionId,
@@ -21093,6 +21935,7 @@ ${input.prompt}` : input.prompt;
21093
21935
  });
21094
21936
  state.modes = response.modes ?? null;
21095
21937
  state.configOptions = response.configOptions ?? [];
21938
+ this.syncStateFromConfigOptions(state);
21096
21939
  } else {
21097
21940
  throw new Error("ACP agent does not support session/load or session/resume.");
21098
21941
  }
@@ -21109,18 +21952,24 @@ ${input.prompt}` : input.prompt;
21109
21952
  );
21110
21953
  }
21111
21954
  }
21112
- async applySessionSettings(state, model, reasoningEffort, sandboxMode, collaborationMode) {
21955
+ async applySessionSettings(state, model, reasoningEffort, sandboxMode, collaborationMode, performanceMode) {
21113
21956
  if (model && model !== "default") {
21114
- await this.setConfigOption(state, "model", model);
21115
- state.model = model;
21957
+ state.model = await this.setConfigOption(state, "model", model);
21116
21958
  }
21117
21959
  if (reasoningEffort) {
21118
- await this.setConfigOption(state, "thought_level", reasoningEffort);
21119
- state.reasoningEffort = reasoningEffort;
21960
+ const applied = await this.setConfigOption(
21961
+ state,
21962
+ "thought_level",
21963
+ reasoningEffort
21964
+ );
21965
+ state.reasoningEffort = normalizeAcpEffort(applied);
21120
21966
  }
21121
21967
  if (sandboxMode !== void 0) {
21122
21968
  state.sandboxMode = sandboxMode;
21123
21969
  }
21970
+ if (performanceMode) {
21971
+ await this.setFastMode(state, performanceMode === "fast");
21972
+ }
21124
21973
  const modes = state.modes?.availableModes ?? [];
21125
21974
  if (modes.length === 0) {
21126
21975
  return;
@@ -21146,16 +21995,27 @@ ${input.prompt}` : input.prompt;
21146
21995
  sessionId: state.providerSessionId,
21147
21996
  modelId: value
21148
21997
  });
21149
- return;
21998
+ return value;
21150
21999
  }
21151
22000
  if (!option || option.type !== "select") {
21152
- return;
22001
+ throw new AgentRuntimeError(
22002
+ `ACP agent does not expose a ${category} config option.`,
22003
+ "acp",
22004
+ "request_failed"
22005
+ );
21153
22006
  }
21154
22007
  const selected = allSelectOptions(option).find(
21155
22008
  (candidate) => candidate.value === value || candidate.name.toLowerCase() === value.toLowerCase()
21156
22009
  );
21157
- if (!selected || selected.value === option.currentValue) {
21158
- return;
22010
+ if (!selected) {
22011
+ throw new AgentRuntimeError(
22012
+ `ACP agent rejected unknown ${category} option: ${value}`,
22013
+ "acp",
22014
+ "request_failed"
22015
+ );
22016
+ }
22017
+ if (selected.value === option.currentValue) {
22018
+ return selected.value;
21159
22019
  }
21160
22020
  const context = await this.requireContext();
21161
22021
  const response = await context.request(methods.agent.session.setConfigOption, {
@@ -21164,6 +22024,60 @@ ${input.prompt}` : input.prompt;
21164
22024
  value: selected.value
21165
22025
  });
21166
22026
  state.configOptions = response.configOptions;
22027
+ return selected.value;
22028
+ }
22029
+ syncStateFromConfigOptions(state) {
22030
+ this.updateCapabilitiesFromConfigOptions(state.configOptions);
22031
+ const modelOption = configOptionByCategory(state.configOptions, "model");
22032
+ if (modelOption?.type === "select") {
22033
+ state.model = modelOption.currentValue;
22034
+ }
22035
+ const thoughtOption = configOptionByCategory(
22036
+ state.configOptions,
22037
+ "thought_level"
22038
+ );
22039
+ if (thoughtOption?.type === "select") {
22040
+ state.reasoningEffort = normalizeAcpEffort(thoughtOption.currentValue);
22041
+ }
22042
+ }
22043
+ updateCapabilitiesFromConfigOptions(configOptions) {
22044
+ this.capabilities.management.models ||= Boolean(
22045
+ configOptionByCategory(configOptions, "model")
22046
+ );
22047
+ this.capabilities.controls.performanceMode ||= configOptions.some(
22048
+ (option) => option.id === "fast-mode" || option.category === "model_config"
22049
+ );
22050
+ }
22051
+ async setFastMode(state, enabled) {
22052
+ const option = state.configOptions.find(
22053
+ (candidate) => candidate.id === "fast-mode" || candidate.category === "model_config"
22054
+ );
22055
+ if (!option) {
22056
+ throw new AgentRuntimeError(
22057
+ "The selected ACP agent does not expose fast mode.",
22058
+ "acp",
22059
+ "request_failed"
22060
+ );
22061
+ }
22062
+ const value = option.type === "boolean" ? enabled : allSelectOptions(option).find((candidate) => enabled ? ["on", "true", "fast"].includes(candidate.value.toLowerCase()) : ["off", "false", "standard"].includes(candidate.value.toLowerCase()))?.value;
22063
+ if (value === void 0 || value === option.currentValue) {
22064
+ return;
22065
+ }
22066
+ const response = await (await this.requireContext()).request(
22067
+ methods.agent.session.setConfigOption,
22068
+ option.type === "boolean" ? {
22069
+ sessionId: state.providerSessionId,
22070
+ configId: option.id,
22071
+ type: "boolean",
22072
+ value
22073
+ } : {
22074
+ sessionId: state.providerSessionId,
22075
+ configId: option.id,
22076
+ value
22077
+ }
22078
+ );
22079
+ state.configOptions = response.configOptions;
22080
+ this.updateCapabilitiesFromConfigOptions(state.configOptions);
21167
22081
  }
21168
22082
  handleSessionUpdate(notification) {
21169
22083
  const state = this.sessions.get(notification.sessionId);
@@ -21172,8 +22086,10 @@ ${input.prompt}` : input.prompt;
21172
22086
  }
21173
22087
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
21174
22088
  const update = notification.update;
22089
+ const hydrator = this.hydrators.get(notification.sessionId);
21175
22090
  if (update.sessionUpdate === "config_option_update") {
21176
22091
  state.configOptions = update.configOptions;
22092
+ this.syncStateFromConfigOptions(state);
21177
22093
  } else if (update.sessionUpdate === "current_mode_update" && state.modes) {
21178
22094
  state.modes = { ...state.modes, currentModeId: update.currentModeId };
21179
22095
  } else if (update.sessionUpdate === "available_commands_update") {
@@ -21181,7 +22097,7 @@ ${input.prompt}` : input.prompt;
21181
22097
  } else if (update.sessionUpdate === "session_info_update") {
21182
22098
  if (update.title !== void 0) {
21183
22099
  state.title = update.title;
21184
- if (update.title) {
22100
+ if (update.title && !hydrator) {
21185
22101
  this.emitRuntimeEvent({
21186
22102
  type: "session.title.updated",
21187
22103
  provider: "acp",
@@ -21190,6 +22106,30 @@ ${input.prompt}` : input.prompt;
21190
22106
  });
21191
22107
  }
21192
22108
  }
22109
+ const goal = this.goalFromSessionInfoUpdate(
22110
+ state.providerSessionId,
22111
+ update
22112
+ );
22113
+ if (goal !== void 0) {
22114
+ state.goal = goal;
22115
+ if (!hydrator) {
22116
+ this.emitRuntimeEvent(goal ? {
22117
+ type: "goal.updated",
22118
+ provider: "acp",
22119
+ providerSessionId: state.providerSessionId,
22120
+ providerTurnId: state.activeMapper?.turnId ?? null,
22121
+ goal
22122
+ } : {
22123
+ type: "goal.cleared",
22124
+ provider: "acp",
22125
+ providerSessionId: state.providerSessionId
22126
+ });
22127
+ }
22128
+ }
22129
+ }
22130
+ if (hydrator) {
22131
+ hydrator.apply(update);
22132
+ return;
21193
22133
  }
21194
22134
  const mapper = state.activeMapper;
21195
22135
  if (!mapper) {
@@ -21328,10 +22268,17 @@ ${input.prompt}` : input.prompt;
21328
22268
  });
21329
22269
  }
21330
22270
  async readTextFile(params) {
21331
- if (!path14.isAbsolute(params.path)) {
21332
- throw new Error("ACP file paths must be absolute.");
22271
+ const session = this.sessions.get(params.sessionId);
22272
+ if (!session) {
22273
+ throw new Error(`ACP session workspace not found: ${params.sessionId}`);
21333
22274
  }
21334
- const content = await fs11.readFile(params.path, "utf8");
22275
+ const filePath = await resolveAcpWorkspacePath(session.cwd, params.path);
22276
+ this.emit("fs-operation", {
22277
+ operation: "fs.readTextFile",
22278
+ sessionId: params.sessionId,
22279
+ path: filePath
22280
+ });
22281
+ const content = await fs13.readFile(filePath, "utf8");
21335
22282
  if (params.line === void 0 && params.limit === void 0) {
21336
22283
  return { content };
21337
22284
  }
@@ -21341,11 +22288,18 @@ ${input.prompt}` : input.prompt;
21341
22288
  return { content: lines.slice(start, end).join("\n") };
21342
22289
  }
21343
22290
  async writeTextFile(params) {
21344
- if (!path14.isAbsolute(params.path)) {
21345
- throw new Error("ACP file paths must be absolute.");
22291
+ const session = this.sessions.get(params.sessionId);
22292
+ if (!session) {
22293
+ throw new Error(`ACP session workspace not found: ${params.sessionId}`);
21346
22294
  }
21347
- await fs11.mkdir(path14.dirname(params.path), { recursive: true });
21348
- await fs11.writeFile(params.path, params.content, "utf8");
22295
+ const filePath = await resolveAcpWorkspacePath(session.cwd, params.path);
22296
+ this.emit("fs-operation", {
22297
+ operation: "fs.writeTextFile",
22298
+ sessionId: params.sessionId,
22299
+ path: filePath
22300
+ });
22301
+ await fs13.mkdir(path16.dirname(filePath), { recursive: true });
22302
+ await fs13.writeFile(filePath, params.content, "utf8");
21349
22303
  return {};
21350
22304
  }
21351
22305
  async requireContext() {
@@ -21356,9 +22310,207 @@ ${input.prompt}` : input.prompt;
21356
22310
  return this.context;
21357
22311
  }
21358
22312
  applyAgentCapabilities(capabilities) {
21359
- this.capabilities.sessions.list = Boolean(capabilities?.sessionCapabilities?.list);
21360
- this.capabilities.sessions.importLocal = false;
21361
- this.capabilities.branching.fork = false;
22313
+ applyNegotiatedAcpCapabilities(this.capabilities, capabilities);
22314
+ }
22315
+ resetCapabilities() {
22316
+ const baseline = cloneCapabilities();
22317
+ for (const section of Object.keys(baseline)) {
22318
+ Object.assign(this.capabilities[section], baseline[section]);
22319
+ }
22320
+ }
22321
+ goalFromSessionInfoUpdate(providerSessionId, update) {
22322
+ const meta = isRecord13(update._meta) ? update._meta : null;
22323
+ if (!meta || !Object.hasOwn(meta, "goal")) {
22324
+ return void 0;
22325
+ }
22326
+ if (meta.goal === null) {
22327
+ return null;
22328
+ }
22329
+ if (!isRecord13(meta.goal)) {
22330
+ return void 0;
22331
+ }
22332
+ const objective = typeof meta.goal.objective === "string" ? meta.goal.objective.trim() : "";
22333
+ if (!objective) {
22334
+ return void 0;
22335
+ }
22336
+ return {
22337
+ providerSessionId,
22338
+ objective,
22339
+ status: typeof meta.goal.status === "string" ? meta.goal.status : "active",
22340
+ tokenBudget: typeof meta.goal.tokenBudget === "number" ? meta.goal.tokenBudget : null,
22341
+ tokensUsed: typeof meta.goal.tokensUsed === "number" ? meta.goal.tokensUsed : 0,
22342
+ timeUsedSeconds: typeof meta.goal.timeUsedSeconds === "number" ? meta.goal.timeUsedSeconds : 0,
22343
+ createdAt: typeof meta.goal.createdAt === "number" ? meta.goal.createdAt : Date.now(),
22344
+ updatedAt: typeof meta.goal.updatedAt === "number" ? meta.goal.updatedAt : Date.now(),
22345
+ rawGoal: structuredClone(meta.goal)
22346
+ };
22347
+ }
22348
+ async invokeAcpGoal(providerSessionId, action) {
22349
+ if (!this.extensionRegistry.supports("acp.goal", 1, action.action)) {
22350
+ throw new AgentRuntimeError(
22351
+ "The selected ACP agent does not expose goal control.",
22352
+ "acp",
22353
+ "request_failed"
22354
+ );
22355
+ }
22356
+ const operationId = randomUUID4();
22357
+ await this.extensionRegistry.invoke({
22358
+ extensionId: "acp.goal",
22359
+ extensionVersion: 1,
22360
+ method: action.action,
22361
+ operationId,
22362
+ idempotencyKey: `${providerSessionId}:goal:${operationId}`,
22363
+ params: { providerSessionId, ...action },
22364
+ timeoutMs: 18e4
22365
+ });
22366
+ }
22367
+ async runControlPrompt(providerSessionId, prompt, signal) {
22368
+ const state = this.sessions.get(providerSessionId);
22369
+ if (!state || state.activeMapper) {
22370
+ throw new AgentRuntimeError(
22371
+ "ACP control prompt requires an idle loaded session.",
22372
+ "acp",
22373
+ "request_failed"
22374
+ );
22375
+ }
22376
+ let startedTurnId = null;
22377
+ let cleanupCompletion;
22378
+ const completed = new Promise(
22379
+ (resolve, reject) => {
22380
+ const cleanup = () => {
22381
+ this.off("event", onEvent);
22382
+ signal.removeEventListener("abort", onAbort);
22383
+ };
22384
+ const onEvent = (event) => {
22385
+ if (event.type === "turn.completed" && event.providerSessionId === providerSessionId && (!startedTurnId || event.turn.providerTurnId === startedTurnId)) {
22386
+ cleanup();
22387
+ resolve(event);
22388
+ }
22389
+ };
22390
+ const onAbort = () => {
22391
+ cleanup();
22392
+ reject(signal.reason ?? new Error("ACP control prompt cancelled."));
22393
+ };
22394
+ cleanupCompletion = cleanup;
22395
+ this.on("event", onEvent);
22396
+ signal.addEventListener("abort", onAbort, { once: true });
22397
+ }
22398
+ );
22399
+ try {
22400
+ const turn = await this.startTurn({
22401
+ providerSessionId,
22402
+ prompt,
22403
+ hidden: true
22404
+ });
22405
+ startedTurnId = turn.providerTurnId;
22406
+ const event = await completed;
22407
+ if (event.turn.status === "failed") {
22408
+ throw new Error(event.turn.error?.message ?? "ACP control prompt failed.");
22409
+ }
22410
+ return { providerTurnId: event.turn.providerTurnId, status: event.turn.status };
22411
+ } catch (error) {
22412
+ cleanupCompletion();
22413
+ throw error;
22414
+ }
22415
+ }
22416
+ registerNegotiatedExtensions(response) {
22417
+ const snapshot = snapshotAcpInitializeResponse(response);
22418
+ if (!snapshot || !this.context) {
22419
+ return;
22420
+ }
22421
+ const wireTransport = {
22422
+ request: (method, params, signal) => this.context.request(method, params, {
22423
+ cancellationSignal: signal
22424
+ })
22425
+ };
22426
+ for (const descriptor of snapshot.harnessExtensions) {
22427
+ this.extensionRegistry.register({
22428
+ ownerId: "acp-agent",
22429
+ descriptor,
22430
+ transport: wireTransport
22431
+ });
22432
+ }
22433
+ if (snapshot.legacyExtensions.steering?.supported) {
22434
+ this.extensionRegistry.register({
22435
+ ownerId: "acp-agent",
22436
+ descriptor: {
22437
+ id: "acp.steering",
22438
+ version: 1,
22439
+ stability: "experimental",
22440
+ methods: ["steer"],
22441
+ events: []
22442
+ },
22443
+ transport: wireTransport,
22444
+ wireMethods: { steer: "_session/steering" },
22445
+ paramMappers: {
22446
+ steer: (envelope) => {
22447
+ const params = isRecord13(envelope.params) ? envelope.params : {};
22448
+ return {
22449
+ sessionId: params.providerSessionId,
22450
+ prompt: params.prompt
22451
+ };
22452
+ }
22453
+ },
22454
+ capabilityPatch: { turns: { steer: true } }
22455
+ });
22456
+ }
22457
+ const goal = snapshot.legacyExtensions.goal;
22458
+ const goalVersion = typeof goal?.version === "number" ? goal.version : goal?.version === "1" ? 1 : null;
22459
+ if (goal?.controlMethod && goalVersion === 1 && goal.actions.includes("set") && goal.actions.includes("clear")) {
22460
+ const actions = goal.actions.filter((action) => ["set", "pause", "resume", "clear"].includes(action));
22461
+ this.extensionRegistry.register({
22462
+ ownerId: "acp-agent",
22463
+ descriptor: {
22464
+ id: "acp.goal",
22465
+ version: goalVersion,
22466
+ stability: "experimental",
22467
+ methods: actions,
22468
+ events: []
22469
+ },
22470
+ transport: wireTransport,
22471
+ wireMethods: Object.fromEntries(actions.map((action) => [action, goal.controlMethod])),
22472
+ paramMappers: Object.fromEntries(actions.map((action) => [
22473
+ action,
22474
+ (envelope) => {
22475
+ const params = isRecord13(envelope.params) ? envelope.params : {};
22476
+ return {
22477
+ sessionId: params.providerSessionId,
22478
+ action,
22479
+ ...action === "set" ? { objective: params.objective } : {}
22480
+ };
22481
+ }
22482
+ ])),
22483
+ capabilityPatch: { controls: { goals: true } }
22484
+ });
22485
+ }
22486
+ if (snapshot.agentInfo?.name === "@agentclientprotocol/codex-acp") {
22487
+ this.extensionRegistry.register({
22488
+ ownerId: "acp-agent",
22489
+ descriptor: {
22490
+ id: "codex.control",
22491
+ version: 1,
22492
+ stability: "experimental",
22493
+ methods: ["compact"],
22494
+ events: []
22495
+ },
22496
+ transport: {
22497
+ request: async (_method, params, signal) => {
22498
+ const providerSessionId = isRecord13(params) ? String(params.providerSessionId ?? "") : "";
22499
+ return this.runControlPrompt(providerSessionId, "/compact", signal);
22500
+ }
22501
+ },
22502
+ paramMappers: {
22503
+ compact: (envelope) => ({
22504
+ providerSessionId: isRecord13(envelope.params) ? envelope.params.providerSessionId : null
22505
+ })
22506
+ },
22507
+ capabilityPatch: { turns: { compact: true } }
22508
+ });
22509
+ }
22510
+ const effective = this.extensionRegistry.effectiveCapabilities(this.capabilities);
22511
+ for (const section of Object.keys(effective)) {
22512
+ Object.assign(this.capabilities[section], effective[section]);
22513
+ }
21362
22514
  }
21363
22515
  withStartupTimeout(promise) {
21364
22516
  const timeoutMs = this.options.startupTimeoutMs ?? 1e4;
@@ -21381,6 +22533,8 @@ ${input.prompt}` : input.prompt;
21381
22533
  }
21382
22534
  markFailed(error) {
21383
22535
  const message = errorMessage3(error);
22536
+ this.settleActiveTurns("failed", message);
22537
+ this.cancelPendingPermissions();
21384
22538
  this.status = {
21385
22539
  ...this.status,
21386
22540
  state: "failed",
@@ -21389,14 +22543,49 @@ ${input.prompt}` : input.prompt;
21389
22543
  this.installation.lastError = message;
21390
22544
  this.emit("status", this.getStatus());
21391
22545
  }
22546
+ cancelPendingPermissions() {
22547
+ for (const [id, permission] of this.pendingPermissions) {
22548
+ clearTimeout(permission.timer);
22549
+ permission.resolve(cancelledPermission());
22550
+ this.pendingPermissions.delete(id);
22551
+ }
22552
+ }
22553
+ settleActiveTurns(status, error) {
22554
+ for (const state of this.sessions.values()) {
22555
+ const mapper = state.activeMapper;
22556
+ if (!mapper) {
22557
+ continue;
22558
+ }
22559
+ const completed = mapper.complete(status, error);
22560
+ for (const itemUpdate of completed.updates) {
22561
+ this.emitItemUpdate(state, mapper.turnId, itemUpdate);
22562
+ }
22563
+ const turn = {
22564
+ ...completed.turn,
22565
+ startedAt: state.turns.find(
22566
+ (candidate) => candidate.providerTurnId === mapper.turnId
22567
+ )?.startedAt ?? null
22568
+ };
22569
+ this.replaceTurn(state, turn);
22570
+ state.activeMapper = null;
22571
+ state.status = status;
22572
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
22573
+ this.emitRuntimeEvent({
22574
+ type: "turn.completed",
22575
+ provider: "acp",
22576
+ providerSessionId: state.providerSessionId,
22577
+ turn
22578
+ });
22579
+ }
22580
+ }
21392
22581
  emitRuntimeEvent(event) {
21393
22582
  this.emit("event", event);
21394
22583
  }
21395
22584
  };
21396
22585
 
21397
22586
  // ../../packages/acp/src/codex-environment.ts
21398
- import fs12 from "fs/promises";
21399
- import path15 from "path";
22587
+ import fs14 from "fs/promises";
22588
+ import path17 from "path";
21400
22589
 
21401
22590
  // ../../node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
21402
22591
  var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
@@ -22060,7 +23249,7 @@ async function loadCodexAcpEnvironment(codexHome, inheritedEnv = process.env, co
22060
23249
  return env;
22061
23250
  }
22062
23251
  try {
22063
- const source = await fs12.readFile(path15.join(normalizedHome, "config.toml"), "utf8");
23252
+ const source = await fs14.readFile(path17.join(normalizedHome, "config.toml"), "utf8");
22064
23253
  env.CODEX_CONFIG = JSON.stringify(parse(source));
22065
23254
  } catch {
22066
23255
  }
@@ -22113,7 +23302,12 @@ var catalogCapabilities = {
22113
23302
  };
22114
23303
  var catalogManagementSchema = {
22115
23304
  hostConfigFiles: [],
22116
- toolboxItems: [],
23305
+ toolboxItems: [
23306
+ { action: "fast", command: "/fast", label: "Fast mode" },
23307
+ { action: "compact", command: "/compact", label: "Compact context" },
23308
+ { action: "goal", command: "/goal", label: "Goal" },
23309
+ { action: "fork", command: "/fork", label: "Fork" }
23310
+ ],
22117
23311
  hookCommandTemplates: [],
22118
23312
  providerConfigFormat: "none",
22119
23313
  mcpConfigFormat: "none",
@@ -22137,12 +23331,14 @@ function decodeScopedId(value) {
22137
23331
  function scopedSession(agentId, session) {
22138
23332
  return {
22139
23333
  ...session,
23334
+ agentId,
22140
23335
  providerSessionId: encodeSessionId(agentId, session.providerSessionId)
22141
23336
  };
22142
23337
  }
22143
23338
  function scopedSummary(agentId, session) {
22144
23339
  return {
22145
23340
  ...session,
23341
+ agentId,
22146
23342
  providerSessionId: encodeSessionId(agentId, session.providerSessionId)
22147
23343
  };
22148
23344
  }
@@ -22151,7 +23347,7 @@ function delegatedSessionInput(input) {
22151
23347
  delete delegated.agentId;
22152
23348
  return delegated;
22153
23349
  }
22154
- var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
23350
+ var AcpCatalogRuntimeAdapter = class extends EventEmitter9 {
22155
23351
  constructor(options = {}) {
22156
23352
  super();
22157
23353
  this.options = options;
@@ -22167,8 +23363,8 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22167
23363
  managementSchema = catalogManagementSchema;
22168
23364
  installation = {
22169
23365
  packageName: null,
22170
- installed: false,
22171
- installedVersion: null,
23366
+ installed: true,
23367
+ installedVersion: "Built in \xB7 0 ACP agents ready",
22172
23368
  latestVersion: null,
22173
23369
  installCommand: null,
22174
23370
  updateCommand: null,
@@ -22178,6 +23374,11 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22178
23374
  catalog;
22179
23375
  agents = /* @__PURE__ */ new Map();
22180
23376
  modelCache = /* @__PURE__ */ new Map();
23377
+ operationalMetrics = {
23378
+ sessionStartFailures: 0,
23379
+ resumeFailures: 0,
23380
+ capabilityProbeFailures: 0
23381
+ };
22181
23382
  status = {
22182
23383
  state: "stopped",
22183
23384
  transport: "stdio",
@@ -22186,7 +23387,10 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22186
23387
  restartCount: 0
22187
23388
  };
22188
23389
  getStatus() {
22189
- return { ...this.status };
23390
+ return {
23391
+ ...this.status,
23392
+ operationalMetrics: { ...this.operationalMetrics }
23393
+ };
22190
23394
  }
22191
23395
  async start() {
22192
23396
  this.status = {
@@ -22201,6 +23405,7 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22201
23405
  async stop() {
22202
23406
  await Promise.allSettled([...this.agents.values()].map((agent) => agent.stop()));
22203
23407
  this.agents.clear();
23408
+ this.recomputeAgentCapabilities();
22204
23409
  this.status = {
22205
23410
  ...this.status,
22206
23411
  state: "stopped",
@@ -22228,21 +23433,58 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22228
23433
  }));
22229
23434
  }
22230
23435
  async listModelsForAgent(agentId, cwd) {
22231
- const cacheKey = `${agentId}\0${cwd}`;
22232
- const cached = this.modelCache.get(cacheKey);
22233
- if (cached && Date.now() - cached.at < 3e4) {
22234
- return cached.models;
22235
- }
22236
- const agent = await this.agentFor(agentId);
22237
- let models = await agent.inspectModelOptions(cwd);
22238
- if (models.length === 1 && models[0]?.model === "default") {
22239
- const commandModels = await this.catalog.listCommandModels(agentId);
22240
- if (commandModels.length > 0) {
22241
- models = commandModels;
23436
+ try {
23437
+ const cacheKey = `${agentId}\0${cwd}`;
23438
+ const cached = this.modelCache.get(cacheKey);
23439
+ if (cached && Date.now() - cached.at < 3e4) {
23440
+ return cached.models;
23441
+ }
23442
+ const agent = await this.agentFor(agentId);
23443
+ let models = await agent.inspectModelOptions(cwd);
23444
+ if (agent.capabilities.controls.performanceMode) {
23445
+ models = models.map((model) => ({
23446
+ ...model,
23447
+ supportsPerformanceMode: true
23448
+ }));
23449
+ }
23450
+ if (models.length === 1 && models[0]?.model === "default") {
23451
+ const commandModels = await this.catalog.listCommandModels(agentId);
23452
+ if (commandModels.length > 0) {
23453
+ models = commandModels;
23454
+ }
22242
23455
  }
23456
+ this.modelCache.set(cacheKey, { at: Date.now(), models });
23457
+ return models;
23458
+ } catch (error) {
23459
+ this.operationalMetrics.capabilityProbeFailures += 1;
23460
+ throw error;
22243
23461
  }
22244
- this.modelCache.set(cacheKey, { at: Date.now(), models });
22245
- return models;
23462
+ }
23463
+ async getAgentCapabilitySnapshot(agentId) {
23464
+ const entry = (await this.refreshCatalog()).find((candidate) => candidate.id === agentId);
23465
+ if (!entry) {
23466
+ throw new AgentRuntimeError(`Unknown ACP agent: ${agentId}`, "acp", "request_failed");
23467
+ }
23468
+ if (entry.availability !== "ready") {
23469
+ return snapshotAcpAgentCapabilities({
23470
+ agentId,
23471
+ availability: entry.availability,
23472
+ effectiveCapabilities: this.capabilities
23473
+ });
23474
+ }
23475
+ const agent = await this.agentFor(agentId);
23476
+ return snapshotAcpAgentCapabilities({
23477
+ agentId,
23478
+ availability: entry.availability,
23479
+ negotiated: agent.getProtocolSnapshot(),
23480
+ effectiveCapabilities: agent.capabilities
23481
+ });
23482
+ }
23483
+ getScopedCapabilities(input) {
23484
+ const sessionOwner = input.providerSessionId ? decodeScopedId(input.providerSessionId) : null;
23485
+ const agentId = sessionOwner?.agentId ?? input.agentId ?? null;
23486
+ const child = agentId ? this.agents.get(agentId) : null;
23487
+ return structuredClone(child?.capabilities ?? catalogCapabilities);
22246
23488
  }
22247
23489
  async installModel(modelId) {
22248
23490
  await this.catalog.installAdapter(modelId);
@@ -22256,6 +23498,23 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22256
23498
  );
22257
23499
  return sessions.flat();
22258
23500
  }
23501
+ async listImportSessions(agentId) {
23502
+ const entries = agentId ? (await this.refreshCatalog()).filter((entry) => entry.id === agentId) : [];
23503
+ const sessions = await Promise.all(
23504
+ entries.map(async (entry) => {
23505
+ if (entry.availability !== "ready") {
23506
+ return [];
23507
+ }
23508
+ try {
23509
+ const agent = await this.agentFor(entry.id);
23510
+ return (await agent.listSessions()).map((session) => scopedSummary(entry.id, session));
23511
+ } catch {
23512
+ return [];
23513
+ }
23514
+ })
23515
+ );
23516
+ return sessions.flat();
23517
+ }
22259
23518
  async listLoadedSessions() {
22260
23519
  const sessions = await Promise.all(
22261
23520
  [...this.agents.entries()].map(
@@ -22280,25 +23539,43 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22280
23539
  if (!agentId) {
22281
23540
  throw new AgentRuntimeError("Select an ACP agent before creating the thread.", "acp");
22282
23541
  }
22283
- const agent = await this.agentFor(agentId);
22284
- const response = await agent.startSession(delegatedSessionInput(input));
23542
+ let agent;
23543
+ let response;
23544
+ try {
23545
+ agent = await this.agentFor(agentId);
23546
+ response = await agent.startSession(delegatedSessionInput(input));
23547
+ } catch (error) {
23548
+ this.operationalMetrics.sessionStartFailures += 1;
23549
+ throw error;
23550
+ }
23551
+ const probedDefaultModel = this.modelCache.get(`${agentId}\0${input.cwd}`)?.models.find((model) => model.isDefault)?.model ?? null;
23552
+ const resolvedModel = response.model && response.model !== "default" ? response.model : input.model !== "default" ? input.model : probedDefaultModel ?? input.model;
23553
+ this.refreshAgentSessionCapabilities(agentId, agent);
22285
23554
  const session = scopedSession(agentId, response.session);
22286
23555
  return {
22287
23556
  ...response,
22288
23557
  agentId,
22289
23558
  providerSessionId: session.providerSessionId,
22290
- model: response.model ?? input.model,
23559
+ model: resolvedModel,
22291
23560
  reasoningEffort: response.reasoningEffort ?? input.reasoningEffort ?? null,
22292
23561
  session
22293
23562
  };
22294
23563
  }
22295
23564
  async resumeSession(input) {
22296
23565
  const owner = this.requireSessionOwner(input.providerSessionId);
22297
- const agent = await this.agentFor(owner.agentId);
22298
- const response = await agent.resumeSession({
22299
- ...input,
22300
- providerSessionId: owner.rawId
22301
- });
23566
+ let agent;
23567
+ let response;
23568
+ try {
23569
+ agent = await this.agentFor(owner.agentId);
23570
+ response = await agent.resumeSession({
23571
+ ...input,
23572
+ providerSessionId: owner.rawId
23573
+ });
23574
+ } catch (error) {
23575
+ this.operationalMetrics.resumeFailures += 1;
23576
+ throw error;
23577
+ }
23578
+ this.refreshAgentSessionCapabilities(owner.agentId, agent);
22302
23579
  const session = scopedSession(owner.agentId, response.session);
22303
23580
  return {
22304
23581
  ...response,
@@ -22324,15 +23601,63 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22324
23601
  providerSessionId: owner.rawId
22325
23602
  });
22326
23603
  }
23604
+ async sendInput(input) {
23605
+ const owner = this.requireSessionOwner(input.providerSessionId);
23606
+ const agent = await this.agentFor(owner.agentId);
23607
+ return agent.sendInput({
23608
+ ...input,
23609
+ providerSessionId: owner.rawId
23610
+ });
23611
+ }
23612
+ async compactSession(providerSessionId) {
23613
+ const owner = this.requireSessionOwner(providerSessionId);
23614
+ const agent = await this.agentFor(owner.agentId);
23615
+ return agent.compactSession(owner.rawId);
23616
+ }
23617
+ async forkSession(input) {
23618
+ const owner = this.requireSessionOwner(input.providerSessionId);
23619
+ const agent = await this.agentFor(owner.agentId);
23620
+ return scopedSession(owner.agentId, await agent.forkSession({
23621
+ ...input,
23622
+ providerSessionId: owner.rawId
23623
+ }));
23624
+ }
23625
+ async getGoal(providerSessionId) {
23626
+ const owner = this.requireSessionOwner(providerSessionId);
23627
+ const agent = await this.agentFor(owner.agentId);
23628
+ return agent.getGoal(owner.rawId);
23629
+ }
23630
+ async setGoal(input) {
23631
+ const owner = this.requireSessionOwner(input.providerSessionId);
23632
+ const agent = await this.agentFor(owner.agentId);
23633
+ const goal = await agent.setGoal({
23634
+ ...input,
23635
+ providerSessionId: owner.rawId
23636
+ });
23637
+ return {
23638
+ ...goal,
23639
+ providerSessionId: encodeSessionId(owner.agentId, goal.providerSessionId)
23640
+ };
23641
+ }
23642
+ async clearGoal(providerSessionId) {
23643
+ const owner = this.requireSessionOwner(providerSessionId);
23644
+ const agent = await this.agentFor(owner.agentId);
23645
+ return agent.clearGoal(owner.rawId);
23646
+ }
22327
23647
  mapProviderRequest(request, options) {
22328
23648
  const owner = decodeScopedId(request.id);
22329
23649
  const agent = owner ? this.agents.get(owner.agentId) : null;
22330
23650
  if (!owner || !agent) {
22331
23651
  return null;
22332
23652
  }
23653
+ const scopedProviderSessionId = request.params && typeof request.params === "object" ? String(request.params.sessionId ?? "") : "";
23654
+ const sessionOwner = decodeScopedId(scopedProviderSessionId);
23655
+ if (!sessionOwner || sessionOwner.agentId !== owner.agentId || !sessionOwner.rawId) {
23656
+ return null;
23657
+ }
22333
23658
  const params = request.params && typeof request.params === "object" ? {
22334
23659
  ...request.params,
22335
- sessionId: owner.rawId
23660
+ sessionId: sessionOwner.rawId
22336
23661
  } : request.params;
22337
23662
  const mapping = agent.mapProviderRequest({
22338
23663
  ...request,
@@ -22377,8 +23702,8 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22377
23702
  const entries = await this.catalog.list({ force });
22378
23703
  const ready = entries.filter((entry) => entry.availability === "ready");
22379
23704
  const baseInstalled = entries.filter((entry) => entry.availability !== "base_missing");
22380
- this.installation.installed = baseInstalled.length > 0;
22381
- this.installation.installedVersion = ready.length > 0 ? `${ready.length} ACP agent${ready.length === 1 ? "" : "s"} ready` : null;
23705
+ this.installation.installed = true;
23706
+ this.installation.installedVersion = `Built in \xB7 ${ready.length} ACP agent${ready.length === 1 ? "" : "s"} ready`;
22382
23707
  this.installation.lastError = ready.length > 0 ? null : baseInstalled.length > 0 ? "ACP adapters or native ACP servers are not ready." : "No supported base agent was detected.";
22383
23708
  this.status = {
22384
23709
  ...this.status,
@@ -22393,6 +23718,7 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22393
23718
  const existing = this.agents.get(agentId);
22394
23719
  if (existing) {
22395
23720
  await existing.start();
23721
+ this.mergeAgentCapabilities(existing);
22396
23722
  return existing;
22397
23723
  }
22398
23724
  const entry = (await this.catalog.list()).find((candidate) => candidate.id === agentId);
@@ -22411,12 +23737,41 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22411
23737
  this.agents.set(agentId, agent);
22412
23738
  try {
22413
23739
  await agent.start();
23740
+ this.mergeAgentCapabilities(agent);
22414
23741
  return agent;
22415
23742
  } catch (error) {
22416
23743
  this.agents.delete(agentId);
22417
23744
  throw error;
22418
23745
  }
22419
23746
  }
23747
+ mergeAgentCapabilities(agent) {
23748
+ void agent;
23749
+ this.recomputeAgentCapabilities();
23750
+ }
23751
+ refreshAgentSessionCapabilities(agentId, agent) {
23752
+ for (const key of this.modelCache.keys()) {
23753
+ if (key.startsWith(`${agentId}\0`)) {
23754
+ this.modelCache.delete(key);
23755
+ }
23756
+ }
23757
+ this.mergeAgentCapabilities(agent);
23758
+ }
23759
+ recomputeAgentCapabilities() {
23760
+ const next = structuredClone(catalogCapabilities);
23761
+ for (const child of this.agents.values()) {
23762
+ next.turns.steer ||= child.capabilities.turns.steer;
23763
+ next.turns.compact ||= child.capabilities.turns.compact;
23764
+ next.branching.fork ||= child.capabilities.branching.fork;
23765
+ next.controls.goals ||= child.capabilities.controls.goals;
23766
+ next.controls.performanceMode ||= child.capabilities.controls.performanceMode;
23767
+ next.management.mcpStatus ||= child.capabilities.management.mcpStatus;
23768
+ next.management.skills ||= child.capabilities.management.skills;
23769
+ next.management.hooks ||= child.capabilities.management.hooks;
23770
+ }
23771
+ for (const section of Object.keys(next)) {
23772
+ Object.assign(this.capabilities[section], next[section]);
23773
+ }
23774
+ }
22420
23775
  async createAgent(entry) {
22421
23776
  const env = entry.id === "codex" ? await loadCodexAcpEnvironment(
22422
23777
  this.options.codexHome,
@@ -22430,9 +23785,16 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22430
23785
  ...this.options.clientInfo !== void 0 ? { clientInfo: this.options.clientInfo } : {}
22431
23786
  });
22432
23787
  agent.on("event", (event) => {
23788
+ const providerSessionId = encodeSessionId(entry.id, event.providerSessionId);
22433
23789
  this.emit("event", {
22434
23790
  ...event,
22435
- providerSessionId: encodeSessionId(entry.id, event.providerSessionId)
23791
+ providerSessionId,
23792
+ ...event.type === "goal.updated" ? {
23793
+ goal: {
23794
+ ...event.goal,
23795
+ providerSessionId
23796
+ }
23797
+ } : {}
22436
23798
  });
22437
23799
  });
22438
23800
  agent.on("provider-request", (request) => {
@@ -22467,9 +23829,9 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter8 {
22467
23829
  };
22468
23830
 
22469
23831
  // src/e2e-fake-runtime.ts
22470
- import { EventEmitter as EventEmitter9 } from "events";
23832
+ import { EventEmitter as EventEmitter10 } from "events";
22471
23833
  import { mkdir, writeFile } from "fs/promises";
22472
- import path16 from "path";
23834
+ import path18 from "path";
22473
23835
  var provider = "claude";
22474
23836
  var firstDelta = "IOS_STREAM_DELTA_READY";
22475
23837
  var secondDelta = " IOS_STREAM_COMPLETED";
@@ -22481,7 +23843,7 @@ var historyDetailPromptMarker = "IOS_HISTORY_DETAIL";
22481
23843
  var historyPagePromptMarker = "IOS_HISTORY_PAGE";
22482
23844
  var imageAssetPromptMarker = "IOS_IMAGE_ASSET";
22483
23845
  var imageAssetPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
22484
- var E2EFakeRuntime = class extends EventEmitter9 {
23846
+ var E2EFakeRuntime = class extends EventEmitter10 {
22485
23847
  provider = provider;
22486
23848
  displayName = "E2E Fake Runtime";
22487
23849
  description = "Deterministic runtime for live iOS and web end-to-end tests.";
@@ -22891,8 +24253,8 @@ var E2EFakeRuntime = class extends EventEmitter9 {
22891
24253
  }
22892
24254
  if (input.prompt.includes(imageAssetPromptMarker)) {
22893
24255
  const relativeImagePath = `./.temp/threads/${providerTurnId}/ios-webview-image.png`;
22894
- const absoluteImagePath = path16.join(session.cwd, relativeImagePath);
22895
- await mkdir(path16.dirname(absoluteImagePath), { recursive: true });
24256
+ const absoluteImagePath = path18.join(session.cwd, relativeImagePath);
24257
+ await mkdir(path18.dirname(absoluteImagePath), { recursive: true });
22896
24258
  await writeFile(absoluteImagePath, Buffer.from(imageAssetPngBase64, "base64"));
22897
24259
  const imageItem = {
22898
24260
  id: `${providerTurnId}:image-asset`,
@@ -23029,20 +24391,20 @@ var E2EFakeRuntime = class extends EventEmitter9 {
23029
24391
  return [];
23030
24392
  }
23031
24393
  mapProviderRequest(request) {
23032
- if (request.provider !== provider || !isRecord13(request.params)) {
24394
+ if (request.provider !== provider || !isRecord14(request.params)) {
23033
24395
  return null;
23034
24396
  }
23035
- const providerSessionId = stringValue4(request.params.providerSessionId);
23036
- const providerTurnId = stringValue4(request.params.providerTurnId);
24397
+ const providerSessionId = stringValue5(request.params.providerSessionId);
24398
+ const providerTurnId = stringValue5(request.params.providerTurnId);
23037
24399
  if (!providerSessionId) {
23038
24400
  return null;
23039
24401
  }
23040
24402
  if (request.method === "item/commandExecution/requestApproval") {
23041
24403
  const requestId = String(request.id);
23042
24404
  const description = [
23043
- stringValue4(request.params.reason),
23044
- stringValue4(request.params.command) ? `Command: ${stringValue4(request.params.command)}` : null,
23045
- stringValue4(request.params.cwd) ? `CWD: ${stringValue4(request.params.cwd)}` : null
24405
+ stringValue5(request.params.reason),
24406
+ stringValue5(request.params.command) ? `Command: ${stringValue5(request.params.command)}` : null,
24407
+ stringValue5(request.params.cwd) ? `CWD: ${stringValue5(request.params.cwd)}` : null
23046
24408
  ].filter(Boolean).join("\n");
23047
24409
  return {
23048
24410
  providerRequestId: request.id,
@@ -23057,7 +24419,7 @@ var E2EFakeRuntime = class extends EventEmitter9 {
23057
24419
  title: "Command approval required",
23058
24420
  description: description || "iOS E2E approval request.",
23059
24421
  turnId: providerTurnId,
23060
- itemId: stringValue4(request.params.itemId),
24422
+ itemId: stringValue5(request.params.itemId),
23061
24423
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
23062
24424
  questions: [
23063
24425
  {
@@ -23091,7 +24453,7 @@ var E2EFakeRuntime = class extends EventEmitter9 {
23091
24453
  title: "Mode",
23092
24454
  description: "Which iOS E2E path should continue?",
23093
24455
  turnId: providerTurnId,
23094
- itemId: stringValue4(request.params.toolUseId),
24456
+ itemId: stringValue5(request.params.toolUseId),
23095
24457
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
23096
24458
  questions: [
23097
24459
  {
@@ -23179,10 +24541,10 @@ function isE2EFakeRuntimeEnabled(env = process.env) {
23179
24541
  const value = env.REMOTE_CODEX_E2E_FAKE_RUNTIME;
23180
24542
  return value ? ["1", "true", "yes", "on"].includes(value.toLowerCase()) : false;
23181
24543
  }
23182
- function isRecord13(value) {
24544
+ function isRecord14(value) {
23183
24545
  return typeof value === "object" && value !== null && !Array.isArray(value);
23184
24546
  }
23185
- function stringValue4(value) {
24547
+ function stringValue5(value) {
23186
24548
  return typeof value === "string" ? value : null;
23187
24549
  }
23188
24550
  function deltasForPrompt(prompt) {
@@ -23279,8 +24641,8 @@ function createOpenCodeRuntime(config) {
23279
24641
  }
23280
24642
 
23281
24643
  // src/event-bus.ts
23282
- import { EventEmitter as EventEmitter10 } from "events";
23283
- var SupervisorEventBus = class extends EventEmitter10 {
24644
+ import { EventEmitter as EventEmitter11 } from "events";
24645
+ var SupervisorEventBus = class extends EventEmitter11 {
23284
24646
  emitThreadEvent(event) {
23285
24647
  this.emit("thread-event", event);
23286
24648
  }
@@ -23302,7 +24664,7 @@ var SupervisorEventBus = class extends EventEmitter10 {
23302
24664
  };
23303
24665
 
23304
24666
  // src/thread-service.ts
23305
- import fs17 from "fs/promises";
24667
+ import fs19 from "fs/promises";
23306
24668
 
23307
24669
  // src/thread-auxiliary-state-store.ts
23308
24670
  var ThreadAuxiliaryStateStore = class {
@@ -23544,7 +24906,8 @@ var ThreadGoalCoordinator = class {
23544
24906
  });
23545
24907
  }
23546
24908
  const runtime = this.callbacks.runtimeForProvider(record2.provider);
23547
- if (!runtime.setGoal || !runtime.capabilities.controls.goals) {
24909
+ const capabilities = await this.callbacks.capabilitiesForRecord(record2);
24910
+ if (!runtime.setGoal || !capabilities.controls.goals) {
23548
24911
  throw new HttpError(409, {
23549
24912
  code: "conflict",
23550
24913
  message: "This backend does not support goals."
@@ -23621,7 +24984,8 @@ var ThreadGoalCoordinator = class {
23621
24984
  });
23622
24985
  }
23623
24986
  const runtime = this.callbacks.runtimeForProvider(record2.provider);
23624
- if (!runtime.clearGoal || !runtime.capabilities.controls.goals) {
24987
+ const capabilities = await this.callbacks.capabilitiesForRecord(record2);
24988
+ if (!runtime.clearGoal || !capabilities.controls.goals) {
23625
24989
  throw new HttpError(409, {
23626
24990
  code: "conflict",
23627
24991
  message: "This backend does not support goals."
@@ -23647,7 +25011,8 @@ var ThreadGoalCoordinator = class {
23647
25011
  await this.ensureGoalsFeatureEnabled(record2.provider);
23648
25012
  }
23649
25013
  const runtime = this.callbacks.runtimeForProvider(record2.provider);
23650
- if (!runtime.getGoal || !runtime.capabilities.controls.goals) {
25014
+ const capabilities = await this.callbacks.capabilitiesForRecord(record2);
25015
+ if (!runtime.getGoal || !capabilities.controls.goals) {
23651
25016
  return null;
23652
25017
  }
23653
25018
  const goal = await runtime.getGoal(record2.providerSessionId);
@@ -24609,7 +25974,7 @@ function agentMessageMatchScore(finalText, liveText) {
24609
25974
  }
24610
25975
 
24611
25976
  // src/thread-usage-accounting.ts
24612
- function isRecord14(value) {
25977
+ function isRecord15(value) {
24613
25978
  return typeof value === "object" && value !== null && !Array.isArray(value);
24614
25979
  }
24615
25980
  function numberOrNull2(value) {
@@ -24655,11 +26020,11 @@ function computeContextRemainingPercent(tokensInContextWindow, contextWindow) {
24655
26020
  return clampPercentage(Math.round(remaining / contextWindow * 100));
24656
26021
  }
24657
26022
  function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
24658
- const tokenUsage = isRecord14(payload) ? payload : null;
26023
+ const tokenUsage = isRecord15(payload) ? payload : null;
24659
26024
  const modelContextWindow = numberOrNull2(
24660
26025
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
24661
26026
  ) ?? contextWindowForModel(model);
24662
- const lastUsage = isRecord14(tokenUsage?.last) ? tokenUsage.last : null;
26027
+ const lastUsage = isRecord15(tokenUsage?.last) ? tokenUsage.last : null;
24663
26028
  const tokensInContextWindow = numberOrNull2(
24664
26029
  lastUsage?.totalTokens ?? lastUsage?.total_tokens
24665
26030
  );
@@ -24679,7 +26044,7 @@ function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (
24679
26044
  }
24680
26045
  function mergeThreadContextUsageFromPayload(current, payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
24681
26046
  const next = buildThreadContextUsageFromPayload(payload, model, timestamp);
24682
- const payloadRecord = isRecord14(payload) ? payload : null;
26047
+ const payloadRecord = isRecord15(payload) ? payload : null;
24683
26048
  const reportedModelContextWindow = numberOrNull2(
24684
26049
  payloadRecord?.modelContextWindow ?? payloadRecord?.model_context_window
24685
26050
  );
@@ -24705,11 +26070,11 @@ function shouldResetThreadContextUsageForTurnStart(current) {
24705
26070
  return current?.availability !== "available";
24706
26071
  }
24707
26072
  function buildTurnTokenBreakdown(payload) {
24708
- const usage = isRecord14(payload) ? payload : null;
24709
- const inputDetails = isRecord14(
26073
+ const usage = isRecord15(payload) ? payload : null;
26074
+ const inputDetails = isRecord15(
24710
26075
  usage?.inputTokensDetails ?? usage?.input_tokens_details
24711
26076
  ) ? usage?.inputTokensDetails ?? usage?.input_tokens_details : null;
24712
- const cache = isRecord14(usage?.cache) ? usage.cache : null;
26077
+ const cache = isRecord15(usage?.cache) ? usage.cache : null;
24713
26078
  const totalTokens2 = numberOrNull2(usage?.totalTokens ?? usage?.total_tokens);
24714
26079
  const inputTokens = numberOrNull2(usage?.inputTokens ?? usage?.input_tokens);
24715
26080
  const cachedInputTokens = numberOrNull2(
@@ -24764,12 +26129,12 @@ function subtractTurnTokenBreakdowns(current, previous) {
24764
26129
  };
24765
26130
  }
24766
26131
  function parseThreadTurnTokenUsage(payload) {
24767
- const tokenUsage = isRecord14(payload) ? payload : null;
26132
+ const tokenUsage = isRecord15(payload) ? payload : null;
24768
26133
  const total = buildTurnTokenBreakdown(
24769
- isRecord14(tokenUsage?.total) ? tokenUsage.total : null
26134
+ isRecord15(tokenUsage?.total) ? tokenUsage.total : null
24770
26135
  );
24771
26136
  const last = buildTurnTokenBreakdown(
24772
- isRecord14(tokenUsage?.last) ? tokenUsage.last : null
26137
+ isRecord15(tokenUsage?.last) ? tokenUsage.last : null
24773
26138
  );
24774
26139
  const modelContextWindow = numberOrNull2(
24775
26140
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -24794,7 +26159,7 @@ function parseStoredThreadTurnTokenUsageState(value) {
24794
26159
  try {
24795
26160
  const parsed = JSON.parse(value);
24796
26161
  const baselineTotal = buildTurnTokenBreakdown(
24797
- isRecord14(parsed?.baselineTotal) ? parsed.baselineTotal : null
26162
+ isRecord15(parsed?.baselineTotal) ? parsed.baselineTotal : null
24798
26163
  );
24799
26164
  const usage = parseThreadTurnTokenUsage(parsed);
24800
26165
  const modelContextWindow = usage?.modelContextWindow ?? numberOrNull2(parsed?.modelContextWindow ?? parsed?.model_context_window);
@@ -24842,12 +26207,12 @@ function stringifyStoredThreadTurnTokenUsageState(state) {
24842
26207
  });
24843
26208
  }
24844
26209
  function buildThreadTurnTokenUsage(payload, baselineTotal, previous = null, fallbackModelContextWindow = null) {
24845
- const tokenUsage = isRecord14(payload) ? payload : null;
26210
+ const tokenUsage = isRecord15(payload) ? payload : null;
24846
26211
  const cumulativeTotal = buildTurnTokenBreakdown(
24847
- isRecord14(tokenUsage?.total) ? tokenUsage.total : null
26212
+ isRecord15(tokenUsage?.total) ? tokenUsage.total : null
24848
26213
  );
24849
26214
  const last = buildTurnTokenBreakdown(
24850
- isRecord14(tokenUsage?.last) ? tokenUsage.last : null
26215
+ isRecord15(tokenUsage?.last) ? tokenUsage.last : null
24851
26216
  );
24852
26217
  const modelContextWindow = numberOrNull2(
24853
26218
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -24941,7 +26306,7 @@ var ThreadUsageAccounting = class {
24941
26306
  input.localThreadId
24942
26307
  );
24943
26308
  const currentCumulativeTotal = buildTurnTokenBreakdown(
24944
- isRecord14(input.tokenUsage?.total) ? input.tokenUsage.total : null
26309
+ isRecord15(input.tokenUsage?.total) ? input.tokenUsage.total : null
24945
26310
  );
24946
26311
  if (currentCumulativeTotal) {
24947
26312
  this.threadCumulativeTokenUsage.set(input.localThreadId, currentCumulativeTotal);
@@ -25148,7 +26513,10 @@ var ThreadRuntimeEventProjector = class {
25148
26513
  }
25149
26514
  const pricingSnapshot = buildThreadTurnPricingSnapshot(
25150
26515
  record2.model,
25151
- callbacks.fastModeForProvider(record2.provider, record2.fastMode)
26516
+ callbacks.fastModeForProvider(record2.provider, record2.fastMode, {
26517
+ agentId: record2.agentId ?? null,
26518
+ providerSessionId: record2.providerSessionId ?? null
26519
+ })
25152
26520
  );
25153
26521
  upsertThreadTurnMetadata(db, {
25154
26522
  threadId: record2.id,
@@ -25279,7 +26647,8 @@ var ThreadRuntimeEventProjector = class {
25279
26647
  itemId: event.itemId,
25280
26648
  delta: event.delta,
25281
26649
  sequence,
25282
- createdAt
26650
+ createdAt,
26651
+ checkpoint: event.provider === "acp"
25283
26652
  });
25284
26653
  callbacks.invalidateThreadDetailCache(record2.id);
25285
26654
  callbacks.emitThreadEvent("thread.output.delta", record2.id, {
@@ -25291,6 +26660,37 @@ var ThreadRuntimeEventProjector = class {
25291
26660
  });
25292
26661
  return;
25293
26662
  }
26663
+ case "harness.extension": {
26664
+ const record2 = this.findRecordByProviderSessionId(
26665
+ event.provider,
26666
+ event.providerSessionId
26667
+ );
26668
+ if (!record2 || !event.providerTurnId || !event.providerItemId) {
26669
+ return;
26670
+ }
26671
+ const turnId = liveState.displayTurnIdForRuntimeTurn(record2.id, event.providerTurnId) ?? event.providerTurnId;
26672
+ const item = {
26673
+ id: event.providerItemId,
26674
+ kind: "other",
26675
+ text: `${event.extensionId}: ${event.event}`,
26676
+ status: "completed",
26677
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
26678
+ sequence: liveState.recordTurnItemOrder(
26679
+ record2.id,
26680
+ turnId,
26681
+ event.providerItemId
26682
+ ),
26683
+ sourceTurnId: event.providerTurnId
26684
+ };
26685
+ callbacks.persistLiveHistoryItem(record2.id, turnId, item);
26686
+ liveState.upsertLiveItem(record2.id, turnId, item);
26687
+ callbacks.invalidateThreadDetailCache(record2.id);
26688
+ callbacks.emitThreadEvent("thread.item.completed", record2.id, {
26689
+ turnId,
26690
+ item
26691
+ });
26692
+ return;
26693
+ }
25294
26694
  case "turn.completed": {
25295
26695
  const record2 = this.findRecordByProviderSessionId(
25296
26696
  event.provider,
@@ -25542,7 +26942,7 @@ var ProviderRequestCoordinator = class {
25542
26942
  const defaultMappedRequest = runtime.mapProviderRequest?.(request, {
25543
26943
  approvalMode: "guarded"
25544
26944
  });
25545
- const providerSessionIdFromParams = isRecord15(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
26945
+ const providerSessionIdFromParams = isRecord16(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
25546
26946
  const providerSessionId = defaultMappedRequest?.providerSessionId ?? (typeof providerSessionIdFromParams === "string" ? providerSessionIdFromParams : null);
25547
26947
  const record2 = providerSessionId ? this.callbacks.findRecordByProviderSessionId(request.provider, providerSessionId) : null;
25548
26948
  if (!record2) {
@@ -25755,7 +27155,7 @@ function buildRequestAnswerLines(request, input) {
25755
27155
  return `- ${question.question}: ${answers.join(", ")}`;
25756
27156
  }).filter((line) => Boolean(line));
25757
27157
  }
25758
- function isRecord15(value) {
27158
+ function isRecord16(value) {
25759
27159
  return typeof value === "object" && value !== null && !Array.isArray(value);
25760
27160
  }
25761
27161
 
@@ -25867,6 +27267,15 @@ var ThreadDetailAssembler = class {
25867
27267
  const persistedItemsByTurnIdForPatch = this.input.callbacks.listPersistedHistoryItemsByTurnId(
25868
27268
  input.localThreadId
25869
27269
  );
27270
+ if (input.record.provider === "acp") {
27271
+ remoteSession = {
27272
+ ...remoteSession,
27273
+ turns: alignHydratedAcpTurnsWithPersistedHistory(
27274
+ remoteSession.turns,
27275
+ persistedItemsByTurnIdForPatch
27276
+ )
27277
+ };
27278
+ }
25870
27279
  const activeDisplayTurnId = this.input.liveState.displayTurnIdForRuntimeTurn(
25871
27280
  input.localThreadId,
25872
27281
  input.record.providerTurnId
@@ -26117,6 +27526,101 @@ function appendPersistedFailureTurnsIfMissing(turns, persistedItemsByTurnId, met
26117
27526
  }
26118
27527
  return sortTurnsByStartedAt([...turns, ...missingFailureTurns]);
26119
27528
  }
27529
+ function normalizedConversationText(value) {
27530
+ return value?.replace(/\s+/g, " ").trim() ?? "";
27531
+ }
27532
+ function turnConversationText(items, kind) {
27533
+ return items.filter((item) => item.kind === kind).map((item) => normalizedConversationText(item.text)).filter(Boolean).join("\n");
27534
+ }
27535
+ function conversationMatchScore(turn, persistedItems) {
27536
+ const remoteUser = turnConversationText(turn.items, "userMessage");
27537
+ const persistedUser = turnConversationText(persistedItems, "userMessage");
27538
+ if (!remoteUser || !persistedUser) {
27539
+ return 0;
27540
+ }
27541
+ const userScore = remoteUser === persistedUser ? 2 : Math.min(remoteUser.length, persistedUser.length) >= 16 && (remoteUser.includes(persistedUser) || persistedUser.includes(remoteUser)) ? 1 : 0;
27542
+ if (userScore === 0) {
27543
+ return 0;
27544
+ }
27545
+ const remoteAgent = turnConversationText(turn.items, "agentMessage");
27546
+ const persistedAgent = turnConversationText(persistedItems, "agentMessage");
27547
+ if (!remoteAgent || !persistedAgent) {
27548
+ return userScore;
27549
+ }
27550
+ if (remoteAgent === persistedAgent) {
27551
+ return userScore + 4;
27552
+ }
27553
+ if (remoteAgent.includes(persistedAgent) || persistedAgent.includes(remoteAgent)) {
27554
+ return userScore + 3;
27555
+ }
27556
+ return userScore;
27557
+ }
27558
+ function historyItemMatchScore(remote, persisted) {
27559
+ if (remote.id === persisted.id) {
27560
+ return 100;
27561
+ }
27562
+ if (remote.kind !== persisted.kind) {
27563
+ return 0;
27564
+ }
27565
+ const remoteText = normalizedConversationText(remote.detailText ?? remote.text);
27566
+ const persistedText = normalizedConversationText(persisted.detailText ?? persisted.text);
27567
+ if (!remoteText || !persistedText) {
27568
+ return 0;
27569
+ }
27570
+ if (remoteText === persistedText) {
27571
+ return 50;
27572
+ }
27573
+ if ((remote.kind === "agentMessage" || remote.kind === "reasoning") && (remoteText.includes(persistedText) || persistedText.includes(remoteText))) {
27574
+ return 40;
27575
+ }
27576
+ return 0;
27577
+ }
27578
+ function alignHydratedItems(remoteItems, persistedItems, turnId) {
27579
+ const used = /* @__PURE__ */ new Set();
27580
+ return remoteItems.map((remote) => {
27581
+ const match = persistedItems.filter((persisted) => !used.has(persisted.id)).map((persisted, index) => ({
27582
+ persisted,
27583
+ index,
27584
+ score: historyItemMatchScore(remote, persisted)
27585
+ })).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.index - right.index)[0];
27586
+ if (!match) {
27587
+ return remote.sourceTurnId ? { ...remote, sourceTurnId: turnId } : remote;
27588
+ }
27589
+ used.add(match.persisted.id);
27590
+ return {
27591
+ ...remote,
27592
+ id: match.persisted.id,
27593
+ ...remote.sourceTurnId || remote.kind === "agentMessage" ? { sourceTurnId: turnId } : {}
27594
+ };
27595
+ });
27596
+ }
27597
+ function alignHydratedAcpTurnsWithPersistedHistory(turns, persistedItemsByTurnId) {
27598
+ const remoteTurnIds = new Set(turns.map((turn) => turn.providerTurnId));
27599
+ const candidates = [...persistedItemsByTurnId.entries()].filter(
27600
+ ([turnId]) => !remoteTurnIds.has(turnId)
27601
+ );
27602
+ const used = /* @__PURE__ */ new Set();
27603
+ return turns.map((turn) => {
27604
+ if (!turn.providerTurnId.startsWith("acp-hydrated:")) {
27605
+ return turn;
27606
+ }
27607
+ const match = candidates.filter(([turnId]) => !used.has(turnId)).map(([turnId, items], index) => ({
27608
+ turnId,
27609
+ items,
27610
+ index,
27611
+ score: conversationMatchScore(turn, items)
27612
+ })).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.index - right.index)[0];
27613
+ if (!match) {
27614
+ return turn;
27615
+ }
27616
+ used.add(match.turnId);
27617
+ return {
27618
+ ...turn,
27619
+ providerTurnId: match.turnId,
27620
+ items: alignHydratedItems(turn.items, match.items, match.turnId)
27621
+ };
27622
+ });
27623
+ }
26120
27624
  function appendPersistedTurnsIfMissing(turns, persistedItemsByTurnId, metadataById) {
26121
27625
  if (persistedItemsByTurnId.size === 0) {
26122
27626
  return turns;
@@ -26389,15 +27893,37 @@ var ThreadProviderRuntimeCoordinator = class {
26389
27893
  isCodexProvider(provider2) {
26390
27894
  return this.providerForRecord({ provider: provider2 }) === "codex";
26391
27895
  }
26392
- runtimeSupportsFastMode(provider2) {
26393
- return this.optionalRuntimeForProvider(provider2)?.capabilities.controls.performanceMode ?? false;
27896
+ capabilitiesFor(input) {
27897
+ const runtime = this.runtimeForProvider(input.provider);
27898
+ return runtime.getScopedCapabilities?.({
27899
+ agentId: input.agentId ?? null,
27900
+ providerSessionId: input.providerSessionId ?? null
27901
+ }) ?? runtime.capabilities;
27902
+ }
27903
+ async resolveCapabilitiesFor(input) {
27904
+ const runtime = this.runtimeForProvider(input.provider);
27905
+ if (input.agentId && runtime.getAgentCapabilitySnapshot) {
27906
+ const snapshot = await runtime.getAgentCapabilitySnapshot(input.agentId);
27907
+ if (snapshot.effectiveCapabilities) {
27908
+ return snapshot.effectiveCapabilities;
27909
+ }
27910
+ }
27911
+ return this.capabilitiesFor(input);
27912
+ }
27913
+ runtimeSupportsFastMode(provider2, scope = {}) {
27914
+ const runtime = this.optionalRuntimeForProvider(provider2);
27915
+ if (!runtime) return false;
27916
+ return (runtime.getScopedCapabilities?.({
27917
+ agentId: scope.agentId ?? null,
27918
+ providerSessionId: scope.providerSessionId ?? null
27919
+ }) ?? runtime.capabilities).controls.performanceMode;
26394
27920
  }
26395
- fastModeForProvider(provider2, fastMode) {
26396
- return this.runtimeSupportsFastMode(provider2) ? normalizeFastMode(fastMode) : false;
27921
+ fastModeForProvider(provider2, fastMode, scope = {}) {
27922
+ return this.runtimeSupportsFastMode(provider2, scope) ? normalizeFastMode(fastMode) : false;
26397
27923
  }
26398
27924
  performanceModeForRecord(record2) {
26399
27925
  return performanceModeForFastMode(
26400
- this.fastModeForProvider(record2.provider, record2.fastMode)
27926
+ this.fastModeForProvider(record2.provider, record2.fastMode, record2)
26401
27927
  );
26402
27928
  }
26403
27929
  async listLoadedProviderSessionIds(provider2 = "codex") {
@@ -26691,7 +28217,7 @@ var ThreadPromptTurnCoordinator = class {
26691
28217
  async startPromptTurn(localThreadId, record2, input) {
26692
28218
  const displayPrompt = input.displayPrompt ?? input.prompt;
26693
28219
  const runtime = this.callbacks.runtimeForProvider(record2.provider);
26694
- const modelRecords = await runtime.listModels().catch(() => []);
28220
+ const modelRecords = await (record2.agentId && runtime.listModelsForAgent ? runtime.listModelsForAgent(record2.agentId, input.workspacePath) : runtime.listModels()).catch(() => []);
26695
28221
  ensureFastModeSupported(
26696
28222
  input.effectiveModel,
26697
28223
  input.performanceMode === "fast",
@@ -27011,7 +28537,10 @@ function toThreadDto(record2, loadedIds, callbacks) {
27011
28537
  title: record2.title,
27012
28538
  model,
27013
28539
  reasoningEffort: normalizeReasoningEffort2(record2.reasoningEffort),
27014
- fastMode: callbacks.fastModeForProvider(record2.provider, record2.fastMode),
28540
+ fastMode: callbacks.fastModeForProvider(record2.provider, record2.fastMode, {
28541
+ agentId,
28542
+ providerSessionId: record2.providerSessionId ?? null
28543
+ }),
27015
28544
  collaborationMode: normalizeCollaborationMode(record2.collaborationMode),
27016
28545
  approvalMode: record2.approvalMode ?? "yolo",
27017
28546
  sandboxMode: normalizeSandboxMode(record2.sandboxMode) ?? defaultSandboxModeForApprovalMode(record2.approvalMode ?? "yolo"),
@@ -27055,24 +28584,32 @@ var ThreadSessionCoordinator = class {
27055
28584
  agentId: input.threadInput.agentId,
27056
28585
  workspacePath: input.workspacePath
27057
28586
  }).catch(() => []);
28587
+ const effectiveModel = input.threadInput.model === "default" ? modelRecords.find((model) => model.isDefault)?.model ?? input.threadInput.model : input.threadInput.model;
27058
28588
  const reasoningEffort = this.providerRuntime.normalizeReasoningForModel(
27059
28589
  modelRecords,
27060
- input.threadInput.model,
28590
+ effectiveModel,
27061
28591
  input.threadInput.reasoningEffort ?? null
27062
28592
  );
27063
28593
  const sandboxMode = defaultSandboxModeForApprovalMode(input.threadInput.approvalMode);
27064
- const fastMode = this.providerRuntime.runtimeSupportsFastMode(provider2) ? this.performanceModeSettings.readFastMode() : false;
27065
- if (this.providerRuntime.runtimeSupportsFastMode(provider2)) {
27066
- ensureFastModeSupported(input.threadInput.model, fastMode, modelRecords);
28594
+ const capabilityScope = {
28595
+ agentId: input.threadInput.agentId ?? null
28596
+ };
28597
+ const supportsFastMode2 = this.providerRuntime.runtimeSupportsFastMode(
28598
+ provider2,
28599
+ capabilityScope
28600
+ );
28601
+ const fastMode = supportsFastMode2 ? this.performanceModeSettings.readFastMode() : false;
28602
+ if (supportsFastMode2) {
28603
+ ensureFastModeSupported(effectiveModel, fastMode, modelRecords);
27067
28604
  }
27068
28605
  const response = await runtime.startSession({
27069
28606
  cwd: input.workspacePath,
27070
28607
  ...input.threadInput.agentId ? { agentId: input.threadInput.agentId } : {},
27071
- model: input.threadInput.model,
28608
+ model: effectiveModel,
27072
28609
  reasoningEffort,
27073
28610
  approvalMode: input.threadInput.approvalMode,
27074
28611
  sandboxMode,
27075
- performanceMode: performanceModeForFastMode(fastMode)
28612
+ ...supportsFastMode2 ? { performanceMode: performanceModeForFastMode(fastMode) } : {}
27076
28613
  });
27077
28614
  return {
27078
28615
  provider: provider2,
@@ -27113,6 +28650,10 @@ var ThreadSessionCoordinator = class {
27113
28650
  provider: provider2
27114
28651
  });
27115
28652
  }
28653
+ async listImportSessions(provider2, agentId) {
28654
+ const runtime = this.providerRuntime.runtimeForProvider(provider2);
28655
+ return runtime.listImportSessions ? runtime.listImportSessions(agentId) : runtime.listSessions();
28656
+ }
27116
28657
  async resolveRuntimeImportSession(provider2, sessionId) {
27117
28658
  try {
27118
28659
  const session = await this.providerRuntime.runtimeForProvider(provider2).readSession(sessionId);
@@ -27121,13 +28662,17 @@ var ThreadSessionCoordinator = class {
27121
28662
  }
27122
28663
  return {
27123
28664
  provider: provider2,
27124
- source: "supervisor",
28665
+ agentId: session.agentId ?? null,
28666
+ source: "local_provider_import",
27125
28667
  sessionId,
27126
28668
  cwd: session.cwd,
27127
28669
  title: session.title?.trim() || session.preview?.trim() || "Untitled imported session",
27128
28670
  model: null,
27129
28671
  summaryText: session.preview,
27130
- fastMode: this.providerRuntime.runtimeSupportsFastMode(provider2) ? this.performanceModeSettings.readFastMode() : false
28672
+ fastMode: this.providerRuntime.runtimeSupportsFastMode(provider2, {
28673
+ agentId: session.agentId ?? null,
28674
+ providerSessionId: sessionId
28675
+ }) ? this.performanceModeSettings.readFastMode() : false
27131
28676
  };
27132
28677
  } catch {
27133
28678
  return null;
@@ -27153,12 +28698,24 @@ var ThreadSessionCoordinator = class {
27153
28698
  async resumeThreadSession(input) {
27154
28699
  const runtime = this.providerRuntime.runtimeForProvider(input.provider);
27155
28700
  const sandboxMode = input.resumeInput.sandboxMode ?? normalizeSandboxMode(input.currentSandboxMode) ?? defaultSandboxModeForApprovalMode(input.approvalMode);
27156
- const fastMode = this.providerRuntime.fastModeForProvider(input.provider, input.fastMode);
27157
28701
  const modelRecords = await this.listSessionModels({
27158
28702
  provider: input.provider,
27159
28703
  agentId: input.agentId,
27160
28704
  workspacePath: input.workspacePath
27161
28705
  }).catch(() => []);
28706
+ const capabilityScope = {
28707
+ agentId: input.agentId ?? null,
28708
+ providerSessionId: input.providerSessionId
28709
+ };
28710
+ const supportsFastMode2 = this.providerRuntime.runtimeSupportsFastMode(
28711
+ input.provider,
28712
+ capabilityScope
28713
+ );
28714
+ const fastMode = this.providerRuntime.fastModeForProvider(
28715
+ input.provider,
28716
+ input.fastMode,
28717
+ capabilityScope
28718
+ );
27162
28719
  let response;
27163
28720
  try {
27164
28721
  ensureFastModeSupported(
@@ -27170,7 +28727,7 @@ var ThreadSessionCoordinator = class {
27170
28727
  providerSessionId: input.providerSessionId,
27171
28728
  model: input.resumeInput.model ?? input.currentModel ?? null,
27172
28729
  sandboxMode,
27173
- performanceMode: performanceModeForFastMode(fastMode)
28730
+ ...supportsFastMode2 ? { performanceMode: performanceModeForFastMode(fastMode) } : {}
27174
28731
  });
27175
28732
  } catch (error) {
27176
28733
  if (!isRemoteThreadBootstrapError(error)) {
@@ -27195,7 +28752,8 @@ var ThreadSessionCoordinator = class {
27195
28752
  }
27196
28753
  async compactThreadSession(input) {
27197
28754
  const runtime = this.providerRuntime.runtimeForProvider(input.provider);
27198
- if (!runtime.compactSession) {
28755
+ const capabilities = this.providerRuntime.capabilitiesFor(input);
28756
+ if (!runtime.compactSession || !capabilities.turns.compact) {
27199
28757
  throw new HttpError(409, {
27200
28758
  code: "conflict",
27201
28759
  message: "This backend does not support context compaction."
@@ -27212,25 +28770,27 @@ var ThreadSessionCoordinator = class {
27212
28770
  });
27213
28771
  }
27214
28772
  const runtime = this.providerRuntime.runtimeForProvider(input.provider);
27215
- if (!runtime.forkSession) {
28773
+ const capabilities = this.providerRuntime.capabilitiesFor(input);
28774
+ if (!runtime.forkSession || !capabilities.branching.fork) {
27216
28775
  throw new HttpError(409, {
27217
28776
  code: "conflict",
27218
28777
  message: "This backend does not support session fork."
27219
28778
  });
27220
28779
  }
28780
+ const turnsToRollback = selectedTurn == null ? 0 : Math.max(0, input.turnOptions.length - selectedTurn.turnIndex);
28781
+ const rollbackSession = runtime.rollbackSession?.bind(runtime);
28782
+ if (turnsToRollback > 0 && !rollbackSession) {
28783
+ throw new HttpError(409, {
28784
+ code: "conflict",
28785
+ message: "This backend supports latest-session fork only."
28786
+ });
28787
+ }
27221
28788
  let forkedSession = await runtime.forkSession({
27222
28789
  providerSessionId: input.providerSessionId,
27223
28790
  atTurnId: selectedTurn?.turnId ?? null
27224
28791
  });
27225
- const turnsToRollback = selectedTurn == null ? 0 : Math.max(0, input.turnOptions.length - selectedTurn.turnIndex);
27226
28792
  if (turnsToRollback > 0) {
27227
- if (!runtime.rollbackSession) {
27228
- throw new HttpError(409, {
27229
- code: "conflict",
27230
- message: "This backend does not support rollback after fork."
27231
- });
27232
- }
27233
- forkedSession = await runtime.rollbackSession({
28793
+ forkedSession = await rollbackSession({
27234
28794
  providerSessionId: forkedSession.providerSessionId,
27235
28795
  count: turnsToRollback
27236
28796
  });
@@ -27266,10 +28826,18 @@ var ThreadSessionCoordinator = class {
27266
28826
  workspacePath: input.workspacePath
27267
28827
  });
27268
28828
  const fallbackModel = modelRecords.find((entry) => entry.isDefault) ?? modelRecords[0] ?? null;
27269
- const supportsFastMode2 = this.providerRuntime.runtimeSupportsFastMode(input.provider);
28829
+ const capabilityScope = {
28830
+ agentId: input.agentId ?? null,
28831
+ providerSessionId: null
28832
+ };
28833
+ const supportsFastMode2 = this.providerRuntime.runtimeSupportsFastMode(
28834
+ input.provider,
28835
+ capabilityScope
28836
+ );
27270
28837
  const currentFastMode = this.providerRuntime.fastModeForProvider(
27271
28838
  input.provider,
27272
- input.currentFastMode
28839
+ input.currentFastMode,
28840
+ capabilityScope
27273
28841
  );
27274
28842
  const nextFastMode = supportsFastMode2 && input.settings.fastMode !== void 0 ? input.settings.fastMode : currentFastMode;
27275
28843
  const currentModel = input.currentModel ?? fallbackModel?.model ?? null;
@@ -27314,9 +28882,15 @@ var ThreadSessionCoordinator = class {
27314
28882
  );
27315
28883
  const collaborationMode = input.promptInput?.collaborationMode ?? normalizeCollaborationMode(input.currentCollaborationMode);
27316
28884
  const sandboxMode = (input.promptInput?.sandboxMode !== void 0 ? normalizeSandboxMode(input.promptInput.sandboxMode) : normalizeSandboxMode(input.currentSandboxMode)) ?? defaultSandboxModeForApprovalMode(input.approvalMode);
28885
+ const capabilities = this.providerRuntime.capabilitiesFor({
28886
+ provider: input.provider,
28887
+ agentId: input.agentId ?? null
28888
+ });
28889
+ const supportsFastMode2 = capabilities.controls.performanceMode;
27317
28890
  const fastMode = this.providerRuntime.fastModeForProvider(
27318
28891
  input.provider,
27319
- input.currentFastMode
28892
+ input.currentFastMode,
28893
+ { agentId: input.agentId ?? null }
27320
28894
  );
27321
28895
  ensureFastModeSupported(effectiveModel, fastMode, modelRecords);
27322
28896
  return {
@@ -27324,8 +28898,8 @@ var ThreadSessionCoordinator = class {
27324
28898
  normalizedReasoning,
27325
28899
  collaborationMode,
27326
28900
  sandboxMode,
27327
- performanceMode: performanceModeForFastMode(fastMode),
27328
- supportsRunningTurnInput: Boolean(runtime.sendInput && runtime.capabilities.turns.steer)
28901
+ performanceMode: supportsFastMode2 ? performanceModeForFastMode(fastMode) : null,
28902
+ supportsRunningTurnInput: Boolean(runtime.sendInput && capabilities.turns.steer)
27329
28903
  };
27330
28904
  }
27331
28905
  };
@@ -27437,12 +29011,15 @@ var ThreadSessionLifecycleCoordinator = class {
27437
29011
 
27438
29012
  // src/thread-history-persistence-coordinator.ts
27439
29013
  var ThreadHistoryPersistenceCoordinator = class {
27440
- constructor(db, liveState) {
29014
+ constructor(db, liveState, checkpointOptions = {}) {
27441
29015
  this.db = db;
27442
29016
  this.liveState = liveState;
29017
+ this.checkpointOptions = checkpointOptions;
27443
29018
  }
27444
29019
  db;
27445
29020
  liveState;
29021
+ checkpointOptions;
29022
+ liveAgentMessageCheckpoints = /* @__PURE__ */ new Map();
27446
29023
  listPersistedHistoryItemsByTurnId(localThreadId) {
27447
29024
  const itemsByTurnId = /* @__PURE__ */ new Map();
27448
29025
  for (const record2 of listThreadHistoryItemRecordsByThreadId(this.db, localThreadId)) {
@@ -27480,10 +29057,64 @@ var ThreadHistoryPersistenceCoordinator = class {
27480
29057
  itemJson: JSON.stringify(item)
27481
29058
  });
27482
29059
  }
29060
+ checkpointLiveAgentMessage(localThreadId, turnId, item) {
29061
+ if (item.kind !== "agentMessage") {
29062
+ return;
29063
+ }
29064
+ const key = `${localThreadId}\0${turnId}\0${item.id}`;
29065
+ const previous = this.liveAgentMessageCheckpoints.get(key);
29066
+ const now = (this.checkpointOptions.now ?? Date.now)();
29067
+ const intervalMs = this.checkpointOptions.intervalMs ?? 250;
29068
+ const textDelta = this.checkpointOptions.textDelta ?? 512;
29069
+ if (previous && now - previous.savedAt < intervalMs && item.text.length - previous.textLength < textDelta) {
29070
+ return;
29071
+ }
29072
+ this.persistLiveHistoryItem(localThreadId, turnId, item);
29073
+ this.liveAgentMessageCheckpoints.set(key, {
29074
+ savedAt: now,
29075
+ textLength: item.text.length
29076
+ });
29077
+ }
29078
+ clearThread(localThreadId) {
29079
+ const prefix = `${localThreadId}\0`;
29080
+ for (const key of this.liveAgentMessageCheckpoints.keys()) {
29081
+ if (key.startsWith(prefix)) {
29082
+ this.liveAgentMessageCheckpoints.delete(key);
29083
+ }
29084
+ }
29085
+ }
29086
+ persistHydratedTurns(localThreadId, turns) {
29087
+ for (const turn of turns) {
29088
+ if (turn.startedAt) {
29089
+ upsertThreadTurnMetadata(this.db, {
29090
+ threadId: localThreadId,
29091
+ turnId: turn.providerTurnId,
29092
+ createdAt: turn.startedAt
29093
+ });
29094
+ }
29095
+ turn.items.forEach((item, index) => {
29096
+ if (!shouldPersistRuntimeFinalHistoryItem(item)) {
29097
+ return;
29098
+ }
29099
+ const createdAt = item.createdAt ?? turn.startedAt;
29100
+ this.persistProjectedHistoryItem(
29101
+ localThreadId,
29102
+ turn.providerTurnId,
29103
+ {
29104
+ ...item,
29105
+ ...createdAt ? { createdAt } : {},
29106
+ sequence: item.sequence ?? index,
29107
+ ...item.kind === "agentMessage" && !item.sourceTurnId ? { sourceTurnId: turn.providerTurnId } : {}
29108
+ }
29109
+ );
29110
+ });
29111
+ }
29112
+ }
27483
29113
  deletePersistedHistoryItemsForTurn(localThreadId, turnId) {
27484
29114
  deleteThreadHistoryItemRecordsByThreadAndTurnId(this.db, localThreadId, turnId);
27485
29115
  }
27486
29116
  persistFinalTurnOrderingHints(localThreadId, turnId, items) {
29117
+ this.clearTurnCheckpoints(localThreadId, turnId);
27487
29118
  const orderingHints = this.liveState.finalTurnAgentMessageOrderingMetadata(
27488
29119
  localThreadId,
27489
29120
  turnId,
@@ -27510,6 +29141,14 @@ var ThreadHistoryPersistenceCoordinator = class {
27510
29141
  });
27511
29142
  }
27512
29143
  }
29144
+ clearTurnCheckpoints(localThreadId, turnId) {
29145
+ const prefix = `${localThreadId}\0${turnId}\0`;
29146
+ for (const key of this.liveAgentMessageCheckpoints.keys()) {
29147
+ if (key.startsWith(prefix)) {
29148
+ this.liveAgentMessageCheckpoints.delete(key);
29149
+ }
29150
+ }
29151
+ }
27513
29152
  persistRuntimeTurnItemsAsDisplayTurn(localThreadId, runtimeTurnId, displayTurnId, items) {
27514
29153
  if (runtimeTurnId === displayTurnId) {
27515
29154
  return;
@@ -27574,10 +29213,10 @@ var ThreadHistoryPersistenceCoordinator = class {
27574
29213
  };
27575
29214
 
27576
29215
  // src/thread-deletion-coordinator.ts
27577
- import fs13 from "fs/promises";
27578
- import path17 from "path";
29216
+ import fs15 from "fs/promises";
29217
+ import path19 from "path";
27579
29218
  function threadTempDirectoryPath(workspacePath, localThreadId) {
27580
- return path17.join(workspacePath, ".temp", "threads", localThreadId);
29219
+ return path19.join(workspacePath, ".temp", "threads", localThreadId);
27581
29220
  }
27582
29221
  var ThreadDeletionCoordinator = class {
27583
29222
  constructor(db, requestCoordinator, usageAccounting, liveState, auxiliaryState, callbacks) {
@@ -27605,10 +29244,11 @@ var ThreadDeletionCoordinator = class {
27605
29244
  const workspace = getWorkspaceRecordById(this.db, record2.workspaceId);
27606
29245
  if (workspace) {
27607
29246
  const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
27608
- await fs13.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
29247
+ await fs15.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
27609
29248
  });
27610
29249
  }
27611
29250
  this.requestCoordinator.clearThread(localThreadId);
29251
+ this.callbacks.clearHistoryPersistence(localThreadId);
27612
29252
  this.callbacks.invalidateThreadDetailCache(localThreadId);
27613
29253
  this.usageAccounting.clearThread(localThreadId);
27614
29254
  this.liveState.clearThread(localThreadId);
@@ -27629,9 +29269,9 @@ var ThreadDeletionCoordinator = class {
27629
29269
  };
27630
29270
 
27631
29271
  // src/exports/thread-pdf-export.ts
27632
- import fs14 from "fs";
29272
+ import fs16 from "fs";
27633
29273
  import { createRequire as createRequire3 } from "module";
27634
- import path18 from "path";
29274
+ import path20 from "path";
27635
29275
  import puppeteer from "puppeteer-core";
27636
29276
  import { marked } from "marked";
27637
29277
  var MAX_TEXT_CHARS = 12e3;
@@ -27682,10 +29322,10 @@ function renderEmbeddedSystemCjkFontCss() {
27682
29322
  embeddedSystemCjkFontCss = "";
27683
29323
  for (const candidate of EMBEDDED_CJK_FONT_CANDIDATES) {
27684
29324
  try {
27685
- if (!fs14.existsSync(candidate.path)) {
29325
+ if (!fs16.existsSync(candidate.path)) {
27686
29326
  continue;
27687
29327
  }
27688
- const font = fs14.readFileSync(candidate.path);
29328
+ const font = fs16.readFileSync(candidate.path);
27689
29329
  embeddedSystemCjkFontCss = `
27690
29330
  @font-face {
27691
29331
  font-family: "RemoteCodexCJK";
@@ -27748,7 +29388,7 @@ function containsCjkCodePoint(codePoints) {
27748
29388
  }
27749
29389
  function renderBundledFontCss(source, usedCodePoints) {
27750
29390
  const packageRoot = resolvePackageRoot3(source.packageName);
27751
- const cssPath = path18.join(packageRoot, source.cssFile);
29391
+ const cssPath = path20.join(packageRoot, source.cssFile);
27752
29392
  const blocks = getFontFaceBlocks(cssPath);
27753
29393
  return blocks.filter((block) => !usedCodePoints || fontFaceIntersects(block.ranges, usedCodePoints)).map((block) => inlineFontFaceBlock(block, packageRoot)).filter(Boolean);
27754
29394
  }
@@ -27757,7 +29397,7 @@ function resolvePackageRoot3(packageName) {
27757
29397
  if (cached) {
27758
29398
  return cached;
27759
29399
  }
27760
- const packageRoot = path18.dirname(require2.resolve(`${packageName}/package.json`));
29400
+ const packageRoot = path20.dirname(require2.resolve(`${packageName}/package.json`));
27761
29401
  packageRootCache.set(packageName, packageRoot);
27762
29402
  return packageRoot;
27763
29403
  }
@@ -27766,7 +29406,7 @@ function getFontFaceBlocks(cssPath) {
27766
29406
  if (cached) {
27767
29407
  return cached;
27768
29408
  }
27769
- const css = fs14.readFileSync(cssPath, "utf8");
29409
+ const css = fs16.readFileSync(cssPath, "utf8");
27770
29410
  const blocks = Array.from(css.matchAll(/@font-face\s*{[\s\S]*?}/g)).map((match) => {
27771
29411
  const block = match[0];
27772
29412
  const fontPath = block.match(/url\((['"]?)(\.\/files\/[^)'"]+\.woff2)\1\)\s*format\((['"]?)woff2\3\)/)?.[2] ?? "";
@@ -27805,9 +29445,9 @@ function fontFaceIntersects(ranges, usedCodePoints) {
27805
29445
  return false;
27806
29446
  }
27807
29447
  function inlineFontFaceBlock(block, packageRoot) {
27808
- const fontPath = path18.join(packageRoot, block.fontPath);
29448
+ const fontPath = path20.join(packageRoot, block.fontPath);
27809
29449
  try {
27810
- const font = fs14.readFileSync(fontPath);
29450
+ const font = fs16.readFileSync(fontPath);
27811
29451
  return block.block.replace(/font-display:\s*swap;/g, "font-display: block;").replace(/src:\s*[^;]+;/, `src: url("data:font/woff2;base64,${font.toString("base64")}") format("woff2");`);
27812
29452
  } catch {
27813
29453
  return "";
@@ -28806,7 +30446,7 @@ endobj
28806
30446
  function resolvePdfBrowserExecutablePath() {
28807
30447
  try {
28808
30448
  const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ? process.env.PUPPETEER_EXECUTABLE_PATH : puppeteer.executablePath(PUPPETEER_CHANNEL);
28809
- if (!fs14.existsSync(executablePath)) {
30449
+ if (!fs16.existsSync(executablePath)) {
28810
30450
  throw new Error(`Browser executable was not found at ${executablePath}`);
28811
30451
  }
28812
30452
  return executablePath;
@@ -29075,6 +30715,7 @@ var ThreadForkCoordinator = class {
29075
30715
  const turnOptions = await this.listForkTurnOptions(localThreadId);
29076
30716
  const forkResult = await this.sessionCoordinator.forkThreadSession({
29077
30717
  provider: record2.provider,
30718
+ agentId: record2.agentId,
29078
30719
  providerSessionId,
29079
30720
  mode: input.mode,
29080
30721
  ...input.turnId ? { turnId: input.turnId } : {},
@@ -29085,11 +30726,15 @@ var ThreadForkCoordinator = class {
29085
30726
  const created = createThreadRecord(this.db, {
29086
30727
  workspaceId: record2.workspaceId,
29087
30728
  provider: this.callbacks.providerForRecord(record2),
30729
+ agentId: forkedSession.agentId ?? record2.agentId ?? null,
29088
30730
  providerSessionId: forkedSession.providerSessionId,
29089
30731
  title: `${forkTitleBase} / fork`,
29090
30732
  model: record2.model,
29091
30733
  reasoningEffort: record2.reasoningEffort,
29092
- fastMode: this.callbacks.fastModeForProvider(record2.provider, record2.fastMode),
30734
+ fastMode: this.callbacks.fastModeForProvider(record2.provider, record2.fastMode, {
30735
+ agentId: record2.agentId ?? null,
30736
+ providerSessionId
30737
+ }),
29093
30738
  fastBaseModel: record2.fastBaseModel,
29094
30739
  fastBaseReasoningEffort: record2.fastBaseReasoningEffort,
29095
30740
  collaborationMode: this.callbacks.normalizeCollaborationMode(record2.collaborationMode),
@@ -29126,19 +30771,19 @@ var ThreadForkCoordinator = class {
29126
30771
 
29127
30772
  // src/thread-attachment-coordinator.ts
29128
30773
  import { randomUUID as randomUUID6 } from "crypto";
29129
- import fs15 from "fs/promises";
29130
- import path19 from "path";
30774
+ import fs17 from "fs/promises";
30775
+ import path21 from "path";
29131
30776
  async function pathExists(absPath) {
29132
30777
  try {
29133
- await fs15.access(absPath);
30778
+ await fs17.access(absPath);
29134
30779
  return true;
29135
30780
  } catch {
29136
30781
  return false;
29137
30782
  }
29138
30783
  }
29139
30784
  function sanitizeAttachmentFileName(originalName) {
29140
- const basename = path19.basename(originalName).trim() || "attachment";
29141
- const extension = path19.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
30785
+ const basename = path21.basename(originalName).trim() || "attachment";
30786
+ const extension = path21.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
29142
30787
  const rawStem = extension ? basename.slice(0, -extension.length) : basename;
29143
30788
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
29144
30789
  const stem = sanitizedStem || "attachment";
@@ -29146,7 +30791,7 @@ function sanitizeAttachmentFileName(originalName) {
29146
30791
  return `${stem}-${randomUUID6().slice(0, 8)}${normalizedExtension}`;
29147
30792
  }
29148
30793
  function threadTempDirectoryPath2(workspacePath, localThreadId) {
29149
- return path19.join(workspacePath, ".temp", "threads", localThreadId);
30794
+ return path21.join(workspacePath, ".temp", "threads", localThreadId);
29150
30795
  }
29151
30796
  var ThreadAttachmentCoordinator = class {
29152
30797
  constructor(db) {
@@ -29175,7 +30820,7 @@ var ThreadAttachmentCoordinator = class {
29175
30820
  });
29176
30821
  }
29177
30822
  const tempDirectory = threadTempDirectoryPath2(workspace.absPath, localThreadId);
29178
- await fs15.mkdir(tempDirectory, { recursive: true });
30823
+ await fs17.mkdir(tempDirectory, { recursive: true });
29179
30824
  let rewrittenPrompt = input.prompt;
29180
30825
  for (const attachment of attachments) {
29181
30826
  if (!rewrittenPrompt.includes(attachment.manifest.placeholder)) {
@@ -29187,7 +30832,7 @@ var ThreadAttachmentCoordinator = class {
29187
30832
  const savedFileName = sanitizeAttachmentFileName(
29188
30833
  attachment.manifest.originalName
29189
30834
  );
29190
- await fs15.writeFile(path19.join(tempDirectory, savedFileName), attachment.buffer);
30835
+ await fs17.writeFile(path21.join(tempDirectory, savedFileName), attachment.buffer);
29191
30836
  const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
29192
30837
  const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
29193
30838
  rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
@@ -29200,30 +30845,30 @@ var ThreadAttachmentCoordinator = class {
29200
30845
  };
29201
30846
 
29202
30847
  // src/thread-import-coordinator.ts
29203
- import fs16 from "fs/promises";
29204
- import path20 from "path";
30848
+ import fs18 from "fs/promises";
30849
+ import path22 from "path";
29205
30850
  async function pathExists2(absPath) {
29206
30851
  try {
29207
- await fs16.access(absPath);
30852
+ await fs18.access(absPath);
29208
30853
  return true;
29209
30854
  } catch {
29210
30855
  return false;
29211
30856
  }
29212
30857
  }
29213
30858
  async function resolveComparablePath2(absPath) {
29214
- const resolved = path20.resolve(absPath);
30859
+ const resolved = path22.resolve(absPath);
29215
30860
  if (await pathExists2(resolved)) {
29216
- return fs16.realpath(resolved);
30861
+ return fs18.realpath(resolved);
29217
30862
  }
29218
- const parentPath = path20.dirname(resolved);
30863
+ const parentPath = path22.dirname(resolved);
29219
30864
  if (parentPath === resolved) {
29220
30865
  return resolved;
29221
30866
  }
29222
30867
  const resolvedParent = await resolveComparablePath2(parentPath);
29223
- return path20.join(resolvedParent, path20.basename(resolved));
30868
+ return path22.join(resolvedParent, path22.basename(resolved));
29224
30869
  }
29225
30870
  async function resolveImportedWorkspacePath(candidatePath) {
29226
- if (!path20.isAbsolute(candidatePath)) {
30871
+ if (!path22.isAbsolute(candidatePath)) {
29227
30872
  throw new HttpError(400, {
29228
30873
  code: "bad_request",
29229
30874
  message: "Imported session path must be absolute."
@@ -29232,12 +30877,33 @@ async function resolveImportedWorkspacePath(candidatePath) {
29232
30877
  return resolveComparablePath2(candidatePath);
29233
30878
  }
29234
30879
  var ThreadImportCoordinator = class {
29235
- constructor(db, sessionCoordinator, _workspaceRoot) {
30880
+ constructor(db, sessionCoordinator) {
29236
30881
  this.db = db;
29237
30882
  this.sessionCoordinator = sessionCoordinator;
29238
30883
  }
29239
30884
  db;
29240
30885
  sessionCoordinator;
30886
+ async listImportCandidates(providerInput, agentId) {
30887
+ const provider2 = normalizeAgentBackendId(providerInput ?? "codex") ?? "codex";
30888
+ const sessions = await this.sessionCoordinator.listImportSessions(provider2, agentId);
30889
+ return sessions.filter((session) => Boolean(session.providerSessionId.trim()) && path22.isAbsolute(session.cwd) && !getThreadRecordByProviderSessionId(
30890
+ this.db,
30891
+ provider2,
30892
+ session.providerSessionId
30893
+ )).map((session) => ({
30894
+ provider: provider2,
30895
+ agentId: session.agentId ?? null,
30896
+ sessionId: session.providerSessionId,
30897
+ cwd: session.cwd,
30898
+ title: session.title?.trim() || session.preview?.trim() || "Untitled session",
30899
+ preview: session.preview,
30900
+ createdAt: session.createdAt,
30901
+ updatedAt: session.updatedAt,
30902
+ historyStatus: "unknown"
30903
+ })).sort((left, right) => (right.updatedAt ?? right.createdAt ?? "").localeCompare(
30904
+ left.updatedAt ?? left.createdAt ?? ""
30905
+ ));
30906
+ }
29241
30907
  async importLocalThread(input) {
29242
30908
  const normalizedSessionId = input.sessionId.trim();
29243
30909
  if (!normalizedSessionId) {
@@ -29270,12 +30936,13 @@ var ThreadImportCoordinator = class {
29270
30936
  if (!workspace) {
29271
30937
  workspace = createWorkspaceRecord(this.db, {
29272
30938
  absPath: importedPath,
29273
- label: path20.basename(importedPath) || "workspace"
30939
+ label: path22.basename(importedPath) || "workspace"
29274
30940
  });
29275
30941
  }
29276
30942
  const created = createThreadRecord(this.db, {
29277
30943
  workspaceId: workspace.id,
29278
30944
  provider: importSession.provider,
30945
+ agentId: importSession.agentId ?? null,
29279
30946
  providerSessionId: importSession.sessionId,
29280
30947
  title: importSession.title,
29281
30948
  model: importSession.model,
@@ -29291,7 +30958,7 @@ var ThreadImportCoordinator = class {
29291
30958
  return created.id;
29292
30959
  }
29293
30960
  async assertImportedThreadReadyForPrompt(input) {
29294
- if (input.source !== "local_codex_import") {
30961
+ if (input.source !== "local_codex_import" && input.source !== "local_provider_import") {
29295
30962
  return;
29296
30963
  }
29297
30964
  const loadedIds = await input.listLoadedProviderSessionIds(input.provider);
@@ -29303,7 +30970,7 @@ var ThreadImportCoordinator = class {
29303
30970
  }
29304
30971
  }
29305
30972
  async ensureImportedThreadConnectedForImplementation(input) {
29306
- if (input.source !== "local_codex_import") {
30973
+ if (input.source !== "local_codex_import" && input.source !== "local_provider_import") {
29307
30974
  return;
29308
30975
  }
29309
30976
  const loadedIds = await input.listLoadedProviderSessionIds(input.provider);
@@ -29356,7 +31023,7 @@ function pluginDeveloperInstructions(pluginService) {
29356
31023
  }
29357
31024
  async function pathExists3(absPath) {
29358
31025
  try {
29359
- await fs17.access(absPath);
31026
+ await fs19.access(absPath);
29360
31027
  return true;
29361
31028
  } catch {
29362
31029
  return false;
@@ -29398,8 +31065,7 @@ var ThreadService = class {
29398
31065
  );
29399
31066
  this.importCoordinator = new ThreadImportCoordinator(
29400
31067
  db,
29401
- this.sessionCoordinator,
29402
- workspaceRoot
31068
+ this.sessionCoordinator
29403
31069
  );
29404
31070
  this.promptTurnCoordinator = new ThreadPromptTurnCoordinator(
29405
31071
  db,
@@ -29445,6 +31111,12 @@ var ThreadService = class {
29445
31111
  },
29446
31112
  syncAfterRemoteSession: (localThreadId, remoteSession) => {
29447
31113
  const updated = getThreadRecordById(this.db, localThreadId);
31114
+ if (updated.provider === "acp") {
31115
+ this.historyPersistence.persistHydratedTurns(
31116
+ updated.id,
31117
+ remoteSession.turns
31118
+ );
31119
+ }
29448
31120
  this.syncPendingPlanDecisionRequest(
29449
31121
  updated.id,
29450
31122
  updated.collaborationMode,
@@ -29464,6 +31136,11 @@ var ThreadService = class {
29464
31136
  emitThreadEvent: (type, threadId, payload) => this.emitThreadEvent(type, threadId, payload),
29465
31137
  requireProviderSessionId: (record2) => this.requireProviderSessionId(record2),
29466
31138
  runtimeForProvider: (provider2) => this.runtimeForProvider(provider2),
31139
+ capabilitiesForRecord: (record2) => this.providerRuntime.resolveCapabilitiesFor({
31140
+ provider: record2.provider,
31141
+ agentId: record2.agentId ?? null,
31142
+ providerSessionId: record2.providerSessionId ?? null
31143
+ }),
29467
31144
  appendGoalActivityNote: (threadId, objective) => this.auxiliaryState.appendActivityNote(threadId, {
29468
31145
  kind: "goal",
29469
31146
  text: objective
@@ -29492,6 +31169,7 @@ var ThreadService = class {
29492
31169
  this.liveState,
29493
31170
  this.auxiliaryState,
29494
31171
  {
31172
+ clearHistoryPersistence: (localThreadId) => this.historyPersistence.clearThread(localThreadId),
29495
31173
  invalidateThreadDetailCache: (localThreadId) => this.invalidateThreadDetailCache(localThreadId)
29496
31174
  }
29497
31175
  );
@@ -29509,7 +31187,7 @@ var ThreadService = class {
29509
31187
  this.sessionCoordinator,
29510
31188
  {
29511
31189
  buildThreadPatch: (remoteSession, model, reasoningEffort) => buildThreadPatch(remoteSession, model, reasoningEffort),
29512
- fastModeForProvider: (provider2, fastMode) => this.fastModeForProvider(provider2, fastMode),
31190
+ fastModeForProvider: (provider2, fastMode, scope) => this.fastModeForProvider(provider2, fastMode, scope),
29513
31191
  getThreadDetail: (localThreadId) => this.getThreadDetail(localThreadId),
29514
31192
  invalidateThreadDetailCache: (localThreadId) => this.invalidateThreadDetailCache(localThreadId),
29515
31193
  normalizeCollaborationMode,
@@ -29530,7 +31208,8 @@ var ThreadService = class {
29530
31208
  input.itemId,
29531
31209
  input.delta,
29532
31210
  input.sequence,
29533
- input.createdAt
31211
+ input.createdAt,
31212
+ input.checkpoint
29534
31213
  ),
29535
31214
  clearPendingPlanDecisionRequests: (localThreadId, emitEvents) => this.clearPendingPlanDecisionRequests(localThreadId, emitEvents),
29536
31215
  clearPendingSteersForTurn: (localThreadId, turnId) => this.auxiliaryState.clearPendingSteersForTurn(localThreadId, turnId),
@@ -29670,14 +31349,8 @@ var ThreadService = class {
29670
31349
  providerSessionId
29671
31350
  );
29672
31351
  }
29673
- runtimeSupportsFastMode(provider2) {
29674
- return this.providerRuntime.runtimeSupportsFastMode(provider2);
29675
- }
29676
- fastModeForProvider(provider2, fastMode) {
29677
- return this.providerRuntime.fastModeForProvider(provider2, fastMode);
29678
- }
29679
- performanceModeForRecord(record2) {
29680
- return this.providerRuntime.performanceModeForRecord(record2);
31352
+ fastModeForProvider(provider2, fastMode, scope = {}) {
31353
+ return this.providerRuntime.fastModeForProvider(provider2, fastMode, scope);
29681
31354
  }
29682
31355
  async handleProviderRequest(request) {
29683
31356
  await this.handleProviderRuntimeRequest(request);
@@ -29767,7 +31440,7 @@ var ThreadService = class {
29767
31440
  updateThreadRecord(this.db, created.id, {
29768
31441
  ...buildThreadPatch(
29769
31442
  session.response.session,
29770
- input.model,
31443
+ session.response.model ?? input.model,
29771
31444
  session.response.reasoningEffort ?? session.reasoningEffort
29772
31445
  ),
29773
31446
  title: session.normalizedTitle === DEFAULT_THREAD_TITLE2 && session.response.session.title && session.response.session.title.trim() !== GENERIC_REMOTE_THREAD_TITLE ? truncateAutoThreadTitle(session.response.session.title) : session.normalizedTitle
@@ -29779,6 +31452,9 @@ var ThreadService = class {
29779
31452
  const localThreadId = await this.importCoordinator.importLocalThread(input);
29780
31453
  return this.getThreadDetail(localThreadId);
29781
31454
  }
31455
+ async listImportCandidates(provider2, agentId) {
31456
+ return this.importCoordinator.listImportCandidates(provider2, agentId);
31457
+ }
29782
31458
  async getThreadDetail(localThreadId, options = {}) {
29783
31459
  const record2 = this.requireThreadRecord(localThreadId);
29784
31460
  const workspace = this.requireWorkspaceForThread(record2);
@@ -30138,6 +31814,7 @@ var ThreadService = class {
30138
31814
  }
30139
31815
  await this.sessionCoordinator.compactThreadSession({
30140
31816
  provider: record2.provider,
31817
+ agentId: record2.agentId,
30141
31818
  providerSessionId
30142
31819
  });
30143
31820
  const updated = getThreadRecordById(this.db, localThreadId);
@@ -30285,7 +31962,12 @@ var ThreadService = class {
30285
31962
  }
30286
31963
  const providerSessionId = this.requireProviderSessionId(record2);
30287
31964
  const runtime = this.runtimeForProvider(record2.provider);
30288
- if (!runtime.sendInput) {
31965
+ const capabilities = this.providerRuntime.capabilitiesFor({
31966
+ provider: record2.provider,
31967
+ agentId: record2.agentId ?? null,
31968
+ providerSessionId
31969
+ });
31970
+ if (!runtime.sendInput || !capabilities.turns.steer) {
30289
31971
  throw new HttpError(409, {
30290
31972
  code: "conflict",
30291
31973
  message: "This backend does not support steering an active turn."
@@ -30399,10 +32081,6 @@ var ThreadService = class {
30399
32081
  async handleRuntimeEvent(event) {
30400
32082
  await this.runtimeEventProjector.handleRuntimeEvent(event);
30401
32083
  }
30402
- runtimeSupportsLiveRunningTurnInput(provider2) {
30403
- const runtime = this.runtimeForProvider(provider2);
30404
- return Boolean(runtime.sendInput && runtime.capabilities.turns.steer);
30405
- }
30406
32084
  shouldPreserveCompletedPendingSteer(localThreadId) {
30407
32085
  const record2 = getThreadRecordById(this.db, localThreadId);
30408
32086
  if (!record2) {
@@ -30496,7 +32174,7 @@ var ThreadService = class {
30496
32174
  }
30497
32175
  toThreadDto(record2, loadedIds) {
30498
32176
  return toThreadDto(record2, loadedIds, {
30499
- fastModeForProvider: (provider2, fastMode) => this.fastModeForProvider(provider2, fastMode),
32177
+ fastModeForProvider: (provider2, fastMode, scope) => this.fastModeForProvider(provider2, fastMode, scope),
30500
32178
  getThreadContextUsage: (localThreadId) => this.getThreadContextUsage(localThreadId)
30501
32179
  });
30502
32180
  }
@@ -30511,8 +32189,8 @@ var ThreadService = class {
30511
32189
  listPendingRequests(localThreadId, options = {}) {
30512
32190
  return this.requestCoordinator.listPendingRequests(localThreadId, options);
30513
32191
  }
30514
- appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence, createdAt) {
30515
- this.liveState.appendLiveAgentMessageDelta({
32192
+ appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence, createdAt, checkpoint = false) {
32193
+ const item = this.liveState.appendLiveAgentMessageDelta({
30516
32194
  localThreadId,
30517
32195
  turnId,
30518
32196
  itemId,
@@ -30520,6 +32198,13 @@ var ThreadService = class {
30520
32198
  sequence,
30521
32199
  createdAt
30522
32200
  });
32201
+ if (checkpoint) {
32202
+ this.historyPersistence.checkpointLiveAgentMessage(
32203
+ localThreadId,
32204
+ turnId,
32205
+ item
32206
+ );
32207
+ }
30523
32208
  }
30524
32209
  createPendingPlanDecisionRequest(localThreadId, turnId, emitEvents) {
30525
32210
  this.requestCoordinator.createPendingPlanDecisionRequest(
@@ -30598,8 +32283,8 @@ function parseQueuedTurnConfig(value) {
30598
32283
  }
30599
32284
 
30600
32285
  // src/routes/agent-runtimes.ts
30601
- import fs18 from "fs/promises";
30602
- import path21 from "path";
32286
+ import fs20 from "fs/promises";
32287
+ import path23 from "path";
30603
32288
  import { fileURLToPath } from "url";
30604
32289
  import { z as z6 } from "zod";
30605
32290
 
@@ -30619,8 +32304,8 @@ var modelQuerySchema = z6.object({
30619
32304
  agentId: z6.string().min(1).optional(),
30620
32305
  cwd: z6.string().min(1).optional()
30621
32306
  });
30622
- var repositoryRoot = path21.resolve(
30623
- path21.dirname(fileURLToPath(import.meta.url)),
32307
+ var repositoryRoot = path23.resolve(
32308
+ path23.dirname(fileURLToPath(import.meta.url)),
30624
32309
  "..",
30625
32310
  "..",
30626
32311
  "..",
@@ -30792,6 +32477,21 @@ async function registerAgentRuntimeRoutes(app2) {
30792
32477
  }
30793
32478
  return (await runtime.listAgentOptions()).map(modelDto);
30794
32479
  });
32480
+ app2.get("/api/agent-runtimes/:provider/capabilities", async (request) => {
32481
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
32482
+ const query = z6.object({ agentId: z6.string().min(1) }).parse(request.query);
32483
+ const runtime = app2.services.agentRuntimes.getOptional(provider2);
32484
+ if (!runtime) {
32485
+ throw providerNotConfigured(provider2);
32486
+ }
32487
+ if (!runtime.getAgentCapabilitySnapshot) {
32488
+ throw new HttpError(409, {
32489
+ code: "conflict",
32490
+ message: `${runtime.displayName} does not expose per-agent capabilities.`
32491
+ });
32492
+ }
32493
+ return runtime.getAgentCapabilitySnapshot(query.agentId);
32494
+ });
30795
32495
  app2.post("/api/agent-runtimes/:provider/build-restart", async (request) => {
30796
32496
  if (!app2.services.config.managementRoutesEnabled) {
30797
32497
  throw new HttpError(403, {
@@ -30843,7 +32543,7 @@ async function refreshBackendInstallation(app2, runtime) {
30843
32543
  const [cliVersion, sdkVersion, latestCliVersion] = await Promise.all([
30844
32544
  commandVersion(command, ["--version"]),
30845
32545
  installedPackageVersion("@anthropic-ai/claude-agent-sdk", [
30846
- path21.join(repositoryRoot, "packages", "claude", "node_modules")
32546
+ path23.join(repositoryRoot, "packages", "claude", "node_modules")
30847
32547
  ]),
30848
32548
  latestPackageVersion("@anthropic-ai/claude-code")
30849
32549
  ]);
@@ -30862,7 +32562,7 @@ async function refreshBackendInstallation(app2, runtime) {
30862
32562
  commandVersion(command, ["--version"]),
30863
32563
  latestPackageVersion("opencode-ai"),
30864
32564
  installedPackageVersion("@opencode-ai/sdk", [
30865
- path21.join(repositoryRoot, "packages", "opencode", "node_modules")
32565
+ path23.join(repositoryRoot, "packages", "opencode", "node_modules")
30866
32566
  ])
30867
32567
  ]);
30868
32568
  runtime.installation.installed = Boolean(cliVersion && sdkVersion);
@@ -30881,13 +32581,13 @@ async function refreshBackendInstallation(app2, runtime) {
30881
32581
  async function installedPackageVersion(packageName, extraNodeModuleRoots = []) {
30882
32582
  const globalRoot = await npmGlobalRoot3();
30883
32583
  if (globalRoot) {
30884
- const global = await packageVersionFromPath(path21.join(globalRoot, packageName, "package.json"));
32584
+ const global = await packageVersionFromPath(path23.join(globalRoot, packageName, "package.json"));
30885
32585
  if (global) {
30886
32586
  return global;
30887
32587
  }
30888
32588
  }
30889
32589
  for (const root of extraNodeModuleRoots) {
30890
- const local = await packageVersionFromPath(path21.join(root, packageName, "package.json"));
32590
+ const local = await packageVersionFromPath(path23.join(root, packageName, "package.json"));
30891
32591
  if (local) {
30892
32592
  return local;
30893
32593
  }
@@ -30940,11 +32640,11 @@ function versionStringContains(installedVersion, latestVersion) {
30940
32640
  return Boolean(installedVersion && latestVersion && installedVersion.includes(latestVersion));
30941
32641
  }
30942
32642
  async function packageVersionFromNode(packageName) {
30943
- return packageVersionFromPath(path21.resolve("node_modules", packageName, "package.json"));
32643
+ return packageVersionFromPath(path23.resolve("node_modules", packageName, "package.json"));
30944
32644
  }
30945
32645
  async function packageVersionFromPath(packageJsonPath) {
30946
32646
  try {
30947
- const parsed = JSON.parse(await fs18.readFile(packageJsonPath, "utf8"));
32647
+ const parsed = JSON.parse(await fs20.readFile(packageJsonPath, "utf8"));
30948
32648
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
30949
32649
  } catch {
30950
32650
  return null;
@@ -30976,7 +32676,7 @@ async function npmGlobalBin(platform = process.platform) {
30976
32676
  return firstLine(result.stdout);
30977
32677
  }
30978
32678
  const prefix = await npmGlobalPrefix();
30979
- return prefix ? platform === "win32" ? prefix : path21.join(prefix, "bin") : null;
32679
+ return prefix ? platform === "win32" ? prefix : path23.join(prefix, "bin") : null;
30980
32680
  }
30981
32681
  async function npmGlobalPrefix() {
30982
32682
  const result = await runProcess({
@@ -31271,8 +32971,8 @@ async function registerSystemRoutes(app2) {
31271
32971
  }
31272
32972
 
31273
32973
  // src/routes/threads.ts
31274
- import fs19 from "fs/promises";
31275
- import path22 from "path";
32974
+ import fs21 from "fs/promises";
32975
+ import path24 from "path";
31276
32976
  import { z as z8 } from "zod";
31277
32977
  var reasoningEffortValues = [
31278
32978
  "none",
@@ -31332,6 +33032,10 @@ var importThreadSchema = z8.object({
31332
33032
  sessionId: z8.string().min(1),
31333
33033
  provider: agentBackendIdSchema.optional()
31334
33034
  });
33035
+ var importThreadCandidatesQuerySchema = z8.object({
33036
+ provider: agentBackendIdSchema.optional(),
33037
+ agentId: z8.string().min(1).optional()
33038
+ });
31335
33039
  var resumeThreadSchema = z8.object({
31336
33040
  model: z8.string().min(1).optional()
31337
33041
  });
@@ -31563,6 +33267,13 @@ async function registerThreadRoutes(app2) {
31563
33267
  };
31564
33268
  return app2.services.threadService.importThread(input);
31565
33269
  });
33270
+ app2.get("/api/threads/import-candidates", async (request) => {
33271
+ const query = importThreadCandidatesQuerySchema.parse(request.query);
33272
+ return app2.services.threadService.listImportCandidates(
33273
+ query.provider,
33274
+ query.agentId
33275
+ );
33276
+ });
31566
33277
  app2.get("/api/threads/:id", async (request) => {
31567
33278
  const params = z8.object({ id: z8.string().uuid() }).parse(request.params);
31568
33279
  const query = threadDetailQuerySchema.parse(request.query);
@@ -31645,15 +33356,15 @@ async function registerThreadRoutes(app2) {
31645
33356
  message: "Workspace was not found for this thread."
31646
33357
  });
31647
33358
  }
31648
- const candidatePath = path22.isAbsolute(query.path) ? query.path : path22.resolve(workspace.absPath, query.path);
31649
- const requestedPath = await fs19.realpath(candidatePath).catch(() => null);
33359
+ const candidatePath = path24.isAbsolute(query.path) ? query.path : path24.resolve(workspace.absPath, query.path);
33360
+ const requestedPath = await fs21.realpath(candidatePath).catch(() => null);
31650
33361
  if (!requestedPath) {
31651
33362
  throw new HttpError(404, {
31652
33363
  code: "not_found",
31653
33364
  message: "Image file was not found."
31654
33365
  });
31655
33366
  }
31656
- const resolvedWorkspaceRoot = await fs19.realpath(app2.services.config.workspaceRoot).catch(() => path22.resolve(app2.services.config.workspaceRoot));
33367
+ const resolvedWorkspaceRoot = await fs21.realpath(app2.services.config.workspaceRoot).catch(() => path24.resolve(app2.services.config.workspaceRoot));
31657
33368
  try {
31658
33369
  await assertPathWithinRoot(resolvedWorkspaceRoot, requestedPath);
31659
33370
  } catch {
@@ -31662,7 +33373,7 @@ async function registerThreadRoutes(app2) {
31662
33373
  message: "Image path must stay within the configured workspace root."
31663
33374
  });
31664
33375
  }
31665
- const stats = await fs19.stat(requestedPath).catch(() => null);
33376
+ const stats = await fs21.stat(requestedPath).catch(() => null);
31666
33377
  if (!stats?.isFile()) {
31667
33378
  throw new HttpError(404, {
31668
33379
  code: "not_found",
@@ -31673,7 +33384,7 @@ async function registerThreadRoutes(app2) {
31673
33384
  const contentType = lowerPath.endsWith(".png") ? "image/png" : lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg") ? "image/jpeg" : lowerPath.endsWith(".gif") ? "image/gif" : lowerPath.endsWith(".webp") ? "image/webp" : lowerPath.endsWith(".svg") ? "image/svg+xml" : lowerPath.endsWith(".heic") ? "image/heic" : lowerPath.endsWith(".heif") ? "image/heif" : "application/octet-stream";
31674
33385
  reply.header("Content-Type", contentType);
31675
33386
  reply.header("Cache-Control", "private, max-age=60");
31676
- return reply.send(await fs19.readFile(requestedPath));
33387
+ return reply.send(await fs21.readFile(requestedPath));
31677
33388
  });
31678
33389
  app2.patch("/api/threads/:id", async (request) => {
31679
33390
  const params = z8.object({ id: z8.string().uuid() }).parse(request.params);
@@ -31869,34 +33580,34 @@ async function registerThreadRoutes(app2) {
31869
33580
  }
31870
33581
 
31871
33582
  // src/routes/workspaces.ts
31872
- import fs22 from "fs/promises";
33583
+ import fs24 from "fs/promises";
31873
33584
  import { createReadStream } from "fs";
31874
- import path25 from "path";
33585
+ import path27 from "path";
31875
33586
  import { Readable as Readable2 } from "stream";
31876
33587
  import { z as z9 } from "zod";
31877
33588
 
31878
33589
  // src/workspace-artifact-service.ts
31879
- import fs20 from "fs/promises";
31880
- import path23 from "path";
33590
+ import fs22 from "fs/promises";
33591
+ import path25 from "path";
31881
33592
  function artifactRoot(record2) {
31882
- return path23.join(record2.absPath, ".remote-codex", "artifacts");
33593
+ return path25.join(record2.absPath, ".remote-codex", "artifacts");
31883
33594
  }
31884
33595
  function artifactFilePath(record2, artifactId) {
31885
- return path23.join(artifactRoot(record2), artifactId, "artifact.bin");
33596
+ return path25.join(artifactRoot(record2), artifactId, "artifact.bin");
31886
33597
  }
31887
33598
  function artifactMetadataPath(record2, artifactId) {
31888
- return path23.join(artifactRoot(record2), artifactId, "metadata.json");
33599
+ return path25.join(artifactRoot(record2), artifactId, "metadata.json");
31889
33600
  }
31890
33601
  function safeArtifactFileName(value) {
31891
- return path23.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
33602
+ return path25.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
31892
33603
  }
31893
33604
  function artifactIdFromName(name) {
31894
- const base = path23.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
33605
+ const base = path25.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
31895
33606
  return `${base || "artifact"}-${Date.now().toString(36)}`;
31896
33607
  }
31897
33608
  async function readWorkspaceArtifactMetadata(record2, artifactId) {
31898
33609
  try {
31899
- const raw = await fs20.readFile(artifactMetadataPath(record2, artifactId), "utf8");
33610
+ const raw = await fs22.readFile(artifactMetadataPath(record2, artifactId), "utf8");
31900
33611
  return JSON.parse(raw);
31901
33612
  } catch (error) {
31902
33613
  if (error.code === "ENOENT") {
@@ -31911,7 +33622,7 @@ async function readWorkspaceArtifactMetadata(record2, artifactId) {
31911
33622
  async function listWorkspaceArtifacts(record2) {
31912
33623
  let entries;
31913
33624
  try {
31914
- entries = await fs20.readdir(artifactRoot(record2));
33625
+ entries = await fs22.readdir(artifactRoot(record2));
31915
33626
  } catch (error) {
31916
33627
  if (error.code === "ENOENT") {
31917
33628
  return [];
@@ -31947,10 +33658,10 @@ async function createWorkspaceArtifact({
31947
33658
  message: "Artifact content must not be empty."
31948
33659
  });
31949
33660
  }
31950
- const dir = path23.dirname(artifactFilePath(record2, artifactId));
31951
- await fs20.mkdir(dir, { recursive: true, mode: 448 });
33661
+ const dir = path25.dirname(artifactFilePath(record2, artifactId));
33662
+ await fs22.mkdir(dir, { recursive: true, mode: 448 });
31952
33663
  const filePath = artifactFilePath(record2, artifactId);
31953
- await fs20.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
33664
+ await fs22.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
31954
33665
  if (error.code === "EEXIST") {
31955
33666
  throw new HttpError(409, {
31956
33667
  code: "conflict",
@@ -31970,12 +33681,12 @@ async function createWorkspaceArtifact({
31970
33681
  updatedAt: now,
31971
33682
  metadata: metadata ?? {}
31972
33683
  };
31973
- await fs20.writeFile(artifactMetadataPath(record2, artifactId), JSON.stringify(artifact, null, 2));
33684
+ await fs22.writeFile(artifactMetadataPath(record2, artifactId), JSON.stringify(artifact, null, 2));
31974
33685
  return artifact;
31975
33686
  }
31976
33687
  async function readWorkspaceArtifactContent(record2, artifactId) {
31977
33688
  try {
31978
- return await fs20.readFile(artifactFilePath(record2, artifactId));
33689
+ return await fs22.readFile(artifactFilePath(record2, artifactId));
31979
33690
  } catch (error) {
31980
33691
  if (error.code === "ENOENT") {
31981
33692
  throw new HttpError(404, {
@@ -31988,7 +33699,7 @@ async function readWorkspaceArtifactContent(record2, artifactId) {
31988
33699
  }
31989
33700
  async function deleteWorkspaceArtifact(record2, artifactId) {
31990
33701
  const artifact = await readWorkspaceArtifactMetadata(record2, artifactId);
31991
- await fs20.rm(path23.dirname(artifactFilePath(record2, artifactId)), {
33702
+ await fs22.rm(path25.dirname(artifactFilePath(record2, artifactId)), {
31992
33703
  recursive: true,
31993
33704
  force: true
31994
33705
  });
@@ -31996,9 +33707,9 @@ async function deleteWorkspaceArtifact(record2, artifactId) {
31996
33707
  }
31997
33708
 
31998
33709
  // src/workspace-file-service.ts
31999
- import fs21 from "fs/promises";
33710
+ import fs23 from "fs/promises";
32000
33711
  import os3 from "os";
32001
- import path24 from "path";
33712
+ import path26 from "path";
32002
33713
  var PREVIEW_DEFAULT_LIMIT_BYTES = 5e4;
32003
33714
  var WORKSPACE_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
32004
33715
  var WORKSPACE_FOLDER_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
@@ -32034,7 +33745,7 @@ function toWorkspaceFileDto(file) {
32034
33745
  };
32035
33746
  }
32036
33747
  function languageForPath(filePath) {
32037
- const extension = path24.extname(filePath).slice(1).toLowerCase();
33748
+ const extension = path26.extname(filePath).slice(1).toLowerCase();
32038
33749
  switch (extension) {
32039
33750
  case "js":
32040
33751
  case "jsx":
@@ -32080,18 +33791,18 @@ function languageForPath(filePath) {
32080
33791
  }
32081
33792
  }
32082
33793
  function relativeWorkspacePath(rootPath, absPath) {
32083
- const relative = path24.relative(rootPath, absPath);
32084
- return relative === "" ? "" : relative.split(path24.sep).join("/");
33794
+ const relative = path26.relative(rootPath, absPath);
33795
+ return relative === "" ? "" : relative.split(path26.sep).join("/");
32085
33796
  }
32086
33797
  async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
32087
- const candidate = path24.resolve(rootPath, relativePath || ".");
33798
+ const candidate = path26.resolve(rootPath, relativePath || ".");
32088
33799
  const comparable = await assertPathWithinRoot(rootPath, candidate);
32089
33800
  return comparable;
32090
33801
  }
32091
33802
  async function buildWorkspaceTreeNode(rootPath, absPath) {
32092
- const stats = await fs21.stat(absPath);
33803
+ const stats = await fs23.stat(absPath);
32093
33804
  const relativePath = relativeWorkspacePath(rootPath, absPath);
32094
- const name = relativePath ? path24.basename(absPath) : path24.basename(rootPath);
33805
+ const name = relativePath ? path26.basename(absPath) : path26.basename(rootPath);
32095
33806
  if (!stats.isDirectory()) {
32096
33807
  return {
32097
33808
  name,
@@ -32109,7 +33820,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
32109
33820
  };
32110
33821
  const visible = [];
32111
33822
  try {
32112
- const directory = await fs21.opendir(absPath);
33823
+ const directory = await fs23.opendir(absPath);
32113
33824
  let scanned = 0;
32114
33825
  for await (const entry of directory) {
32115
33826
  scanned += 1;
@@ -32144,7 +33855,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
32144
33855
  });
32145
33856
  node.children = (await Promise.all(
32146
33857
  visible.map(async (entry) => {
32147
- const childPath = path24.join(absPath, entry.name);
33858
+ const childPath = path26.join(absPath, entry.name);
32148
33859
  try {
32149
33860
  const childRelativePath = relativeWorkspacePath(rootPath, childPath);
32150
33861
  if (entry.isDirectory()) {
@@ -32156,7 +33867,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
32156
33867
  childrenLoaded: false
32157
33868
  };
32158
33869
  }
32159
- const childStats = await fs21.stat(childPath);
33870
+ const childStats = await fs23.stat(childPath);
32160
33871
  return {
32161
33872
  name: entry.name,
32162
33873
  path: childRelativePath,
@@ -32171,7 +33882,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
32171
33882
  return node;
32172
33883
  }
32173
33884
  function contentTypeForPath(filePath) {
32174
- switch (path24.extname(filePath).slice(1).toLowerCase()) {
33885
+ switch (path26.extname(filePath).slice(1).toLowerCase()) {
32175
33886
  case "png":
32176
33887
  return "image/png";
32177
33888
  case "jpg":
@@ -32201,15 +33912,15 @@ function contentTypeForPath(filePath) {
32201
33912
  }
32202
33913
  }
32203
33914
  async function collectFolderZipEntries(rootPath, folderPath) {
32204
- const folderName = path24.basename(folderPath) || "workspace-folder";
33915
+ const folderName = path26.basename(folderPath) || "workspace-folder";
32205
33916
  const entries = [];
32206
33917
  let totalBytes = 0;
32207
33918
  const pending = [folderPath];
32208
33919
  while (pending.length > 0) {
32209
33920
  const current = pending.pop();
32210
- const children = await fs21.readdir(current, { withFileTypes: true });
33921
+ const children = await fs23.readdir(current, { withFileTypes: true });
32211
33922
  for (const child of children) {
32212
- const childPath = await resolveWorkspaceItemPath(rootPath, path24.relative(rootPath, path24.join(current, child.name)));
33923
+ const childPath = await resolveWorkspaceItemPath(rootPath, path26.relative(rootPath, path26.join(current, child.name)));
32213
33924
  if (child.isDirectory()) {
32214
33925
  pending.push(childPath);
32215
33926
  continue;
@@ -32217,7 +33928,7 @@ async function collectFolderZipEntries(rootPath, folderPath) {
32217
33928
  if (!child.isFile()) {
32218
33929
  continue;
32219
33930
  }
32220
- const stats = await fs21.stat(childPath);
33931
+ const stats = await fs23.stat(childPath);
32221
33932
  totalBytes += stats.size;
32222
33933
  entries.push({
32223
33934
  absPath: childPath,
@@ -32268,8 +33979,8 @@ async function createFolderZipFile(rootPath, folderPath) {
32268
33979
  const centralParts = [];
32269
33980
  let offset = 0;
32270
33981
  for (const entry of entries) {
32271
- const data = await fs21.readFile(entry.absPath);
32272
- const name = Buffer.from(entry.archivePath.split(path24.sep).join("/"), "utf8");
33982
+ const data = await fs23.readFile(entry.absPath);
33983
+ const name = Buffer.from(entry.archivePath.split(path26.sep).join("/"), "utf8");
32273
33984
  const checksum = crc32(data);
32274
33985
  const { dosDate, dosTime } = zipDosDateTime(entry.updatedAt);
32275
33986
  const localHeader = Buffer.alloc(30);
@@ -32316,19 +34027,19 @@ async function createFolderZipFile(rootPath, folderPath) {
32316
34027
  endRecord.writeUInt32LE(centralSize, 12);
32317
34028
  endRecord.writeUInt32LE(offset, 16);
32318
34029
  endRecord.writeUInt16LE(0, 20);
32319
- const tempDir = await fs21.mkdtemp(path24.join(os3.tmpdir(), "remote-codex-folder-download-"));
32320
- const zipPath = path24.join(tempDir, `${path24.basename(folderPath) || "workspace-folder"}.zip`);
32321
- await fs21.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
34030
+ const tempDir = await fs23.mkdtemp(path26.join(os3.tmpdir(), "remote-codex-folder-download-"));
34031
+ const zipPath = path26.join(tempDir, `${path26.basename(folderPath) || "workspace-folder"}.zip`);
34032
+ await fs23.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
32322
34033
  return { zipPath, tempDir };
32323
34034
  }
32324
34035
  function cleanupTemporaryZip(zipPath, tempDir) {
32325
34036
  return async () => {
32326
- await fs21.rm(zipPath, { force: true }).catch(() => void 0);
32327
- await fs21.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
34037
+ await fs23.rm(zipPath, { force: true }).catch(() => void 0);
34038
+ await fs23.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
32328
34039
  };
32329
34040
  }
32330
34041
  function sanitizeUploadFilename(filename) {
32331
- const baseName = path24.basename(filename?.trim() || "upload");
34042
+ const baseName = path26.basename(filename?.trim() || "upload");
32332
34043
  if (!baseName || baseName === "." || baseName === "..") {
32333
34044
  return "upload";
32334
34045
  }
@@ -32340,7 +34051,7 @@ function inferGitRepoName(gitUrl) {
32340
34051
  const normalized = withoutQuery.replace(/[\\/]+$/, "");
32341
34052
  const rawName = normalized.split(/[\\/:]/).filter(Boolean).at(-1) ?? "";
32342
34053
  const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
32343
- if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path24.sep)) {
34054
+ if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path26.sep)) {
32344
34055
  throw new HttpError(400, {
32345
34056
  code: "bad_request",
32346
34057
  message: "Unable to infer a target directory from the Git URL."
@@ -32350,7 +34061,7 @@ function inferGitRepoName(gitUrl) {
32350
34061
  }
32351
34062
  async function pathExists4(absPath) {
32352
34063
  try {
32353
- await fs21.stat(absPath);
34064
+ await fs23.stat(absPath);
32354
34065
  return true;
32355
34066
  } catch (error) {
32356
34067
  if (error.code === "ENOENT") {
@@ -32474,7 +34185,7 @@ async function registerWorkspaceRoutes(app2) {
32474
34185
  });
32475
34186
  app2.get("/api/workspaces/tree", async (request) => {
32476
34187
  const query = treeQuerySchema.parse(request.query);
32477
- const requestedPath = query.path ? path25.resolve(query.path) : app2.services.config.workspaceRoot;
34188
+ const requestedPath = query.path ? path27.resolve(query.path) : app2.services.config.workspaceRoot;
32478
34189
  const tree = await readWorkspaceTree({
32479
34190
  rootPath: app2.services.config.workspaceRoot,
32480
34191
  targetPath: requestedPath,
@@ -32501,7 +34212,7 @@ async function registerWorkspaceRoutes(app2) {
32501
34212
  const params = z9.object({ id: z9.string().uuid() }).parse(request.params);
32502
34213
  const query = workspaceFileQuerySchema.parse(request.query);
32503
34214
  const record2 = requireWorkspaceRecord(app2, params.id);
32504
- const rootPath = await fs22.realpath(record2.absPath);
34215
+ const rootPath = await fs24.realpath(record2.absPath);
32505
34216
  const targetPath = await resolveWorkspaceItemPath(rootPath, query.path);
32506
34217
  return buildWorkspaceTreeNode(rootPath, targetPath);
32507
34218
  });
@@ -32520,9 +34231,9 @@ async function registerWorkspaceRoutes(app2) {
32520
34231
  const params = z9.object({ id: z9.string().uuid() }).parse(request.params);
32521
34232
  const query = workspacePreviewQuerySchema.parse(request.query);
32522
34233
  const record2 = requireWorkspaceRecord(app2, params.id);
32523
- const rootPath = await fs22.realpath(record2.absPath);
34234
+ const rootPath = await fs24.realpath(record2.absPath);
32524
34235
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
32525
- const stats = await fs22.stat(filePath);
34236
+ const stats = await fs24.stat(filePath);
32526
34237
  if (!stats.isFile()) {
32527
34238
  throw new HttpError(400, {
32528
34239
  code: "bad_request",
@@ -32531,7 +34242,7 @@ async function registerWorkspaceRoutes(app2) {
32531
34242
  }
32532
34243
  const offset = query.offset ?? 0;
32533
34244
  const limit = query.limit ?? PREVIEW_DEFAULT_LIMIT_BYTES;
32534
- const handle = await fs22.open(filePath, "r");
34245
+ const handle = await fs24.open(filePath, "r");
32535
34246
  try {
32536
34247
  const length = Math.min(limit, Math.max(0, stats.size - offset));
32537
34248
  const buffer = Buffer.alloc(length);
@@ -32539,7 +34250,7 @@ async function registerWorkspaceRoutes(app2) {
32539
34250
  const nextOffset = offset + read.bytesRead;
32540
34251
  return {
32541
34252
  path: relativeWorkspacePath(rootPath, filePath),
32542
- name: path25.basename(filePath),
34253
+ name: path27.basename(filePath),
32543
34254
  content: buffer.subarray(0, read.bytesRead).toString("utf8"),
32544
34255
  language: languageForPath(filePath),
32545
34256
  size: stats.size,
@@ -32554,9 +34265,9 @@ async function registerWorkspaceRoutes(app2) {
32554
34265
  const params = z9.object({ id: z9.string().uuid() }).parse(request.params);
32555
34266
  const query = workspacePreviewQuerySchema.pick({ path: true }).parse(request.query);
32556
34267
  const record2 = requireWorkspaceRecord(app2, params.id);
32557
- const rootPath = await fs22.realpath(record2.absPath);
34268
+ const rootPath = await fs24.realpath(record2.absPath);
32558
34269
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
32559
- const stats = await fs22.stat(filePath);
34270
+ const stats = await fs24.stat(filePath);
32560
34271
  if (!stats.isFile()) {
32561
34272
  throw new HttpError(400, {
32562
34273
  code: "bad_request",
@@ -32564,18 +34275,18 @@ async function registerWorkspaceRoutes(app2) {
32564
34275
  });
32565
34276
  }
32566
34277
  reply.header("content-type", contentTypeForPath(filePath));
32567
- return reply.send(Readable2.from(await fs22.readFile(filePath)));
34278
+ return reply.send(Readable2.from(await fs24.readFile(filePath)));
32568
34279
  });
32569
34280
  app2.get("/api/workspaces/:id/files/download", async (request, reply) => {
32570
34281
  const params = z9.object({ id: z9.string().uuid() }).parse(request.params);
32571
34282
  const query = workspaceFileQuerySchema.parse(request.query);
32572
34283
  const record2 = requireWorkspaceRecord(app2, params.id);
32573
- const rootPath = await fs22.realpath(record2.absPath);
34284
+ const rootPath = await fs24.realpath(record2.absPath);
32574
34285
  const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
32575
- const stats = await fs22.stat(itemPath);
34286
+ const stats = await fs24.stat(itemPath);
32576
34287
  if (stats.isDirectory()) {
32577
34288
  const { zipPath, tempDir } = await createFolderZipFile(rootPath, itemPath);
32578
- const filename2 = `${path25.basename(itemPath) || "workspace-folder"}.zip`;
34289
+ const filename2 = `${path27.basename(itemPath) || "workspace-folder"}.zip`;
32579
34290
  const cleanup = cleanupTemporaryZip(zipPath, tempDir);
32580
34291
  reply.raw.once("finish", () => void cleanup());
32581
34292
  reply.raw.once("close", () => void cleanup());
@@ -32591,17 +34302,17 @@ async function registerWorkspaceRoutes(app2) {
32591
34302
  message: "Only file and folder downloads are supported from this endpoint."
32592
34303
  });
32593
34304
  }
32594
- const filename = path25.basename(itemPath);
34305
+ const filename = path27.basename(itemPath);
32595
34306
  reply.header("content-type", contentTypeForPath(itemPath)).header(
32596
34307
  "content-disposition",
32597
34308
  `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
32598
34309
  );
32599
- return reply.send(Readable2.from(await fs22.readFile(itemPath)));
34310
+ return reply.send(Readable2.from(await fs24.readFile(itemPath)));
32600
34311
  });
32601
34312
  app2.post("/api/workspaces/:id/files/upload", async (request) => {
32602
34313
  const params = z9.object({ id: z9.string().uuid() }).parse(request.params);
32603
34314
  const record2 = requireWorkspaceRecord(app2, params.id);
32604
- const rootPath = await fs22.realpath(record2.absPath);
34315
+ const rootPath = await fs24.realpath(record2.absPath);
32605
34316
  const uploadRequest = request;
32606
34317
  if (!uploadRequest.isMultipart()) {
32607
34318
  throw new HttpError(400, {
@@ -32656,7 +34367,7 @@ async function registerWorkspaceRoutes(app2) {
32656
34367
  kind: "file",
32657
34368
  file: {
32658
34369
  path: file.path,
32659
- name: path25.basename(file.path),
34370
+ name: path27.basename(file.path),
32660
34371
  size: file.size
32661
34372
  }
32662
34373
  };
@@ -32732,7 +34443,7 @@ async function registerWorkspaceRoutes(app2) {
32732
34443
  let validated;
32733
34444
  if ("gitUrl" in body) {
32734
34445
  const repoName = inferGitRepoName(body.gitUrl);
32735
- const targetPath = path25.join(settings.devHome, repoName);
34446
+ const targetPath = path27.join(settings.devHome, repoName);
32736
34447
  if (await pathExists4(targetPath)) {
32737
34448
  throw new HttpError(409, {
32738
34449
  code: "conflict",
@@ -32746,14 +34457,14 @@ async function registerWorkspaceRoutes(app2) {
32746
34457
  validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath);
32747
34458
  } else {
32748
34459
  const requestedPath = body.absPath.trim();
32749
- const isWorkspaceName = !path25.isAbsolute(requestedPath) && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(requestedPath) && requestedPath !== "." && requestedPath !== "..";
32750
- if (!path25.isAbsolute(requestedPath) && !isWorkspaceName) {
34460
+ const isWorkspaceName = !path27.isAbsolute(requestedPath) && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(requestedPath) && requestedPath !== "." && requestedPath !== "..";
34461
+ if (!path27.isAbsolute(requestedPath) && !isWorkspaceName) {
32751
34462
  throw new HttpError(400, {
32752
34463
  code: "bad_request",
32753
34464
  message: "Use a simple directory name, an absolute path, or a Git URL."
32754
34465
  });
32755
34466
  }
32756
- const targetPath = isWorkspaceName ? path25.join(settings.devHome, requestedPath) : requestedPath;
34467
+ const targetPath = isWorkspaceName ? path27.join(settings.devHome, requestedPath) : requestedPath;
32757
34468
  validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath, {
32758
34469
  devHome: settings.devHome,
32759
34470
  createMissingLeaf: true
@@ -32860,8 +34571,8 @@ async function registerWorkspaceRoutes(app2) {
32860
34571
  import { z as z10 } from "zod";
32861
34572
 
32862
34573
  // src/plugins/plugin-service.ts
32863
- import fs23 from "fs/promises";
32864
- import path26 from "path";
34574
+ import fs25 from "fs/promises";
34575
+ import path28 from "path";
32865
34576
  var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
32866
34577
  var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
32867
34578
  var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
@@ -32882,7 +34593,7 @@ function normalizeManagedCommand(server, repoRoot) {
32882
34593
  if (server.name === "remote_codex_plugins") {
32883
34594
  return {
32884
34595
  command: process.execPath,
32885
- args: [path26.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
34596
+ args: [path28.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
32886
34597
  };
32887
34598
  }
32888
34599
  return {
@@ -33081,10 +34792,10 @@ var PluginService = class {
33081
34792
  if (!input.codexHome) {
33082
34793
  return;
33083
34794
  }
33084
- const configPath = path26.join(input.codexHome, "config.toml");
34795
+ const configPath = path28.join(input.codexHome, "config.toml");
33085
34796
  let current = "";
33086
34797
  try {
33087
- current = await fs23.readFile(configPath, "utf8");
34798
+ current = await fs25.readFile(configPath, "utf8");
33088
34799
  } catch (error) {
33089
34800
  if (error.code !== "ENOENT") {
33090
34801
  throw error;
@@ -33099,8 +34810,8 @@ var PluginService = class {
33099
34810
  if (next === current) {
33100
34811
  return;
33101
34812
  }
33102
- await fs23.mkdir(path26.dirname(configPath), { recursive: true });
33103
- await fs23.writeFile(configPath, next, "utf8");
34813
+ await fs25.mkdir(path28.dirname(configPath), { recursive: true });
34814
+ await fs25.writeFile(configPath, next, "utf8");
33104
34815
  }
33105
34816
  async importPlugin(input) {
33106
34817
  const manifestInput = input.manifest ?? (input.manifestUrl ? await this.fetchManifestUrl(input.manifestUrl) : this.parseManifestJson(input.manifestJson));
@@ -33466,8 +35177,8 @@ async function registerAuthRoutes(app2) {
33466
35177
  }
33467
35178
 
33468
35179
  // src/provider-host-config-service.ts
33469
- import fs24 from "fs/promises";
33470
- import path27 from "path";
35180
+ import fs26 from "fs/promises";
35181
+ import path29 from "path";
33471
35182
  import { randomUUID as randomUUID7 } from "crypto";
33472
35183
  function providerError(message, statusCode = 404) {
33473
35184
  const error = new Error(message);
@@ -33475,23 +35186,23 @@ function providerError(message, statusCode = 404) {
33475
35186
  return error;
33476
35187
  }
33477
35188
  function resolveProviderHostFilePath(providerHome, name) {
33478
- return path27.join(providerHome, name);
35189
+ return path29.join(providerHome, name);
33479
35190
  }
33480
35191
  function resolveArchiveRoot(providerHome) {
33481
- return path27.join(providerHome, "supervisor-config-archives");
35192
+ return path29.join(providerHome, "supervisor-config-archives");
33482
35193
  }
33483
35194
  function resolveArchiveIndexPath(providerHome) {
33484
- return path27.join(resolveArchiveRoot(providerHome), "index.json");
35195
+ return path29.join(resolveArchiveRoot(providerHome), "index.json");
33485
35196
  }
33486
35197
  function resolveArchivePath(providerHome, archiveId) {
33487
- return path27.join(resolveArchiveRoot(providerHome), archiveId);
35198
+ return path29.join(resolveArchiveRoot(providerHome), archiveId);
33488
35199
  }
33489
35200
  function defaultArchiveLabel(createdAt) {
33490
35201
  return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
33491
35202
  }
33492
35203
  async function readArchiveIndex(providerHome) {
33493
35204
  try {
33494
- const raw = await fs24.readFile(resolveArchiveIndexPath(providerHome), "utf8");
35205
+ const raw = await fs26.readFile(resolveArchiveIndexPath(providerHome), "utf8");
33495
35206
  const parsed = JSON.parse(raw);
33496
35207
  return {
33497
35208
  archives: Array.isArray(parsed.archives) ? parsed.archives : []
@@ -33505,8 +35216,8 @@ async function readArchiveIndex(providerHome) {
33505
35216
  }
33506
35217
  async function writeArchiveIndex(providerHome, index) {
33507
35218
  const root = resolveArchiveRoot(providerHome);
33508
- await fs24.mkdir(root, { recursive: true });
33509
- await fs24.writeFile(
35219
+ await fs26.mkdir(root, { recursive: true });
35220
+ await fs26.writeFile(
33510
35221
  resolveArchiveIndexPath(providerHome),
33511
35222
  `${JSON.stringify(index, null, 2)}
33512
35223
  `,
@@ -33577,7 +35288,7 @@ var ProviderHostConfigService = class {
33577
35288
  const fileName = this.assertHostFile(provider2, name);
33578
35289
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
33579
35290
  try {
33580
- const content = await fs24.readFile(filePath, "utf8");
35291
+ const content = await fs26.readFile(filePath, "utf8");
33581
35292
  return {
33582
35293
  name: fileName,
33583
35294
  path: filePath,
@@ -33600,8 +35311,8 @@ var ProviderHostConfigService = class {
33600
35311
  const providerHome = this.providerHome(provider2);
33601
35312
  const fileName = this.assertHostFile(provider2, name);
33602
35313
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
33603
- await fs24.mkdir(path27.dirname(filePath), { recursive: true });
33604
- await fs24.writeFile(filePath, input.content, "utf8");
35314
+ await fs26.mkdir(path29.dirname(filePath), { recursive: true });
35315
+ await fs26.writeFile(filePath, input.content, "utf8");
33605
35316
  return this.readFile(provider2, fileName);
33606
35317
  }
33607
35318
  async listArchives(provider2) {
@@ -33627,7 +35338,7 @@ var ProviderHostConfigService = class {
33627
35338
  }
33628
35339
  ])
33629
35340
  );
33630
- await fs24.mkdir(archivePath, { recursive: true });
35341
+ await fs26.mkdir(archivePath, { recursive: true });
33631
35342
  for (const name of fileNames) {
33632
35343
  const hostFile = await this.readFile(provider2, name);
33633
35344
  files[name] = {
@@ -33635,7 +35346,7 @@ var ProviderHostConfigService = class {
33635
35346
  exists: hostFile.exists
33636
35347
  };
33637
35348
  if (hostFile.exists) {
33638
- await fs24.writeFile(path27.join(archivePath, name), hostFile.content, "utf8");
35349
+ await fs26.writeFile(path29.join(archivePath, name), hostFile.content, "utf8");
33639
35350
  }
33640
35351
  }
33641
35352
  const archive = {
@@ -33671,14 +35382,14 @@ var ProviderHostConfigService = class {
33671
35382
  const fileNames = this.archiveFileNames(provider2);
33672
35383
  const { archive } = await findArchiveOrThrow(providerHome, id);
33673
35384
  const archivePath = resolveArchivePath(providerHome, archive.id);
33674
- await fs24.mkdir(providerHome, { recursive: true });
35385
+ await fs26.mkdir(providerHome, { recursive: true });
33675
35386
  for (const name of fileNames) {
33676
35387
  const hostPath = resolveProviderHostFilePath(providerHome, name);
33677
35388
  if (archive.files[name]?.exists) {
33678
- const content = await fs24.readFile(path27.join(archivePath, name), "utf8");
33679
- await fs24.writeFile(hostPath, content, "utf8");
35389
+ const content = await fs26.readFile(path29.join(archivePath, name), "utf8");
35390
+ await fs26.writeFile(hostPath, content, "utf8");
33680
35391
  } else {
33681
- await fs24.rm(hostPath, { force: true });
35392
+ await fs26.rm(hostPath, { force: true });
33682
35393
  }
33683
35394
  }
33684
35395
  await runtime.stop();
@@ -33691,12 +35402,12 @@ var ProviderHostConfigService = class {
33691
35402
  };
33692
35403
 
33693
35404
  // src/shell/shell-session-service.ts
33694
- import fs26 from "fs/promises";
35405
+ import fs28 from "fs/promises";
33695
35406
 
33696
35407
  // src/shell/shell-prompt.ts
33697
- import fs25 from "fs/promises";
35408
+ import fs27 from "fs/promises";
33698
35409
  import os4 from "os";
33699
- import path28 from "path";
35410
+ import path30 from "path";
33700
35411
  function basenameFromPath2(filePath) {
33701
35412
  if (!filePath) {
33702
35413
  return "";
@@ -33705,7 +35416,7 @@ function basenameFromPath2(filePath) {
33705
35416
  if (!normalized) {
33706
35417
  return "";
33707
35418
  }
33708
- return path28.basename(normalized) || normalized;
35419
+ return path30.basename(normalized) || normalized;
33709
35420
  }
33710
35421
  function isInteractiveShellCommand(command) {
33711
35422
  const normalized = (command ?? "").trim().toLowerCase();
@@ -33866,11 +35577,11 @@ function buildShellPromptInitScriptContents(command) {
33866
35577
  async function ensureShellPromptInitScript(command) {
33867
35578
  const normalized = command.trim().toLowerCase();
33868
35579
  const extension = normalized === "zsh" ? "zsh" : "sh";
33869
- const filePath = path28.join(
35580
+ const filePath = path30.join(
33870
35581
  os4.tmpdir(),
33871
35582
  `remote-codex-shell-prompt.${extension}`
33872
35583
  );
33873
- await fs25.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
35584
+ await fs27.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
33874
35585
  return filePath;
33875
35586
  }
33876
35587
  async function buildShellPromptInitCommand(command, options = {}) {
@@ -33886,7 +35597,7 @@ clear
33886
35597
  // src/shell/shell-session-service.ts
33887
35598
  async function pathExists5(filePath) {
33888
35599
  try {
33889
- await fs26.access(filePath);
35600
+ await fs28.access(filePath);
33890
35601
  return true;
33891
35602
  } catch {
33892
35603
  return false;
@@ -34623,19 +36334,19 @@ var BackendPluginHost = class {
34623
36334
 
34624
36335
  // src/shell/pty-shell-backend.ts
34625
36336
  import { createRequire as createRequire4 } from "module";
34626
- import path29 from "path";
36337
+ import path31 from "path";
34627
36338
 
34628
36339
  // src/shell/default-shell.ts
34629
- import fs27 from "fs";
36340
+ import fs29 from "fs";
34630
36341
  var POSIX_SHELL_CANDIDATES = ["/bin/bash", "/usr/bin/bash", "/bin/sh"];
34631
36342
  function resolveDefaultShell(env = process.env, platform = process.platform) {
34632
36343
  if (platform === "win32") {
34633
36344
  return env.COMSPEC ?? "cmd.exe";
34634
36345
  }
34635
- if (env.SHELL && fs27.existsSync(env.SHELL)) {
36346
+ if (env.SHELL && fs29.existsSync(env.SHELL)) {
34636
36347
  return env.SHELL;
34637
36348
  }
34638
- return POSIX_SHELL_CANDIDATES.find((candidate) => fs27.existsSync(candidate)) ?? "/bin/sh";
36349
+ return POSIX_SHELL_CANDIDATES.find((candidate) => fs29.existsSync(candidate)) ?? "/bin/sh";
34639
36350
  }
34640
36351
 
34641
36352
  // src/shell/pty-shell-backend.ts
@@ -34647,7 +36358,7 @@ function spawnPty(...args) {
34647
36358
  return nodePty.spawn(...args);
34648
36359
  }
34649
36360
  function shellArgs(shell) {
34650
- const shellName = path29.basename(shell).toLowerCase();
36361
+ const shellName = path31.basename(shell).toLowerCase();
34651
36362
  if (process.platform === "win32") {
34652
36363
  return [];
34653
36364
  }
@@ -34669,7 +36380,7 @@ function lastVisibleLine(snapshot) {
34669
36380
  }
34670
36381
  function inferRuntime(session) {
34671
36382
  const promptLine = lastVisibleLine(session.scrollback);
34672
- const shell = path29.basename(session.shell);
36383
+ const shell = path31.basename(session.shell);
34673
36384
  const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
34674
36385
  return {
34675
36386
  panePid: session.pty.pid,
@@ -34815,8 +36526,8 @@ var PtyShellBackend = class {
34815
36526
  };
34816
36527
 
34817
36528
  // src/shell/tmux-manager.ts
34818
- import fs28 from "fs";
34819
- import path30 from "path";
36529
+ import fs30 from "fs";
36530
+ import path32 from "path";
34820
36531
  import { spawn as spawnChild } from "child_process";
34821
36532
  async function defaultExecCommand(command, args) {
34822
36533
  return await new Promise((resolve, reject) => {
@@ -34843,17 +36554,17 @@ async function defaultExecCommand(command, args) {
34843
36554
  });
34844
36555
  }
34845
36556
  function resolveExecutablePath(command) {
34846
- if (command.includes(path30.sep)) {
36557
+ if (command.includes(path32.sep)) {
34847
36558
  return command;
34848
36559
  }
34849
36560
  const searchPath = process.env.PATH ?? "";
34850
- for (const entry of searchPath.split(path30.delimiter)) {
36561
+ for (const entry of searchPath.split(path32.delimiter)) {
34851
36562
  const trimmed = entry.trim();
34852
36563
  if (!trimmed) {
34853
36564
  continue;
34854
36565
  }
34855
- const candidate = path30.join(trimmed, command);
34856
- if (fs28.existsSync(candidate)) {
36566
+ const candidate = path32.join(trimmed, command);
36567
+ if (fs30.existsSync(candidate)) {
34857
36568
  return candidate;
34858
36569
  }
34859
36570
  }
@@ -35331,13 +37042,17 @@ var UnsupportedShellBackend = class {
35331
37042
  async listSessionNames() {
35332
37043
  return [];
35333
37044
  }
35334
- async hasSession(_sessionId) {
37045
+ async hasSession(sessionId) {
37046
+ void sessionId;
35335
37047
  return false;
35336
37048
  }
35337
- async createSession(_input) {
37049
+ async createSession(input) {
37050
+ void input;
35338
37051
  this.throwUnavailable();
35339
37052
  }
35340
- async attach(_sessionId, _options) {
37053
+ async attach(sessionId, options) {
37054
+ void sessionId;
37055
+ void options;
35341
37056
  return this.throwUnavailable();
35342
37057
  }
35343
37058
  async sendInput() {
@@ -36090,16 +37805,16 @@ var HttpError = class extends Error {
36090
37805
  };
36091
37806
  function findRepoRoot(start = process.cwd()) {
36092
37807
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
36093
- return path31.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
37808
+ return path33.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
36094
37809
  }
36095
- let current = path31.resolve(start);
36096
- while (current !== path31.dirname(current)) {
36097
- if (fs29.existsSync(path31.join(current, "pnpm-workspace.yaml")) && fs29.existsSync(path31.join(current, "scripts", "service-restart.mjs"))) {
37810
+ let current = path33.resolve(start);
37811
+ while (current !== path33.dirname(current)) {
37812
+ if (fs31.existsSync(path33.join(current, "pnpm-workspace.yaml")) && fs31.existsSync(path33.join(current, "scripts", "service-restart.mjs"))) {
36098
37813
  return current;
36099
37814
  }
36100
- current = path31.dirname(current);
37815
+ current = path33.dirname(current);
36101
37816
  }
36102
- return path31.resolve(process.cwd());
37817
+ return path33.resolve(process.cwd());
36103
37818
  }
36104
37819
  function createServiceLifecycle() {
36105
37820
  return {
@@ -36111,12 +37826,12 @@ function createServiceLifecycle() {
36111
37826
  });
36112
37827
  }
36113
37828
  const repoRoot = findRepoRoot();
36114
- const restartScript = path31.join(
37829
+ const restartScript = path33.join(
36115
37830
  repoRoot,
36116
37831
  "scripts",
36117
37832
  "service-restart.mjs"
36118
37833
  );
36119
- if (!fs29.existsSync(restartScript) || !fs29.existsSync(path31.join(repoRoot, "pnpm-workspace.yaml"))) {
37834
+ if (!fs31.existsSync(restartScript) || !fs31.existsSync(path33.join(repoRoot, "pnpm-workspace.yaml"))) {
36120
37835
  throw new HttpError(503, {
36121
37836
  code: "service_unavailable",
36122
37837
  message: "Build and restart requires a Remote Codex source checkout. Set REMOTE_CODEX_REPO_ROOT to the checkout path, or update the npm package with npm install -g remote-codex@latest."
@@ -36599,9 +38314,9 @@ function relayResponseHeaders(headers) {
36599
38314
  }
36600
38315
 
36601
38316
  // src/platform/lifecycle-control.ts
36602
- import fs30 from "fs/promises";
38317
+ import fs32 from "fs/promises";
36603
38318
  import net2 from "net";
36604
- import path32 from "path";
38319
+ import path34 from "path";
36605
38320
  var LifecycleControlServer = class {
36606
38321
  constructor(options) {
36607
38322
  this.options = options;
@@ -36613,12 +38328,12 @@ var LifecycleControlServer = class {
36613
38328
  return;
36614
38329
  }
36615
38330
  if (process.platform !== "win32") {
36616
- await fs30.unlink(this.options.endpoint).catch((error) => {
38331
+ await fs32.unlink(this.options.endpoint).catch((error) => {
36617
38332
  if (error.code !== "ENOENT") {
36618
38333
  throw error;
36619
38334
  }
36620
38335
  });
36621
- await fs30.mkdir(path32.dirname(this.options.endpoint), { recursive: true });
38336
+ await fs32.mkdir(path34.dirname(this.options.endpoint), { recursive: true });
36622
38337
  }
36623
38338
  const server = net2.createServer((socket) => {
36624
38339
  socket.setEncoding("utf8");
@@ -36655,7 +38370,7 @@ var LifecycleControlServer = class {
36655
38370
  await new Promise((resolve) => server.close(() => resolve()));
36656
38371
  }
36657
38372
  if (process.platform !== "win32") {
36658
- await fs30.unlink(this.options.endpoint).catch(() => void 0);
38373
+ await fs32.unlink(this.options.endpoint).catch(() => void 0);
36659
38374
  }
36660
38375
  }
36661
38376
  async handleRequest(line, socket) {
@@ -36698,7 +38413,7 @@ var LifecycleControlServer = class {
36698
38413
  };
36699
38414
 
36700
38415
  // src/index.ts
36701
- if (fs31.existsSync(".env")) {
38416
+ if (fs33.existsSync(".env")) {
36702
38417
  process.loadEnvFile?.(".env");
36703
38418
  }
36704
38419
  var app = buildApp();