@agentconnect.md/codex-acp 1.6.2-agentconnect.1 → 1.7.0-agentconnect.2

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 (3) hide show
  1. package/README.md +10 -3
  2. package/dist/index.js +1946 -1043
  3. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -3453,7 +3453,7 @@ var require_main = __commonJS({
3453
3453
  exports.createMessageConnection = createMessageConnection2;
3454
3454
  var ril_1 = __importDefault(require_ril());
3455
3455
  ril_1.default.install();
3456
- var path8 = __importStar(__require("path"));
3456
+ var path9 = __importStar(__require("path"));
3457
3457
  var os2 = __importStar(__require("os"));
3458
3458
  var fs8 = __importStar(__require("fs"));
3459
3459
  var crypto_1 = __require("crypto");
@@ -3462,9 +3462,9 @@ var require_main = __commonJS({
3462
3462
  __exportStar(require_api(), exports);
3463
3463
  var IPCMessageReader = class extends api_1.AbstractMessageReader {
3464
3464
  process;
3465
- constructor(process10) {
3465
+ constructor(process12) {
3466
3466
  super();
3467
- this.process = process10;
3467
+ this.process = process12;
3468
3468
  const eventEmitter = this.process;
3469
3469
  eventEmitter.on("error", (error51) => this.fireError(error51));
3470
3470
  eventEmitter.on("close", () => this.fireClose());
@@ -3478,9 +3478,9 @@ var require_main = __commonJS({
3478
3478
  var IPCMessageWriter = class extends api_1.AbstractMessageWriter {
3479
3479
  process;
3480
3480
  errorCount;
3481
- constructor(process10) {
3481
+ constructor(process12) {
3482
3482
  super();
3483
- this.process = process10;
3483
+ this.process = process12;
3484
3484
  this.errorCount = 0;
3485
3485
  const eventEmitter = this.process;
3486
3486
  eventEmitter.on("error", (error51) => this.fireError(error51));
@@ -3605,7 +3605,7 @@ var require_main = __commonJS({
3605
3605
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
3606
3606
  }
3607
3607
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
3608
- return path8.join(tmpDir, `lsp-${randomSuffix}.sock`);
3608
+ return path9.join(tmpDir, `lsp-${randomSuffix}.sock`);
3609
3609
  }
3610
3610
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
3611
3611
  let connectResolve;
@@ -4509,10 +4509,10 @@ function mergeDefs(...defs) {
4509
4509
  function cloneDef(schema) {
4510
4510
  return mergeDefs(schema._zod.def);
4511
4511
  }
4512
- function getElementAtPath(obj, path8) {
4513
- if (!path8)
4512
+ function getElementAtPath(obj, path9) {
4513
+ if (!path9)
4514
4514
  return obj;
4515
- return path8.reduce((acc, key) => acc?.[key], obj);
4515
+ return path9.reduce((acc, key) => acc?.[key], obj);
4516
4516
  }
4517
4517
  function promiseAllObject(promisesObj) {
4518
4518
  const keys = Object.keys(promisesObj);
@@ -4921,11 +4921,11 @@ function explicitlyAborted(x, startIndex = 0) {
4921
4921
  }
4922
4922
  return false;
4923
4923
  }
4924
- function prefixIssues(path8, issues) {
4924
+ function prefixIssues(path9, issues) {
4925
4925
  return issues.map((iss) => {
4926
4926
  var _a3;
4927
4927
  (_a3 = iss).path ?? (_a3.path = []);
4928
- iss.path.unshift(path8);
4928
+ iss.path.unshift(path9);
4929
4929
  return iss;
4930
4930
  });
4931
4931
  }
@@ -5072,16 +5072,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
5072
5072
  }
5073
5073
  function formatError(error51, mapper = (issue2) => issue2.message) {
5074
5074
  const fieldErrors = { _errors: [] };
5075
- const processError = (error52, path8 = []) => {
5075
+ const processError = (error52, path9 = []) => {
5076
5076
  for (const issue2 of error52.issues) {
5077
5077
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5078
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
5078
+ issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
5079
5079
  } else if (issue2.code === "invalid_key") {
5080
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5080
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
5081
5081
  } else if (issue2.code === "invalid_element") {
5082
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5082
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
5083
5083
  } else {
5084
- const fullpath = [...path8, ...issue2.path];
5084
+ const fullpath = [...path9, ...issue2.path];
5085
5085
  if (fullpath.length === 0) {
5086
5086
  fieldErrors._errors.push(mapper(issue2));
5087
5087
  } else {
@@ -5108,17 +5108,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
5108
5108
  }
5109
5109
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
5110
5110
  const result = { errors: [] };
5111
- const processError = (error52, path8 = []) => {
5111
+ const processError = (error52, path9 = []) => {
5112
5112
  var _a3, _b;
5113
5113
  for (const issue2 of error52.issues) {
5114
5114
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5115
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
5115
+ issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
5116
5116
  } else if (issue2.code === "invalid_key") {
5117
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5117
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
5118
5118
  } else if (issue2.code === "invalid_element") {
5119
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
5119
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
5120
5120
  } else {
5121
- const fullpath = [...path8, ...issue2.path];
5121
+ const fullpath = [...path9, ...issue2.path];
5122
5122
  if (fullpath.length === 0) {
5123
5123
  result.errors.push(mapper(issue2));
5124
5124
  continue;
@@ -5150,8 +5150,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
5150
5150
  }
5151
5151
  function toDotPath(_path) {
5152
5152
  const segs = [];
5153
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
- for (const seg of path8) {
5153
+ const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5154
+ for (const seg of path9) {
5155
5155
  if (typeof seg === "number")
5156
5156
  segs.push(`[${seg}]`);
5157
5157
  else if (typeof seg === "symbol")
@@ -17843,13 +17843,13 @@ function resolveRef(ref, ctx) {
17843
17843
  if (!ref.startsWith("#")) {
17844
17844
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17845
17845
  }
17846
- const path8 = ref.slice(1).split("/").filter(Boolean);
17847
- if (path8.length === 0) {
17846
+ const path9 = ref.slice(1).split("/").filter(Boolean);
17847
+ if (path9.length === 0) {
17848
17848
  return ctx.rootSchema;
17849
17849
  }
17850
17850
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17851
- if (path8[0] === defsKey) {
17852
- const key = path8[1];
17851
+ if (path9[0] === defsKey) {
17852
+ const key = path9[1];
17853
17853
  if (!key || !ctx.defs[key]) {
17854
17854
  throw new Error(`Reference not found: ${ref}`);
17855
17855
  }
@@ -18856,21 +18856,6 @@ var zAgentCapabilities = object({
18856
18856
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
18857
18857
  });
18858
18858
  var zAuthMethodId = string2();
18859
- var zAuthEnvVar = object({
18860
- name: string2(),
18861
- label: defaultOnError(string2().nullish(), () => void 0),
18862
- secret: defaultOnError(boolean2().optional().default(true), () => true),
18863
- optional: defaultOnError(boolean2().optional().default(false), () => false),
18864
- _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
18865
- });
18866
- var zAuthMethodEnvVar = object({
18867
- id: zAuthMethodId,
18868
- name: string2(),
18869
- description: defaultOnError(string2().nullish(), () => void 0),
18870
- vars: requiredDefaultOnError(vecSkipError(zAuthEnvVar), () => []),
18871
- link: defaultOnError(string2().nullish(), () => void 0),
18872
- _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
18873
- });
18874
18859
  var zAuthMethodTerminal = object({
18875
18860
  id: zAuthMethodId,
18876
18861
  name: string2(),
@@ -18886,9 +18871,6 @@ var zAuthMethodAgent = object({
18886
18871
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
18887
18872
  });
18888
18873
  var zAuthMethod = union([
18889
- zAuthMethodEnvVar.and(object({
18890
- type: literal("env_var")
18891
- })),
18892
18874
  zAuthMethodTerminal.and(object({
18893
18875
  type: literal("terminal")
18894
18876
  })),
@@ -19339,6 +19321,26 @@ var zUsageUpdate = object({
19339
19321
  cost: defaultOnError(zCost.nullish(), () => void 0),
19340
19322
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19341
19323
  });
19324
+ var zCompactionId = string2();
19325
+ var zCompactionStatus = union([
19326
+ literal("in_progress"),
19327
+ literal("completed"),
19328
+ literal("failed"),
19329
+ literal("cancelled"),
19330
+ string2()
19331
+ ]);
19332
+ var zCompactionUpdate = object({
19333
+ compactionId: zCompactionId,
19334
+ status: zCompactionStatus,
19335
+ summary: defaultOnError(vecSkipError(zContentBlock).nullish(), () => void 0),
19336
+ error: defaultOnError(string2().nullish(), () => void 0),
19337
+ _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19338
+ });
19339
+ var zCompactionSummaryChunk = object({
19340
+ compactionId: zCompactionId,
19341
+ content: zContentBlock,
19342
+ _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19343
+ });
19342
19344
  var zSessionUpdate = union([
19343
19345
  zContentChunk.and(object({
19344
19346
  sessionUpdate: literal("user_message_chunk")
@@ -19378,6 +19380,12 @@ var zSessionUpdate = union([
19378
19380
  })),
19379
19381
  zUsageUpdate.and(object({
19380
19382
  sessionUpdate: literal("usage_update")
19383
+ })),
19384
+ zCompactionUpdate.and(object({
19385
+ sessionUpdate: literal("compaction_update")
19386
+ })),
19387
+ zCompactionSummaryChunk.and(object({
19388
+ sessionUpdate: literal("compaction_summary_chunk")
19381
19389
  }))
19382
19390
  ]);
19383
19391
  var zSessionNotification = object({
@@ -19410,6 +19418,7 @@ var zFileSystemCapabilities = object({
19410
19418
  writeTextFile: defaultOnError(boolean2().optional().default(false), () => false),
19411
19419
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19412
19420
  });
19421
+ var zCompactionCapabilities = record(string2(), unknown());
19413
19422
  var zBooleanConfigOptionCapabilities = object({
19414
19423
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19415
19424
  });
@@ -19418,6 +19427,7 @@ var zSessionConfigOptionsCapabilities = object({
19418
19427
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19419
19428
  });
19420
19429
  var zClientSessionCapabilities = object({
19430
+ compaction: defaultOnError(zCompactionCapabilities.nullish(), () => void 0),
19421
19431
  configOptions: defaultOnError(zSessionConfigOptionsCapabilities.nullish(), () => void 0),
19422
19432
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => void 0)
19423
19433
  });
@@ -20096,6 +20106,7 @@ var RequestResponder = class {
20096
20106
  });
20097
20107
  }
20098
20108
  };
20109
+ var requestBatchSizes = /* @__PURE__ */ new WeakMap();
20099
20110
  var HandlerRegistration = class {
20100
20111
  disposeHandler;
20101
20112
  active = true;
@@ -20446,9 +20457,6 @@ var Connection = class {
20446
20457
  if (done) {
20447
20458
  break;
20448
20459
  }
20449
- if (!message) {
20450
- continue;
20451
- }
20452
20460
  this.receiveWireMessage(message);
20453
20461
  }
20454
20462
  } finally {
@@ -20472,19 +20480,16 @@ var Connection = class {
20472
20480
  this.receiveBatch(message);
20473
20481
  return;
20474
20482
  }
20475
- if (!isRecord(message)) {
20476
- console.error("Invalid message", { message });
20483
+ if (!isRequestMessage(message) && !isNotificationMessage(message) && !isResponseShapedMessage(message)) {
20484
+ void this.sendWireMessage(protocolErrorResponse(RequestError.invalidRequest(message))).catch(() => {
20485
+ });
20477
20486
  return;
20478
20487
  }
20479
20488
  this.receiveMessage(message);
20480
20489
  }
20481
20490
  receiveBatch(batch) {
20482
20491
  if (batch.length === 0) {
20483
- void this.sendWireMessage({
20484
- jsonrpc: "2.0",
20485
- id: null,
20486
- error: RequestError.invalidRequest(batch).toErrorResponse()
20487
- }).catch(() => {
20492
+ void this.sendWireMessage(protocolErrorResponse(RequestError.invalidRequest(batch))).catch(() => {
20488
20493
  });
20489
20494
  return;
20490
20495
  }
@@ -20514,15 +20519,11 @@ var Connection = class {
20514
20519
  continue;
20515
20520
  }
20516
20521
  if (!isRequestMessage(message) && !isNotificationMessage(message)) {
20517
- void collectResponse({
20518
- jsonrpc: "2.0",
20519
- id: null,
20520
- error: RequestError.invalidRequest(message).toErrorResponse()
20521
- }).catch(() => {
20522
+ void collectResponse(protocolErrorResponse(RequestError.invalidRequest(message))).catch(() => {
20522
20523
  });
20523
20524
  continue;
20524
20525
  }
20525
- const processing = this.receiveMessage(message, isRequestMessage(message) ? collectResponse : void 0);
20526
+ const processing = this.receiveMessage(message, isRequestMessage(message) ? collectResponse : void 0, batch.length);
20526
20527
  if (isNotificationMessage(message)) {
20527
20528
  void processing.finally(() => {
20528
20529
  remainingNotifications -= 1;
@@ -20531,7 +20532,7 @@ var Connection = class {
20531
20532
  }
20532
20533
  }
20533
20534
  }
20534
- receiveMessage(message, sendResponse) {
20535
+ receiveMessage(message, sendResponse, batchSize) {
20535
20536
  if (this.abortController.signal.aborted) {
20536
20537
  return Promise.resolve();
20537
20538
  }
@@ -20543,7 +20544,7 @@ var Connection = class {
20543
20544
  if (!("id" in message)) {
20544
20545
  this.handleProtocolNotification(message);
20545
20546
  }
20546
- return this.processIncomingMessage(this.toIncomingMessage(message, sendResponse)).catch((error51) => this.close(error51));
20547
+ return this.processIncomingMessage(this.toIncomingMessage(message, sendResponse, batchSize)).catch((error51) => this.close(error51));
20547
20548
  } else if ("id" in message) {
20548
20549
  this.handleResponse(message);
20549
20550
  } else {
@@ -20593,7 +20594,7 @@ var Connection = class {
20593
20594
  }
20594
20595
  }
20595
20596
  }
20596
- toIncomingMessage(message, sendResponse) {
20597
+ toIncomingMessage(message, sendResponse, batchSize) {
20597
20598
  if ("id" in message) {
20598
20599
  const abortController = new AbortController();
20599
20600
  this.incomingRequests.set(message.id, abortController);
@@ -20602,20 +20603,24 @@ var Connection = class {
20602
20603
  this.incomingRequests.delete(message.id);
20603
20604
  }
20604
20605
  };
20606
+ const responder = new RequestResponder(message.id, (result) => {
20607
+ const response = {
20608
+ jsonrpc: "2.0",
20609
+ id: message.id,
20610
+ ...result
20611
+ };
20612
+ return sendResponse ? sendResponse(response) : this.sendWireMessage(response);
20613
+ }, abortController.signal, finishRequest);
20614
+ if (batchSize !== void 0) {
20615
+ requestBatchSizes.set(responder, batchSize);
20616
+ }
20605
20617
  return {
20606
20618
  kind: "request",
20607
20619
  method: message.method,
20608
20620
  params: message.params,
20609
20621
  raw: message,
20610
20622
  signal: abortController.signal,
20611
- responder: new RequestResponder(message.id, (result) => {
20612
- const response = {
20613
- jsonrpc: "2.0",
20614
- id: message.id,
20615
- ...result
20616
- };
20617
- return sendResponse ? sendResponse(response) : this.sendWireMessage(response);
20618
- }, abortController.signal, finishRequest)
20623
+ responder
20619
20624
  };
20620
20625
  }
20621
20626
  return {
@@ -20837,6 +20842,13 @@ var RequestError = class _RequestError extends Error {
20837
20842
  };
20838
20843
  }
20839
20844
  };
20845
+ function protocolErrorResponse(error51) {
20846
+ return {
20847
+ jsonrpc: "2.0",
20848
+ id: null,
20849
+ error: error51.toErrorResponse()
20850
+ };
20851
+ }
20840
20852
 
20841
20853
  // node_modules/@agentclientprotocol/sdk/dist/line-buffer.js
20842
20854
  var newline = 10;
@@ -20897,22 +20909,40 @@ function ndJsonStream(output, input) {
20897
20909
  const textDecoder = new TextDecoder();
20898
20910
  let cancelled = false;
20899
20911
  let inputReader;
20912
+ let outputWrite = Promise.resolve();
20913
+ const writeJson = (message) => {
20914
+ const content = JSON.stringify(message) + "\n";
20915
+ const write = outputWrite.then(async () => {
20916
+ const writer = output.getWriter();
20917
+ try {
20918
+ await writer.write(textEncoder.encode(content));
20919
+ } finally {
20920
+ writer.releaseLock();
20921
+ }
20922
+ });
20923
+ outputWrite = write.catch(() => {
20924
+ });
20925
+ return write;
20926
+ };
20900
20927
  const readable = new ReadableStream({
20901
20928
  async start(controller) {
20902
20929
  const lines = new LineBuffer();
20903
- const enqueueLine = (lineBytes) => {
20930
+ const enqueueLine = async (lineBytes) => {
20904
20931
  const trimmedLine = textDecoder.decode(lineBytes).trim();
20905
- if (trimmedLine) {
20906
- try {
20907
- const message = JSON.parse(trimmedLine);
20908
- if (isRecord(message) || Array.isArray(message)) {
20909
- controller.enqueue(message);
20910
- } else {
20911
- console.warn("Skipping JSON line that is not an object:", trimmedLine);
20912
- }
20913
- } catch (err) {
20914
- console.error("Failed to parse JSON message:", trimmedLine, err);
20915
- }
20932
+ if (!trimmedLine) {
20933
+ return;
20934
+ }
20935
+ let message;
20936
+ try {
20937
+ message = JSON.parse(trimmedLine);
20938
+ } catch {
20939
+ await writeJson(protocolErrorResponse(RequestError.parseError()));
20940
+ return;
20941
+ }
20942
+ if (isRecord(message) || Array.isArray(message)) {
20943
+ controller.enqueue(message);
20944
+ } else {
20945
+ await writeJson(protocolErrorResponse(RequestError.invalidRequest(message)));
20916
20946
  }
20917
20947
  };
20918
20948
  const reader = input.getReader();
@@ -20930,7 +20960,7 @@ function ndJsonStream(output, input) {
20930
20960
  continue;
20931
20961
  }
20932
20962
  for (const line of lines.push(value)) {
20933
- enqueueLine(line);
20963
+ await enqueueLine(line);
20934
20964
  if (cancelled) {
20935
20965
  return;
20936
20966
  }
@@ -20941,7 +20971,7 @@ function ndJsonStream(output, input) {
20941
20971
  }
20942
20972
  const lastLine = lines.flush();
20943
20973
  if (lastLine) {
20944
- enqueueLine(lastLine);
20974
+ await enqueueLine(lastLine);
20945
20975
  }
20946
20976
  } catch (err) {
20947
20977
  if (cancelled) {
@@ -20966,14 +20996,8 @@ function ndJsonStream(output, input) {
20966
20996
  }
20967
20997
  });
20968
20998
  const writable = new WritableStream({
20969
- async write(message) {
20970
- const content = JSON.stringify(message) + "\n";
20971
- const writer = output.getWriter();
20972
- try {
20973
- await writer.write(textEncoder.encode(content));
20974
- } finally {
20975
- writer.releaseLock();
20976
- }
20999
+ write(message) {
21000
+ return writeJson(message);
20977
21001
  }
20978
21002
  });
20979
21003
  return { readable, writable };
@@ -21592,11 +21616,11 @@ var clientRequestSpecs = {
21592
21616
  releaseTerminal: requestSpec(CLIENT_METHODS.terminal_release, zReleaseTerminalRequest, emptyObjectResponse),
21593
21617
  waitForTerminalExit: requestSpec(CLIENT_METHODS.terminal_wait_for_exit, zWaitForTerminalExitRequest),
21594
21618
  killTerminal: requestSpec(CLIENT_METHODS.terminal_kill, zKillTerminalRequest, emptyObjectResponse),
21595
- unstable_createElicitation: requestSpec(CLIENT_METHODS.elicitation_create, zCreateElicitationRequest)
21619
+ createElicitation: requestSpec(CLIENT_METHODS.elicitation_create, zCreateElicitationRequest)
21596
21620
  };
21597
21621
  var clientNotificationSpecs = {
21598
21622
  sessionUpdate: notificationSpec(CLIENT_METHODS.session_update, zSessionNotification),
21599
- unstable_completeElicitation: notificationSpec(CLIENT_METHODS.elicitation_complete, zCompleteElicitationNotification)
21623
+ completeElicitation: notificationSpec(CLIENT_METHODS.elicitation_complete, zCompleteElicitationNotification)
21600
21624
  };
21601
21625
  var agentRequestSpecsByMethod = specsByMethod(agentRequestSpecs);
21602
21626
  var agentNotificationSpecsByMethod = specsByMethod(agentNotificationSpecs);
@@ -22021,7 +22045,12 @@ var Logger = class {
22021
22045
  }
22022
22046
  }
22023
22047
  error(message, err) {
22024
- this.log(`[SYSTEM_ERROR] ${message}`, { exception: this.formatError(err) });
22048
+ const formattedError = this.formatError(err);
22049
+ if (!this.logFilePath) {
22050
+ console.error(`[SYSTEM_ERROR] ${message}: ${formattedError}`);
22051
+ return;
22052
+ }
22053
+ this.log(`[SYSTEM_ERROR] ${message}`, { exception: formattedError });
22025
22054
  }
22026
22055
  log(message, context) {
22027
22056
  if (!this.logFilePath) return;
@@ -22103,6 +22132,36 @@ function attachLogs(proc) {
22103
22132
  });
22104
22133
  }
22105
22134
 
22135
+ // src/AirExtension.ts
22136
+ var JETBRAINS_META_KEY = "jetbrains";
22137
+ var AIR_META_KEY = "air";
22138
+ var AIR_EXTENSION_VERSION_KEY = "version";
22139
+ var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
22140
+ var AIR_SESSION_FAILURE_KEY = "sessionFailure";
22141
+ var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
22142
+ var AIR_NATIVE_SUBAGENT_SESSIONS_KEY = "nativeSubagentSessions";
22143
+ var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
22144
+ var AIR_EXTENSION_VERSION = 1;
22145
+ function clientSupportsAirCapability(capabilities, capability) {
22146
+ const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
22147
+ const air = jetbrains?.[AIR_META_KEY];
22148
+ const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
22149
+ const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
22150
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22151
+ }
22152
+
22153
+ // src/subagents/AcpSubagents.ts
22154
+ function clientSupportsSubagents(capabilities) {
22155
+ const subagents = capabilities?.subagents;
22156
+ if (typeof subagents === "object" && subagents !== null && !Array.isArray(subagents)) {
22157
+ return true;
22158
+ }
22159
+ return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
22160
+ }
22161
+ function asSdkSessionNotification(notification) {
22162
+ return notification;
22163
+ }
22164
+
22106
22165
  // src/ACPSessionConnection.ts
22107
22166
  var ACPSessionConnection = class {
22108
22167
  connection;
@@ -22111,11 +22170,11 @@ var ACPSessionConnection = class {
22111
22170
  this.connection = connection;
22112
22171
  this.sessionId = sessionId;
22113
22172
  }
22114
- async update(update) {
22115
- await this.connection.notify(methods.client.session.update, {
22116
- sessionId: this.sessionId,
22173
+ async update(update, sessionId = this.sessionId) {
22174
+ await this.connection.notify(methods.client.session.update, asSdkSessionNotification({
22175
+ sessionId,
22117
22176
  update
22118
- });
22177
+ }));
22119
22178
  }
22120
22179
  };
22121
22180
 
@@ -23258,13 +23317,13 @@ function createTerminalCommandEvent(event, terminalId, cwd) {
23258
23317
  }
23259
23318
  };
23260
23319
  }
23261
- function createSearchTitle(query, path8) {
23262
- if (query && path8) {
23263
- return `Search for '${query}' in ${path8}`;
23320
+ function createSearchTitle(query, path9) {
23321
+ if (query && path9) {
23322
+ return `Search for '${query}' in ${path9}`;
23264
23323
  } else if (query) {
23265
23324
  return `Search for '${query}'`;
23266
- } else if (path8) {
23267
- return `Search in '${path8}'`;
23325
+ } else if (path9) {
23326
+ return `Search in '${path9}'`;
23268
23327
  }
23269
23328
  return "Search";
23270
23329
  }
@@ -23490,12 +23549,12 @@ function revertPatch(unifiedDiff) {
23490
23549
  if (!patch) return null;
23491
23550
  return reversePatch(patch);
23492
23551
  }
23493
- function createUpdateDiffContent(path8, oldText, newText) {
23552
+ function createUpdateDiffContent(path9, oldText, newText) {
23494
23553
  return {
23495
23554
  type: "diff",
23496
23555
  oldText,
23497
23556
  newText,
23498
- path: path8,
23557
+ path: path9,
23499
23558
  _meta: {
23500
23559
  kind: "update"
23501
23560
  }
@@ -23622,15 +23681,380 @@ function sameThreadGoalSnapshot(left, right) {
23622
23681
  // src/CodexEventHandler.ts
23623
23682
  import { randomUUID } from "node:crypto";
23624
23683
 
23625
- // src/AirExtension.ts
23626
- var JETBRAINS_META_KEY = "jetbrains";
23627
- var AIR_META_KEY = "air";
23628
- var AIR_EXTENSION_VERSION_KEY = "version";
23629
- var AIR_EXTENSION_CAPABILITIES_KEY = "capabilities";
23630
- var AIR_SESSION_FAILURE_KEY = "sessionFailure";
23631
- var AIR_AGENT_FILE_CHANGE_REPORT_KEY = "agentFileChangeReport";
23632
- var AIR_AGENT_FILE_CHANGE_REPORT_REQUEST_KEY = "agentFileChangeReportRequest";
23633
- var AIR_EXTENSION_VERSION = 1;
23684
+ // src/subagents/CodexAgentPath.ts
23685
+ function normalizeAgentPath(path9) {
23686
+ const normalized = path9.trim().replace(/\/+$/, "");
23687
+ return normalized || "/root";
23688
+ }
23689
+ function isRootAgentPath(path9) {
23690
+ const normalized = normalizeAgentPath(path9);
23691
+ return normalized === "/root" || normalized === "root";
23692
+ }
23693
+ function nameFromAgentPath(path9, fallback) {
23694
+ const normalized = normalizeAgentPath(path9);
23695
+ const name = normalized.slice(normalized.lastIndexOf("/") + 1).trim();
23696
+ if (!name) return fallback;
23697
+ const words = name.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
23698
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : fallback;
23699
+ }
23700
+
23701
+ // src/subagents/CodexSubagentEventRouter.ts
23702
+ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23703
+ constructor(rootSessionId, supported, session) {
23704
+ this.rootSessionId = rootSessionId;
23705
+ this.supported = supported;
23706
+ this.session = session;
23707
+ }
23708
+ rootSessionId;
23709
+ supported;
23710
+ session;
23711
+ static DEFAULT_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
23712
+ static MAX_PENDING_NOTIFICATIONS = 256;
23713
+ children = /* @__PURE__ */ new Map();
23714
+ pendingSpawns = /* @__PURE__ */ new Map();
23715
+ terminalPendingSpawns = /* @__PURE__ */ new Map();
23716
+ waiters = /* @__PURE__ */ new Set();
23717
+ materializationWaiters = /* @__PURE__ */ new Map();
23718
+ replayQueue = [];
23719
+ activeLegacyActivities = /* @__PURE__ */ new Set();
23720
+ async handle(notification) {
23721
+ if (notification.method === "turn/started") {
23722
+ return this.isKnownChild(notification.params.threadId);
23723
+ }
23724
+ if (notification.method === "turn/completed") {
23725
+ const childTurn = this.isKnownChild(notification.params.threadId);
23726
+ const state = terminalStateFromTurn(notification.params.turn.status);
23727
+ if (!state) return childTurn;
23728
+ if (notification.params.threadId === this.rootSessionId) {
23729
+ if (state !== "completed") await this.finishOutstanding(state);
23730
+ } else {
23731
+ if (this.pendingSpawns.has(notification.params.threadId)) {
23732
+ this.finishPending(notification.params.threadId);
23733
+ } else {
23734
+ await this.finish(notification.params.threadId, state);
23735
+ }
23736
+ }
23737
+ return childTurn;
23738
+ }
23739
+ const notificationThreadId = notification.params.threadId;
23740
+ if (typeof notificationThreadId === "string" && this.pendingSpawns.has(notificationThreadId)) {
23741
+ const pending = this.pendingSpawns.get(notificationThreadId);
23742
+ if (pending.buffered.length === _CodexSubagentEventRouter.MAX_PENDING_NOTIFICATIONS) {
23743
+ pending.buffered.shift();
23744
+ pending.droppedBufferedNotifications += 1;
23745
+ if (pending.droppedBufferedNotifications === 1) {
23746
+ logger.log(`Pending subagent ${notificationThreadId} exceeded the notification buffer; dropping oldest updates`);
23747
+ }
23748
+ }
23749
+ pending.buffered.push(notification);
23750
+ return true;
23751
+ }
23752
+ if (notification.method !== "item/started" && notification.method !== "item/completed") {
23753
+ return false;
23754
+ }
23755
+ const item = notification.params.item;
23756
+ if (!this.supported) {
23757
+ return false;
23758
+ }
23759
+ if (item.type === "subAgentActivity") {
23760
+ if (isRootAgentPath(item.agentPath)) return true;
23761
+ if (this.terminalPendingSpawns.has(item.agentThreadId)) return true;
23762
+ let hasNativeRepresentation = this.children.has(item.agentThreadId);
23763
+ if (!hasNativeRepresentation) {
23764
+ await this.materialize(item.agentThreadId, item.agentPath);
23765
+ hasNativeRepresentation = this.children.has(item.agentThreadId);
23766
+ }
23767
+ if (hasNativeRepresentation && item.kind === "interrupted") {
23768
+ await this.finish(item.agentThreadId, "cancelled");
23769
+ }
23770
+ return hasNativeRepresentation;
23771
+ }
23772
+ if (item.type !== "collabAgentToolCall") return false;
23773
+ if (item.tool === "resumeAgent" || item.tool === "sendInput") {
23774
+ for (const [childThreadId, state] of Object.entries(item.agentsStates)) {
23775
+ if (state?.status === "running" || state?.status === "pendingInit") {
23776
+ await this.reopen(childThreadId);
23777
+ }
23778
+ }
23779
+ }
23780
+ let representedSpawn = false;
23781
+ if (item.tool === "spawnAgent") {
23782
+ const parent = this.children.get(item.senderThreadId);
23783
+ const parentThreadId = parent ? item.senderThreadId : this.rootSessionId;
23784
+ const parentSessionId = parent?.sessionId ?? this.rootSessionId;
23785
+ for (const childSessionId of item.receiverThreadIds) {
23786
+ if (childSessionId.trim().length === 0) {
23787
+ logger.log("Ignoring spawned subagent with an empty thread id");
23788
+ continue;
23789
+ }
23790
+ if (childSessionId === parentSessionId || childSessionId === this.rootSessionId) {
23791
+ logger.log(`Ignoring self-referential spawned subagent ${childSessionId}`);
23792
+ continue;
23793
+ }
23794
+ if (this.children.has(childSessionId) || this.pendingSpawns.has(childSessionId) || this.terminalPendingSpawns.has(childSessionId)) {
23795
+ representedSpawn = true;
23796
+ continue;
23797
+ }
23798
+ this.pendingSpawns.set(childSessionId, {
23799
+ parentThreadId,
23800
+ parentSessionId,
23801
+ task: item.prompt?.trim() || "Delegated task",
23802
+ buffered: [],
23803
+ droppedBufferedNotifications: 0
23804
+ });
23805
+ representedSpawn = true;
23806
+ }
23807
+ }
23808
+ for (const [childSessionId, state] of Object.entries(item.agentsStates)) {
23809
+ const terminalState = state && terminalStateOf(state.status);
23810
+ if (!terminalState) continue;
23811
+ if (this.children.has(childSessionId)) await this.finish(childSessionId, terminalState);
23812
+ else if (this.pendingSpawns.has(childSessionId)) this.finishPending(childSessionId);
23813
+ }
23814
+ if (item.tool === "spawnAgent" && item.status === "failed") {
23815
+ for (const childSessionId of item.receiverThreadIds) {
23816
+ if (this.pendingSpawns.has(childSessionId)) this.finishPending(childSessionId);
23817
+ }
23818
+ }
23819
+ return item.tool === "spawnAgent" && representedSpawn;
23820
+ }
23821
+ shouldIgnore(notification) {
23822
+ const threadId = notification.params.threadId;
23823
+ const ignored = typeof threadId === "string" && (this.children.get(threadId)?.terminalState !== void 0 || this.terminalPendingSpawns.has(threadId));
23824
+ if (ignored) logger.log(`Ignoring update for terminal subagent ${threadId}`);
23825
+ return ignored;
23826
+ }
23827
+ notificationSessionId(notification) {
23828
+ const threadId = notification.params.threadId;
23829
+ return typeof threadId === "string" && this.children.has(threadId) ? this.children.get(threadId).sessionId : this.rootSessionId;
23830
+ }
23831
+ takeBufferedNotifications() {
23832
+ return this.replayQueue.splice(0);
23833
+ }
23834
+ async waitForMaterializedSession(childThreadId) {
23835
+ const child = this.children.get(childThreadId);
23836
+ if (child) return child.terminalState === void 0 ? child.sessionId : null;
23837
+ if (this.terminalPendingSpawns.has(childThreadId)) return null;
23838
+ if (!this.pendingSpawns.has(childThreadId)) return null;
23839
+ return await new Promise((resolve) => {
23840
+ const waiters = this.materializationWaiters.get(childThreadId) ?? /* @__PURE__ */ new Set();
23841
+ waiters.add(resolve);
23842
+ this.materializationWaiters.set(childThreadId, waiters);
23843
+ });
23844
+ }
23845
+ legacyActivityStarted(item) {
23846
+ this.activeLegacyActivities.add(item.id);
23847
+ return createSubAgentActivityUpdate(item, "in_progress", "tool_call");
23848
+ }
23849
+ legacyCollaborationStarted(item) {
23850
+ return createCollabAgentToolCallUpdate(item);
23851
+ }
23852
+ legacyCollaborationCompleted(item) {
23853
+ return createCollabAgentToolCallCompleteUpdate(item);
23854
+ }
23855
+ legacyActivityCompleted(item) {
23856
+ const sessionUpdate = this.activeLegacyActivities.delete(item.id) ? "tool_call_update" : "tool_call";
23857
+ return createSubAgentActivityUpdate(item, "completed", sessionUpdate);
23858
+ }
23859
+ async wait(signal, timeoutMs = _CodexSubagentEventRouter.DEFAULT_WAIT_TIMEOUT_MS) {
23860
+ const deadline = Date.now() + timeoutMs;
23861
+ while (this.hasOutstanding()) {
23862
+ if (signal.aborted) return;
23863
+ const remainingMs = deadline - Date.now();
23864
+ if (remainingMs <= 0) {
23865
+ logger.log(`Timed out waiting for subagents in session ${this.rootSessionId}; marking them failed`);
23866
+ await this.finishOutstanding("failed");
23867
+ return;
23868
+ }
23869
+ const changed = await new Promise((resolve) => {
23870
+ const timeout = setTimeout(() => {
23871
+ this.waiters.delete(onChange);
23872
+ signal.removeEventListener("abort", onAbort);
23873
+ resolve(false);
23874
+ }, remainingMs);
23875
+ const onAbort = () => {
23876
+ clearTimeout(timeout);
23877
+ this.waiters.delete(onChange);
23878
+ resolve(true);
23879
+ };
23880
+ const onChange = () => {
23881
+ clearTimeout(timeout);
23882
+ signal.removeEventListener("abort", onAbort);
23883
+ resolve(true);
23884
+ };
23885
+ this.waiters.add(onChange);
23886
+ signal.addEventListener("abort", onAbort, { once: true });
23887
+ });
23888
+ if (!changed) {
23889
+ logger.log(`Timed out waiting for subagents in session ${this.rootSessionId}; marking them failed`);
23890
+ await this.finishOutstanding("failed");
23891
+ return;
23892
+ }
23893
+ }
23894
+ }
23895
+ async finishOutstanding(state) {
23896
+ for (const childSessionId of [...this.pendingSpawns.keys()]) {
23897
+ this.finishPending(childSessionId);
23898
+ }
23899
+ for (const childSessionId of [...this.children.keys()].reverse()) {
23900
+ await this.finish(childSessionId, state);
23901
+ }
23902
+ }
23903
+ isKnownChild(threadId) {
23904
+ return threadId !== this.rootSessionId && (this.children.has(threadId) || this.pendingSpawns.has(threadId) || this.terminalPendingSpawns.has(threadId));
23905
+ }
23906
+ async materialize(childSessionId, path9) {
23907
+ if (this.children.has(childSessionId)) return;
23908
+ const pending = this.pendingSpawns.get(childSessionId);
23909
+ const name = nameFromAgentPath(path9, fallbackName(childSessionId));
23910
+ const inferredParent = this.parentForPath(path9);
23911
+ const parentThreadId = pending?.parentThreadId ?? inferredParent.threadId;
23912
+ const parentSessionId = pending?.parentSessionId ?? inferredParent.sessionId;
23913
+ const task = pending?.task ?? `Delegated task for ${name}`;
23914
+ await this.session.update({
23915
+ sessionUpdate: "subagent_spawned",
23916
+ subagentSessionId: childSessionId,
23917
+ name,
23918
+ task,
23919
+ capabilities: {}
23920
+ }, parentSessionId);
23921
+ this.children.set(childSessionId, {
23922
+ parentThreadId,
23923
+ parentSessionId,
23924
+ sessionId: childSessionId,
23925
+ name,
23926
+ task,
23927
+ path: normalizeAgentPath(path9),
23928
+ generation: 1
23929
+ });
23930
+ this.pendingSpawns.delete(childSessionId);
23931
+ this.replayQueue.push(...pending?.buffered ?? []);
23932
+ this.resolveMaterialization(childSessionId, childSessionId);
23933
+ }
23934
+ finishPending(childSessionId) {
23935
+ const pending = this.pendingSpawns.get(childSessionId);
23936
+ if (!pending) return;
23937
+ this.pendingSpawns.delete(childSessionId);
23938
+ this.terminalPendingSpawns.set(childSessionId, pending);
23939
+ this.resolveMaterialization(childSessionId, null);
23940
+ this.notifyWaiters();
23941
+ }
23942
+ async finish(childSessionId, state) {
23943
+ const child = this.children.get(childSessionId);
23944
+ if (!child || child.terminalState !== void 0) return;
23945
+ child.terminalState = state;
23946
+ try {
23947
+ await this.session.update({
23948
+ sessionUpdate: "subagent_state_update",
23949
+ subagentSessionId: child.sessionId,
23950
+ state
23951
+ }, child.parentSessionId);
23952
+ } catch (error51) {
23953
+ if (child.terminalState === state) delete child.terminalState;
23954
+ throw error51;
23955
+ }
23956
+ this.notifyWaiters();
23957
+ }
23958
+ async reopen(childThreadId) {
23959
+ const child = this.children.get(childThreadId);
23960
+ if (!child) {
23961
+ const pending = this.terminalPendingSpawns.get(childThreadId);
23962
+ if (!pending) return;
23963
+ const parentSessionId = this.children.get(pending.parentThreadId)?.sessionId ?? this.rootSessionId;
23964
+ const reopened = {
23965
+ parentThreadId: pending.parentThreadId,
23966
+ parentSessionId,
23967
+ sessionId: `${childThreadId}:generation:2`,
23968
+ name: fallbackName(childThreadId),
23969
+ task: pending.task,
23970
+ generation: 2
23971
+ };
23972
+ await this.session.update({
23973
+ sessionUpdate: "subagent_spawned",
23974
+ subagentSessionId: reopened.sessionId,
23975
+ name: reopened.name,
23976
+ task: reopened.task,
23977
+ capabilities: {}
23978
+ }, parentSessionId);
23979
+ this.children.set(childThreadId, reopened);
23980
+ this.terminalPendingSpawns.delete(childThreadId);
23981
+ return;
23982
+ }
23983
+ if (!child.terminalState) return;
23984
+ const previousSessionId = child.sessionId;
23985
+ const previousParentSessionId = child.parentSessionId;
23986
+ const previousState = child.terminalState;
23987
+ child.parentSessionId = this.children.get(child.parentThreadId)?.sessionId ?? this.rootSessionId;
23988
+ child.generation += 1;
23989
+ child.sessionId = `${childThreadId}:generation:${child.generation}`;
23990
+ delete child.terminalState;
23991
+ try {
23992
+ await this.session.update({
23993
+ sessionUpdate: "subagent_spawned",
23994
+ subagentSessionId: child.sessionId,
23995
+ name: child.name,
23996
+ task: child.task,
23997
+ capabilities: {}
23998
+ }, child.parentSessionId);
23999
+ } catch (error51) {
24000
+ child.generation -= 1;
24001
+ child.sessionId = previousSessionId;
24002
+ child.parentSessionId = previousParentSessionId;
24003
+ child.terminalState = previousState;
24004
+ throw error51;
24005
+ }
24006
+ }
24007
+ resolveMaterialization(childThreadId, sessionId) {
24008
+ for (const resolve of this.materializationWaiters.get(childThreadId) ?? []) resolve(sessionId);
24009
+ this.materializationWaiters.delete(childThreadId);
24010
+ }
24011
+ hasOutstanding() {
24012
+ return this.pendingSpawns.size > 0 || [...this.children.values()].some((child) => child.terminalState === void 0);
24013
+ }
24014
+ notifyWaiters() {
24015
+ for (const waiter of this.waiters) waiter();
24016
+ this.waiters.clear();
24017
+ }
24018
+ parentForPath(path9) {
24019
+ const normalized = normalizeAgentPath(path9);
24020
+ const separator = normalized.lastIndexOf("/");
24021
+ if (separator <= 0) return { threadId: this.rootSessionId, sessionId: this.rootSessionId };
24022
+ const parentPath = normalized.slice(0, separator);
24023
+ const parent = [...this.children.entries()].find(([, child]) => child.path === parentPath);
24024
+ return parent ? { threadId: parent[0], sessionId: parent[1].sessionId } : { threadId: this.rootSessionId, sessionId: this.rootSessionId };
24025
+ }
24026
+ };
24027
+ function terminalStateOf(status) {
24028
+ switch (status) {
24029
+ case "completed":
24030
+ return "completed";
24031
+ case "interrupted":
24032
+ return "cancelled";
24033
+ case "errored":
24034
+ case "shutdown":
24035
+ case "notFound":
24036
+ return "failed";
24037
+ case "pendingInit":
24038
+ case "running":
24039
+ return void 0;
24040
+ }
24041
+ }
24042
+ function terminalStateFromTurn(status) {
24043
+ switch (status) {
24044
+ case "completed":
24045
+ return "completed";
24046
+ case "interrupted":
24047
+ return "cancelled";
24048
+ case "failed":
24049
+ return "failed";
24050
+ case "inProgress":
24051
+ return void 0;
24052
+ }
24053
+ }
24054
+ function fallbackName(sessionId) {
24055
+ const suffix = sessionId.length > 8 ? sessionId.slice(-8) : sessionId;
24056
+ return `Agent ${suffix}`;
24057
+ }
23634
24058
 
23635
24059
  // src/CodexEventHandler.ts
23636
24060
  var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
@@ -23738,13 +24162,18 @@ var CodexEventHandler = class _CodexEventHandler {
23738
24162
  terminalCommandIds = /* @__PURE__ */ new Set();
23739
24163
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
23740
24164
  agentMessagePhases = /* @__PURE__ */ new Map();
23741
- activeSubAgentActivities = /* @__PURE__ */ new Set();
23742
- constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID()) {
24165
+ subagents;
24166
+ constructor(connection, sessionState, supportsPlanUpdates = false, supportsTypedSessionFailures = false, sessionFailureEpoch = randomUUID(), subagents = new CodexSubagentEventRouter(
24167
+ sessionState.sessionId,
24168
+ false,
24169
+ new ACPSessionConnection(connection, sessionState.sessionId)
24170
+ )) {
23743
24171
  this.sessionState = sessionState;
23744
24172
  this.supportsPlanUpdates = supportsPlanUpdates;
23745
24173
  this.supportsTypedSessionFailures = supportsTypedSessionFailures;
23746
24174
  this.sessionFailureEpoch = sessionFailureEpoch;
23747
24175
  this.session = new ACPSessionConnection(connection, sessionState.sessionId);
24176
+ this.subagents = subagents;
23748
24177
  if (sessionState.sessionFailure !== void 0) {
23749
24178
  this.failuresById.set(sessionState.sessionFailure.id, sessionState.sessionFailure);
23750
24179
  }
@@ -23846,11 +24275,30 @@ var CodexEventHandler = class _CodexEventHandler {
23846
24275
  }
23847
24276
  async handleNotification(notification) {
23848
24277
  await this.flushPendingErrors();
24278
+ const handledBySubagents = await this.subagents.handle(notification);
24279
+ for (const buffered of this.subagents.takeBufferedNotifications()) {
24280
+ await this.handleNotification(buffered);
24281
+ }
24282
+ if (handledBySubagents) {
24283
+ return;
24284
+ }
24285
+ if (this.subagents.shouldIgnore(notification)) {
24286
+ return;
24287
+ }
23849
24288
  const updateEvent = await this.createUpdateEvent(notification);
23850
24289
  if (updateEvent) {
23851
- await this.session.update(updateEvent);
24290
+ await this.session.update(updateEvent, this.subagents.notificationSessionId(notification));
23852
24291
  }
23853
24292
  }
24293
+ async waitForNativeSubagentSession(childThreadId) {
24294
+ return await this.subagents.waitForMaterializedSession(childThreadId);
24295
+ }
24296
+ async waitForNativeSubagents(signal) {
24297
+ await this.subagents.wait(signal);
24298
+ }
24299
+ async finishOutstandingNativeSubagents(state) {
24300
+ await this.subagents.finishOutstanding(state);
24301
+ }
23854
24302
  async flushPendingPlanUpdates() {
23855
24303
  this.cancelPlanUpdateTimer();
23856
24304
  do {
@@ -24142,15 +24590,14 @@ ${event.details}` : event.summary;
24142
24590
  this.activeImageGenerationItems.add(event.item.id);
24143
24591
  return createImageGenerationStartUpdate(event.item);
24144
24592
  case "collabAgentToolCall":
24145
- return createCollabAgentToolCallUpdate(event.item);
24593
+ return this.subagents.legacyCollaborationStarted(event.item);
24146
24594
  case "agentMessage":
24147
24595
  this.rememberAgentMessagePhase(event.item);
24148
24596
  return null;
24149
24597
  case "contextCompaction":
24150
24598
  return createContextCompactionStartUpdate(event.item);
24151
24599
  case "subAgentActivity":
24152
- this.activeSubAgentActivities.add(event.item.id);
24153
- return createSubAgentActivityUpdate(event.item, "in_progress", "tool_call");
24600
+ return this.subagents.legacyActivityStarted(event.item);
24154
24601
  case "sleep":
24155
24602
  case "userMessage":
24156
24603
  case "hookPrompt":
@@ -24198,7 +24645,7 @@ ${event.details}` : event.summary;
24198
24645
  case "webSearch":
24199
24646
  return createWebSearchCompleteUpdate(event.item);
24200
24647
  case "collabAgentToolCall":
24201
- return createCollabAgentToolCallCompleteUpdate(event.item);
24648
+ return this.subagents.legacyCollaborationCompleted(event.item);
24202
24649
  case "agentMessage":
24203
24650
  this.rememberAgentMessagePhase(event.item);
24204
24651
  return null;
@@ -24211,10 +24658,8 @@ ${event.details}` : event.summary;
24211
24658
  case "contextCompaction":
24212
24659
  return createContextCompactionCompleteUpdate(event.item);
24213
24660
  //ignored types
24214
- case "subAgentActivity": {
24215
- const sessionUpdate = this.activeSubAgentActivities.delete(event.item.id) ? "tool_call_update" : "tool_call";
24216
- return createSubAgentActivityUpdate(event.item, "completed", sessionUpdate);
24217
- }
24661
+ case "subAgentActivity":
24662
+ return this.subagents.legacyActivityCompleted(event.item);
24218
24663
  case "sleep":
24219
24664
  case "userMessage":
24220
24665
  case "hookPrompt":
@@ -24663,343 +25108,545 @@ ${event.stdin}
24663
25108
  }
24664
25109
  };
24665
25110
 
24666
- // src/ApprovalOptionId.ts
25111
+ // src/permissions/option-ids.ts
24667
25112
  var ApprovalOptionId = {
24668
25113
  AllowOnce: "allow_once",
24669
- AllowAlways: "allow_always",
24670
- RejectOnce: "reject_once",
25114
+ AllowForSession: "allow_for_session",
25115
+ Decline: "decline",
25116
+ Cancel: "cancel",
24671
25117
  AcceptWithExecpolicyAmendment: "accept_execpolicy_amendment",
24672
25118
  ApplyNetworkPolicyAmendment: "apply_network_policy_amendment",
24673
25119
  AllowPermissionsForTurn: "allow_permissions_turn",
25120
+ AllowPermissionsForTurnWithStrictAutoReview: "allow_permissions_turn_strict_auto_review",
24674
25121
  AllowPermissionsForSession: "allow_permissions_session",
24675
25122
  RejectPermissions: "reject_permissions"
24676
25123
  };
25124
+ var McpApprovalOptionId = {
25125
+ AllowOnce: "allow_once",
25126
+ AllowSession: "allow_session",
25127
+ AllowAlways: "allow_always",
25128
+ Decline: "decline",
25129
+ Cancel: "cancel"
25130
+ };
24677
25131
 
24678
- // src/CodexApprovalHandler.ts
24679
- function permissionOption(optionId, name, kind, codexMeta, permission) {
24680
- return {
24681
- optionId,
24682
- name,
24683
- kind,
24684
- ...codexMeta || permission ? {
24685
- _meta: {
24686
- ...permission ? { permission } : {},
24687
- ...codexMeta ? { codex: codexMeta } : {}
24688
- }
24689
- } : {}
24690
- };
25132
+ // src/permissions/json.ts
25133
+ function isRecord2(value) {
25134
+ return value !== null && typeof value === "object" && !Array.isArray(value);
24691
25135
  }
24692
- var CodexApprovalHandler = class {
24693
- connection;
24694
- sessionState;
24695
- cancellationSignal;
24696
- constructor(connection, sessionState, cancellationSignal) {
24697
- this.connection = connection;
24698
- this.sessionState = sessionState;
24699
- this.cancellationSignal = cancellationSignal;
25136
+ function recordOrNull(value) {
25137
+ return isRecord2(value) ? value : null;
25138
+ }
25139
+ function normalizeJsonValue(value) {
25140
+ if (value === null || value === void 0) return null;
25141
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
25142
+ if (typeof value === "bigint") return Number(value);
25143
+ if (Array.isArray(value)) return value.map(normalizeJsonValue);
25144
+ if (typeof value === "object") {
25145
+ return Object.fromEntries(
25146
+ Object.entries(value).filter(([, nested]) => nested !== void 0).map(([key, nested]) => [key, normalizeJsonValue(nested)])
25147
+ );
24700
25148
  }
24701
- async handleCommandExecution(params) {
24702
- try {
24703
- const sessionId = this.sessionState.sessionId;
24704
- const acpRequest = this.buildCommandPermissionRequest(sessionId, params);
24705
- const response = await this.connection.request(
24706
- methods.client.session.requestPermission,
24707
- acpRequest,
24708
- this.requestOptions()
24709
- );
24710
- return this.convertCommandResponse(params, response);
24711
- } catch (error51) {
24712
- logger.error("Error requesting command execution permission", error51);
24713
- return { decision: "cancel" };
24714
- }
25149
+ return String(value);
25150
+ }
25151
+ function normalizeJsonObject(value) {
25152
+ return Object.fromEntries(
25153
+ Object.entries(value).filter(([, nested]) => nested !== void 0).map(([key, nested]) => [key, normalizeJsonValue(nested)])
25154
+ );
25155
+ }
25156
+
25157
+ // src/permissions/command-decision-contract.ts
25158
+ function parseAvailableCommandDecisions(params) {
25159
+ if (params.availableDecisions === void 0 || params.availableDecisions === null) {
25160
+ return defaultCommandDecisions(params);
25161
+ }
25162
+ if (!Array.isArray(params.availableDecisions) || params.availableDecisions.length === 0) return void 0;
25163
+ const decisions = [];
25164
+ for (const candidate of params.availableDecisions) {
25165
+ const decision = parseCommandDecision(candidate, params);
25166
+ if (!decision) return void 0;
25167
+ decisions.push(decision);
25168
+ }
25169
+ return decisions;
25170
+ }
25171
+ function defaultCommandDecisions(params) {
25172
+ if (params.networkApprovalContext) {
25173
+ const decisions2 = ["accept", "acceptForSession"];
25174
+ for (const amendment of params.proposedNetworkPolicyAmendments ?? []) {
25175
+ decisions2.push({ applyNetworkPolicyAmendment: { network_policy_amendment: amendment } });
25176
+ }
25177
+ decisions2.push("decline", "cancel");
25178
+ return decisions2;
25179
+ }
25180
+ if (params.additionalPermissions) return ["accept", "cancel"];
25181
+ const decisions = ["accept", "acceptForSession"];
25182
+ if (params.proposedExecpolicyAmendment && params.proposedExecpolicyAmendment.length > 0) {
25183
+ decisions.push({
25184
+ acceptWithExecpolicyAmendment: { execpolicy_amendment: params.proposedExecpolicyAmendment }
25185
+ });
24715
25186
  }
24716
- async handleFileChange(params) {
24717
- try {
24718
- const sessionId = this.sessionState.sessionId;
24719
- const acpRequest = this.buildFileChangePermissionRequest(sessionId, params);
24720
- const response = await this.connection.request(
24721
- methods.client.session.requestPermission,
24722
- acpRequest,
24723
- this.requestOptions()
24724
- );
24725
- return this.convertFileChangeResponse(params, response);
24726
- } catch (error51) {
24727
- logger.error("Error requesting file change permission", error51);
24728
- return { decision: "cancel" };
25187
+ decisions.push("decline", "cancel");
25188
+ return decisions;
25189
+ }
25190
+ function parseCommandDecision(candidate, params) {
25191
+ if (candidate === "accept" || candidate === "acceptForSession" || candidate === "decline" || candidate === "cancel") {
25192
+ return candidate;
25193
+ }
25194
+ if (!isRecord2(candidate)) return void 0;
25195
+ if ("acceptWithExecpolicyAmendment" in candidate) {
25196
+ const value = candidate["acceptWithExecpolicyAmendment"];
25197
+ if (!isRecord2(value)) return void 0;
25198
+ const amendment = value["execpolicy_amendment"];
25199
+ if (!isStringArray(amendment) || amendment.length === 0) return void 0;
25200
+ if (!sameStrings(amendment, params.proposedExecpolicyAmendment)) return void 0;
25201
+ return { acceptWithExecpolicyAmendment: { execpolicy_amendment: [...amendment] } };
25202
+ }
25203
+ if ("applyNetworkPolicyAmendment" in candidate) {
25204
+ const value = candidate["applyNetworkPolicyAmendment"];
25205
+ if (!isRecord2(value)) return void 0;
25206
+ const amendment = parseNetworkAmendment(value["network_policy_amendment"]);
25207
+ if (!amendment || !params.networkApprovalContext) return void 0;
25208
+ if (amendment.host !== params.networkApprovalContext.host) return void 0;
25209
+ if (!(params.proposedNetworkPolicyAmendments ?? []).some((proposed) => sameNetworkAmendment(proposed, amendment))) {
25210
+ return void 0;
24729
25211
  }
25212
+ return { applyNetworkPolicyAmendment: { network_policy_amendment: amendment } };
24730
25213
  }
24731
- async handlePermissionsRequest(params) {
24732
- try {
24733
- const sessionId = this.sessionState.sessionId;
24734
- const acpRequest = this.buildPermissionsRequest(sessionId, params);
24735
- const response = await this.connection.request(
24736
- methods.client.session.requestPermission,
24737
- acpRequest,
24738
- this.requestOptions()
24739
- );
24740
- return this.convertPermissionsResponse(params, response);
24741
- } catch (error51) {
24742
- logger.error("Error requesting permissions", error51);
24743
- return this.rejectPermissionsResponse();
25214
+ return void 0;
25215
+ }
25216
+ function parseNetworkAmendment(value) {
25217
+ if (!isRecord2(value) || typeof value["host"] !== "string") return void 0;
25218
+ const action = value["action"];
25219
+ if (action !== "allow" && action !== "deny") return void 0;
25220
+ return { host: value["host"], action };
25221
+ }
25222
+ function isStringArray(value) {
25223
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
25224
+ }
25225
+ function sameStrings(left, right) {
25226
+ return !!right && left.length === right.length && left.every((value, index) => value === right[index]);
25227
+ }
25228
+ function sameNetworkAmendment(left, right) {
25229
+ return left.host === right.host && left.action === right.action;
25230
+ }
25231
+
25232
+ // src/permissions/options.ts
25233
+ function commandDecisionOptions(params) {
25234
+ const decisions = parseAvailableCommandDecisions(params);
25235
+ if (!decisions) return void 0;
25236
+ const options = [];
25237
+ let networkIndex = 0;
25238
+ for (const decision of decisions) {
25239
+ if (decision === "accept") {
25240
+ options.push(decisionOption(
25241
+ ApprovalOptionId.AllowOnce,
25242
+ params.networkApprovalContext ? "Yes, just this once" : "Yes, proceed",
25243
+ "allow_once",
25244
+ decision
25245
+ ));
25246
+ } else if (decision === "acceptForSession") {
25247
+ options.push(decisionOption(
25248
+ ApprovalOptionId.AllowForSession,
25249
+ params.networkApprovalContext ? "Yes, and allow this host for this conversation" : params.additionalPermissions ? "Yes, and allow these permissions for this session" : "Yes, and don't ask again for this command in this session",
25250
+ "allow_always",
25251
+ decision
25252
+ ));
25253
+ } else if (decision === "decline") {
25254
+ options.push(decisionOption(
25255
+ ApprovalOptionId.Decline,
25256
+ "No, continue without running it",
25257
+ "reject_once",
25258
+ decision
25259
+ ));
25260
+ } else if (decision === "cancel") {
25261
+ options.push(decisionOption(
25262
+ ApprovalOptionId.Cancel,
25263
+ "No, and tell Codex what to do differently",
25264
+ "reject_once",
25265
+ decision
25266
+ ));
25267
+ } else if ("acceptWithExecpolicyAmendment" in decision) {
25268
+ const prefix = renderExecPolicyPrefix(decision.acceptWithExecpolicyAmendment.execpolicy_amendment);
25269
+ if (prefix.includes("\n") || prefix.includes("\r")) continue;
25270
+ options.push(decisionOption(
25271
+ ApprovalOptionId.AcceptWithExecpolicyAmendment,
25272
+ `Yes, and don't ask again for commands that start with \`${prefix}\``,
25273
+ "allow_always",
25274
+ decision
25275
+ ));
25276
+ } else {
25277
+ const amendment = decision.applyNetworkPolicyAmendment.network_policy_amendment;
25278
+ options.push(decisionOption(
25279
+ `${ApprovalOptionId.ApplyNetworkPolicyAmendment}:${networkIndex++}`,
25280
+ amendment.action === "allow" ? "Yes, and allow this host in the future" : "No, and block this host in the future",
25281
+ amendment.action === "allow" ? "allow_always" : "reject_always",
25282
+ decision
25283
+ ));
24744
25284
  }
24745
25285
  }
24746
- requestOptions() {
24747
- return this.cancellationSignal ? { cancellationSignal: this.cancellationSignal } : void 0;
25286
+ const orderedOptions = [...options].sort(
25287
+ (left, right) => permissionOptionOrder(left.option) - permissionOptionOrder(right.option)
25288
+ );
25289
+ const hasAllow = orderedOptions.some(({ option }) => option.kind === "allow_once" || option.kind === "allow_always");
25290
+ const hasReject = orderedOptions.some(({ option }) => option.kind === "reject_once" || option.kind === "reject_always");
25291
+ const optionIds = orderedOptions.map(({ option }) => option.optionId);
25292
+ const hasUniqueOptionIds = new Set(optionIds).size === optionIds.length;
25293
+ return hasAllow && hasReject && hasUniqueOptionIds ? orderedOptions : void 0;
25294
+ }
25295
+ function permissionOptionOrder(option) {
25296
+ if (option.kind === "allow_once") return 0;
25297
+ if (option.kind === "allow_always") return 1;
25298
+ return 2;
25299
+ }
25300
+ function fileChangeDecisionOptions() {
25301
+ return [
25302
+ decisionOption(ApprovalOptionId.AllowOnce, "Yes, proceed", "allow_once", "accept"),
25303
+ decisionOption(
25304
+ ApprovalOptionId.AllowForSession,
25305
+ "Yes, and don't ask again for these files",
25306
+ "allow_always",
25307
+ "acceptForSession"
25308
+ ),
25309
+ decisionOption(
25310
+ ApprovalOptionId.Cancel,
25311
+ "No, and tell Codex what to do differently",
25312
+ "reject_once",
25313
+ "cancel"
25314
+ )
25315
+ ];
25316
+ }
25317
+ function permissionProfileOptions() {
25318
+ return [
25319
+ permissionOption(
25320
+ ApprovalOptionId.AllowPermissionsForTurn,
25321
+ "Yes, grant these permissions for this turn",
25322
+ "allow_once"
25323
+ ),
25324
+ permissionOption(
25325
+ ApprovalOptionId.AllowPermissionsForTurnWithStrictAutoReview,
25326
+ "Yes, grant for this turn with strict auto review",
25327
+ "allow_once"
25328
+ ),
25329
+ permissionOption(
25330
+ ApprovalOptionId.AllowPermissionsForSession,
25331
+ "Yes, grant these permissions for this session",
25332
+ "allow_always"
25333
+ ),
25334
+ permissionOption(
25335
+ ApprovalOptionId.RejectPermissions,
25336
+ "No, continue without permissions",
25337
+ "reject_once"
25338
+ )
25339
+ ];
25340
+ }
25341
+ function decisionOption(optionId, name, kind, decision) {
25342
+ return { option: permissionOption(optionId, name, kind), decision };
25343
+ }
25344
+ function permissionOption(optionId, name, kind) {
25345
+ return { optionId, name, kind };
25346
+ }
25347
+ function renderExecPolicyPrefix(command) {
25348
+ const script = extractWrappedScript(command);
25349
+ if (script !== void 0) return script;
25350
+ if (command.some((value) => value.includes("\0"))) return command.join(" ");
25351
+ return command.map(shlexQuote).join(" ");
25352
+ }
25353
+ function extractWrappedScript(command) {
25354
+ const executable = executableName(command[0]);
25355
+ if ((executable === "bash" || executable === "zsh" || executable === "sh") && command.length === 3 && (command[1] === "-lc" || command[1] === "-c")) {
25356
+ return command[2];
24748
25357
  }
24749
- buildCommandPermissionRequest(sessionId, params) {
24750
- const options = this.buildCommandOptions(params).map(({ option }) => option);
24751
- return {
24752
- sessionId,
24753
- toolCall: {
24754
- toolCallId: params.itemId,
24755
- kind: "execute",
24756
- status: "pending",
24757
- rawInput: params.command ? { command: stripShellPrefix(params.command), cwd: params.cwd } : null
24758
- },
24759
- options,
24760
- _meta: { codex: { params } }
24761
- };
25358
+ if (executable !== "pwsh" && executable !== "powershell") return void 0;
25359
+ const allowedFlags = /* @__PURE__ */ new Set(["-nologo", "-noprofile", "-command", "-c"]);
25360
+ for (let index = 1; index + 1 < command.length; index++) {
25361
+ const flag = command[index]?.toLowerCase();
25362
+ if (flag === void 0 || !allowedFlags.has(flag)) return void 0;
25363
+ if (flag === "-command" || flag === "-c") return command[index + 1];
24762
25364
  }
24763
- buildFileChangePermissionRequest(sessionId, params) {
24764
- const options = this.buildFileChangeOptions(params).map(({ option }) => option);
24765
- return {
24766
- sessionId,
24767
- toolCall: {
24768
- toolCallId: params.itemId,
24769
- kind: "edit",
24770
- status: "pending"
24771
- },
24772
- options,
24773
- _meta: { codex: { params } }
24774
- };
25365
+ return void 0;
25366
+ }
25367
+ function executableName(command) {
25368
+ let filename = command?.replaceAll("\\", "/").split("/").at(-1);
25369
+ while (filename !== void 0) {
25370
+ if (["bash", "zsh", "sh", "pwsh", "powershell"].includes(filename)) return filename;
25371
+ const dot = filename.lastIndexOf(".");
25372
+ if (dot <= 0) return void 0;
25373
+ filename = filename.slice(0, dot);
24775
25374
  }
24776
- buildPermissionsRequest(sessionId, params) {
24777
- const content = this.createContent([
24778
- params.reason,
24779
- this.formatRequestedPermissions(params.permissions)
24780
- ]);
24781
- return {
24782
- sessionId,
24783
- toolCall: {
24784
- toolCallId: params.itemId,
24785
- kind: "other",
24786
- status: "pending",
24787
- title: params.reason ?? "Permissions Request",
24788
- rawInput: params,
24789
- ...content ? { content } : {}
24790
- },
24791
- options: [
24792
- permissionOption(
24793
- ApprovalOptionId.AllowPermissionsForSession,
24794
- "Allow for Session",
24795
- "allow_always",
24796
- { decision: "allowPermissionsForSession", permissions: params.permissions },
24797
- this.permissionGrantMetadata(params.permissions, "session")
24798
- ),
24799
- permissionOption(
24800
- ApprovalOptionId.AllowPermissionsForTurn,
24801
- "Allow Once",
24802
- "allow_once",
24803
- { decision: "allowPermissionsForTurn", permissions: params.permissions },
24804
- this.permissionGrantMetadata(params.permissions, "turn")
24805
- ),
24806
- permissionOption(
24807
- ApprovalOptionId.RejectPermissions,
24808
- "Reject",
24809
- "reject_once",
24810
- { decision: "rejectPermissions" }
24811
- )
24812
- ],
24813
- _meta: { codex: { params } }
24814
- };
25375
+ return void 0;
25376
+ }
25377
+ function shlexQuote(value) {
25378
+ if (value.length === 0) return "''";
25379
+ const UNQUOTED = 1;
25380
+ const SINGLE_QUOTED = 2;
25381
+ const DOUBLE_QUOTED = 4;
25382
+ let offset = 0;
25383
+ let result = "";
25384
+ while (offset < value.length) {
25385
+ const start = offset;
25386
+ let allowed = UNQUOTED | SINGLE_QUOTED | DOUBLE_QUOTED;
25387
+ if (value[offset] === "^") {
25388
+ allowed = SINGLE_QUOTED;
25389
+ offset++;
25390
+ }
25391
+ while (offset < value.length) {
25392
+ const character = value[offset];
25393
+ let nextAllowed = allowed;
25394
+ if (!isShlexUnquoted(character)) nextAllowed &= ~UNQUOTED;
25395
+ if (character === "'" || character === "^" || character === "\\") nextAllowed &= ~SINGLE_QUOTED;
25396
+ if (character === "`" || character === "$" || character === "!" || character === "^") {
25397
+ nextAllowed &= ~DOUBLE_QUOTED;
25398
+ }
25399
+ if (nextAllowed === 0) break;
25400
+ allowed = nextAllowed;
25401
+ offset++;
25402
+ }
25403
+ const chunk = value.slice(start, offset);
25404
+ if ((allowed & UNQUOTED) !== 0) result += chunk;
25405
+ else if ((allowed & SINGLE_QUOTED) !== 0) result += `'${chunk}'`;
25406
+ else result += `"${chunk.replace(/["\\]/g, "\\$&")}"`;
24815
25407
  }
24816
- convertCommandResponse(params, response) {
24817
- if (response.outcome.outcome === "cancelled") {
25408
+ return result;
25409
+ }
25410
+ function isShlexUnquoted(character) {
25411
+ return /^[0-9A-Za-z]$/.test(character) || "+-./:@]_".includes(character);
25412
+ }
25413
+
25414
+ // src/permissions/metadata.ts
25415
+ var CODEX_COMMAND_PERMISSION_TITLE = "Run command?";
25416
+ var CODEX_NETWORK_PERMISSION_TITLE = "Allow network access?";
25417
+ var CODEX_FILE_CHANGE_PERMISSION_TITLE = "Make edits?";
25418
+ var CODEX_ADDITIONAL_PERMISSIONS_TITLE = "Grant permissions?";
25419
+ function requestPermissionMeta(title, reason) {
25420
+ const description = nonBlank(reason);
25421
+ const permission = {
25422
+ version: 1,
25423
+ title,
25424
+ ...description ? { description } : {}
25425
+ };
25426
+ return { permission };
25427
+ }
25428
+ function optionPermissionMeta(description) {
25429
+ const normalized = nonBlank(description);
25430
+ if (!normalized) return void 0;
25431
+ const permission = { version: 1, description: normalized };
25432
+ return { permission };
25433
+ }
25434
+ function nonBlank(value) {
25435
+ const normalized = value?.trim();
25436
+ return normalized ? normalized : void 0;
25437
+ }
25438
+
25439
+ // src/permissions/presentation.ts
25440
+ function commandToolCall(params) {
25441
+ const network = params.networkApprovalContext;
25442
+ const rawInput = {
25443
+ ...params.command ? { command: stripShellPrefix(params.command) } : {},
25444
+ ...params.cwd ? { cwd: params.cwd } : {},
25445
+ ...network?.protocol === "http" || network?.protocol === "https" ? { url: `${network.protocol}://${network.host}` } : {},
25446
+ ...params.additionalPermissions ? { additionalPermissions: params.additionalPermissions } : {}
25447
+ };
25448
+ const additionalPermissionContent = params.additionalPermissions ? permissionProfileContent(params.additionalPermissions) : [];
25449
+ return {
25450
+ toolCallId: params.itemId,
25451
+ kind: "execute",
25452
+ status: "pending",
25453
+ title: network ? `${network.protocol} network access to ${network.host}` : commandTitle(params.commandActions),
25454
+ ...Object.keys(rawInput).length > 0 ? { rawInput } : {},
25455
+ ...locationsField(unique([
25456
+ ...commandActionPaths(params.commandActions),
25457
+ ...permissionProfilePaths(params.additionalPermissions)
25458
+ ])),
25459
+ ...network ? { content: [textContent(`${network.protocol} access to ${network.host}`), ...additionalPermissionContent] } : additionalPermissionContent.length > 0 ? { content: additionalPermissionContent } : {}
25460
+ };
25461
+ }
25462
+ function fileChangeToolCall(params, permissionContext) {
25463
+ const item = permissionContext.fileChange(params.threadId, params.itemId);
25464
+ return {
25465
+ toolCallId: params.itemId,
25466
+ kind: "edit",
25467
+ status: "pending",
25468
+ title: "Edit files",
25469
+ ...locationsField(fileChangePaths(item))
25470
+ };
25471
+ }
25472
+ function additionalPermissionsToolCall(itemId, cwd, environmentId, permissions) {
25473
+ const content = permissionProfileContent(permissions);
25474
+ return {
25475
+ toolCallId: itemId,
25476
+ kind: "other",
25477
+ status: "pending",
25478
+ title: "Additional sandbox permissions",
25479
+ rawInput: { permissions, cwd, environmentId },
25480
+ ...locationsField(permissionProfilePaths(permissions)),
25481
+ ...content.length > 0 ? { content } : {}
25482
+ };
25483
+ }
25484
+ function commandTitle(actions) {
25485
+ const first = actions?.[0];
25486
+ if (!first) return "Run command";
25487
+ switch (first.type) {
25488
+ case "read":
25489
+ return actions?.length === 1 ? "Read file" : "Run command with file reads";
25490
+ case "listFiles":
25491
+ return "List files";
25492
+ case "search":
25493
+ return "Search files";
25494
+ case "unknown":
25495
+ return "Run command";
25496
+ }
25497
+ }
25498
+ function commandActionPaths(actions) {
25499
+ return unique((actions ?? []).flatMap((action) => {
25500
+ switch (action.type) {
25501
+ case "read":
25502
+ return [action.path];
25503
+ case "listFiles":
25504
+ case "search":
25505
+ return action.path ? [action.path] : [];
25506
+ case "unknown":
25507
+ return [];
25508
+ }
25509
+ }));
25510
+ }
25511
+ function fileChangePaths(item) {
25512
+ return unique(item?.changes.map((change) => change.path) ?? []);
25513
+ }
25514
+ function permissionProfilePaths(permissions) {
25515
+ const fileSystem = permissions?.fileSystem;
25516
+ return unique([
25517
+ ...fileSystem?.read ?? [],
25518
+ ...fileSystem?.write ?? [],
25519
+ ...(fileSystem?.entries ?? []).flatMap((entry) => entry.path.type === "path" ? [entry.path.path] : [])
25520
+ ]);
25521
+ }
25522
+ function permissionProfileContent(permissions) {
25523
+ const lines = [];
25524
+ const networkEnabled = permissions.network?.enabled;
25525
+ if (networkEnabled !== null && networkEnabled !== void 0) {
25526
+ lines.push(networkEnabled ? "Enable network access" : "Disable network access");
25527
+ }
25528
+ for (const entry of permissions.fileSystem?.entries ?? []) {
25529
+ switch (entry.path.type) {
25530
+ case "glob_pattern":
25531
+ lines.push(`${entry.access} filesystem pattern ${entry.path.pattern}`);
25532
+ break;
25533
+ case "special":
25534
+ lines.push(`${entry.access} Codex filesystem scope ${JSON.stringify(entry.path.value)}`);
25535
+ break;
25536
+ case "path":
25537
+ break;
25538
+ }
25539
+ }
25540
+ return lines.length > 0 ? [textContent(lines.join("\n"))] : [];
25541
+ }
25542
+ function locationsField(paths) {
25543
+ return paths.length > 0 ? { locations: paths.map((path9) => ({ path: path9 })) } : {};
25544
+ }
25545
+ function textContent(text) {
25546
+ return { type: "content", content: { type: "text", text } };
25547
+ }
25548
+ function unique(values) {
25549
+ return [...new Set(values)];
25550
+ }
25551
+
25552
+ // src/permissions/CodexApprovalHandler.ts
25553
+ var CodexApprovalHandler = class {
25554
+ constructor(connection, permissionContext, cancellationSignal) {
25555
+ this.connection = connection;
25556
+ this.permissionContext = permissionContext;
25557
+ this.cancellationSignal = cancellationSignal;
25558
+ }
25559
+ connection;
25560
+ permissionContext;
25561
+ cancellationSignal;
25562
+ async handleCommandExecution(params) {
25563
+ const authoritativeParams = params;
25564
+ const decisions = commandDecisionOptions(authoritativeParams);
25565
+ if (!decisions) {
25566
+ logger.error("Cancelling command approval without a complete authoritative decision set", void 0);
25567
+ return { decision: "cancel" };
25568
+ }
25569
+ try {
25570
+ const response = await this.requestPermission({
25571
+ sessionId: params.threadId,
25572
+ toolCall: commandToolCall(authoritativeParams),
25573
+ options: decisions.map(({ option }) => option),
25574
+ _meta: requestPermissionMeta(
25575
+ params.networkApprovalContext ? CODEX_NETWORK_PERMISSION_TITLE : CODEX_COMMAND_PERMISSION_TITLE,
25576
+ params.reason
25577
+ )
25578
+ });
25579
+ return { decision: this.selectedDecision(response, decisions) ?? "cancel" };
25580
+ } catch (error51) {
25581
+ logger.error("Error requesting command execution permission", error51);
24818
25582
  return { decision: "cancel" };
24819
25583
  }
24820
- const optionId = response.outcome.optionId;
24821
- const decision = this.buildCommandOptions(params).find(({ option }) => option.optionId === optionId)?.decision;
24822
- return { decision: decision ?? "decline" };
24823
25584
  }
24824
- convertFileChangeResponse(params, response) {
24825
- if (response.outcome.outcome === "cancelled") {
25585
+ async handleFileChange(params) {
25586
+ const decisions = fileChangeDecisionOptions();
25587
+ try {
25588
+ const response = await this.requestPermission({
25589
+ sessionId: params.threadId,
25590
+ toolCall: fileChangeToolCall(params, this.permissionContext),
25591
+ options: decisions.map(({ option }) => option),
25592
+ _meta: requestPermissionMeta(CODEX_FILE_CHANGE_PERMISSION_TITLE, params.reason)
25593
+ });
25594
+ return { decision: this.selectedDecision(response, decisions) ?? "cancel" };
25595
+ } catch (error51) {
25596
+ logger.error("Error requesting file change permission", error51);
24826
25597
  return { decision: "cancel" };
24827
25598
  }
24828
- const optionId = response.outcome.optionId;
24829
- const decision = this.buildFileChangeOptions(params).find(({ option }) => option.optionId === optionId)?.decision;
24830
- return { decision: decision ?? "decline" };
24831
25599
  }
24832
- convertPermissionsResponse(params, response) {
24833
- if (response.outcome.outcome === "cancelled") {
25600
+ async handlePermissionsRequest(params) {
25601
+ try {
25602
+ const response = await this.requestPermission({
25603
+ sessionId: params.threadId,
25604
+ toolCall: additionalPermissionsToolCall(
25605
+ params.itemId,
25606
+ params.cwd,
25607
+ params.environmentId,
25608
+ params.permissions
25609
+ ),
25610
+ options: permissionProfileOptions(),
25611
+ _meta: requestPermissionMeta(CODEX_ADDITIONAL_PERMISSIONS_TITLE, params.reason)
25612
+ });
25613
+ return this.permissionsResponse(params.permissions, response);
25614
+ } catch (error51) {
25615
+ logger.error("Error requesting permissions", error51);
24834
25616
  return this.rejectPermissionsResponse();
24835
25617
  }
25618
+ }
25619
+ requestPermission(request) {
25620
+ return this.connection.request(
25621
+ methods.client.session.requestPermission,
25622
+ request,
25623
+ this.cancellationSignal ? { cancellationSignal: this.cancellationSignal } : void 0
25624
+ );
25625
+ }
25626
+ selectedDecision(response, decisions) {
25627
+ if (response.outcome.outcome === "cancelled") return void 0;
25628
+ const optionId = response.outcome.optionId;
25629
+ return decisions.find(({ option }) => option.optionId === optionId)?.decision;
25630
+ }
25631
+ permissionsResponse(permissions, response) {
25632
+ if (response.outcome.outcome === "cancelled") return this.rejectPermissionsResponse();
24836
25633
  switch (response.outcome.optionId) {
24837
- case ApprovalOptionId.AllowPermissionsForSession:
24838
- case ApprovalOptionId.AllowAlways:
24839
- return {
24840
- permissions: this.grantedPermissions(params.permissions),
24841
- scope: "session",
24842
- strictAutoReview: false
24843
- };
24844
25634
  case ApprovalOptionId.AllowPermissionsForTurn:
24845
- case ApprovalOptionId.AllowOnce:
24846
- return {
24847
- permissions: this.grantedPermissions(params.permissions),
24848
- scope: "turn",
24849
- strictAutoReview: false
24850
- };
25635
+ return this.grantedPermissionsResponse(permissions, "turn", false);
25636
+ case ApprovalOptionId.AllowPermissionsForTurnWithStrictAutoReview:
25637
+ return this.grantedPermissionsResponse(permissions, "turn", true);
25638
+ case ApprovalOptionId.AllowPermissionsForSession:
25639
+ return this.grantedPermissionsResponse(permissions, "session", false);
25640
+ case ApprovalOptionId.RejectPermissions:
24851
25641
  default:
24852
25642
  return this.rejectPermissionsResponse();
24853
25643
  }
24854
25644
  }
24855
- buildCommandOptions(params) {
24856
- const options = [
24857
- {
24858
- option: permissionOption(ApprovalOptionId.AllowOnce, "Allow Once", "allow_once", { decision: "accept" }),
24859
- decision: "accept"
24860
- },
24861
- {
24862
- option: permissionOption(
24863
- ApprovalOptionId.AllowAlways,
24864
- params.networkApprovalContext ? "Allow Host for Session" : "Allow for Session",
24865
- "allow_always",
24866
- { decision: "acceptForSession" },
24867
- params.networkApprovalContext ? {
24868
- version: 1,
24869
- changes: [{
24870
- type: "grant",
24871
- operation: "grant",
24872
- description: `Allow access to ${params.networkApprovalContext.host} for this session`,
24873
- lifetime: { scope: "session" },
24874
- targets: [{
24875
- type: "network",
24876
- matcher: {
24877
- type: "host",
24878
- host: params.networkApprovalContext.host,
24879
- protocol: params.networkApprovalContext.protocol
24880
- }
24881
- }]
24882
- }]
24883
- } : void 0
24884
- ),
24885
- decision: "acceptForSession"
24886
- }
24887
- ];
24888
- if (params.proposedExecpolicyAmendment && params.proposedExecpolicyAmendment.length > 0) {
24889
- options.push({
24890
- option: permissionOption(
24891
- ApprovalOptionId.AcceptWithExecpolicyAmendment,
24892
- this.execpolicyAmendmentLabel(params.proposedExecpolicyAmendment),
24893
- "allow_always",
24894
- {
24895
- decision: "acceptWithExecpolicyAmendment",
24896
- execpolicyAmendment: params.proposedExecpolicyAmendment
24897
- },
24898
- {
24899
- version: 1,
24900
- changes: [{
24901
- type: "policy_rule",
24902
- operation: "add",
24903
- ruleBehavior: "allow",
24904
- description: `Allow commands starting with ${params.proposedExecpolicyAmendment.join(" ")}`,
24905
- targets: [{
24906
- type: "command",
24907
- matcher: {
24908
- type: "argv_prefix",
24909
- argv: params.proposedExecpolicyAmendment
24910
- }
24911
- }]
24912
- }]
24913
- }
24914
- ),
24915
- decision: {
24916
- acceptWithExecpolicyAmendment: {
24917
- execpolicy_amendment: params.proposedExecpolicyAmendment
24918
- }
24919
- }
24920
- });
24921
- }
24922
- params.proposedNetworkPolicyAmendments?.forEach((amendment, index) => {
24923
- options.push({
24924
- option: permissionOption(
24925
- this.networkPolicyAmendmentOptionId(index),
24926
- this.networkPolicyAmendmentLabel(amendment),
24927
- amendment.action === "allow" ? "allow_always" : "reject_always",
24928
- {
24929
- decision: "applyNetworkPolicyAmendment",
24930
- networkPolicyAmendment: amendment
24931
- },
24932
- {
24933
- version: 1,
24934
- changes: [{
24935
- type: "policy_rule",
24936
- operation: "add",
24937
- ruleBehavior: amendment.action,
24938
- description: amendment.action === "allow" ? `Allow access to ${amendment.host}` : `Block access to ${amendment.host}`,
24939
- targets: [{
24940
- type: "network",
24941
- matcher: {
24942
- type: "host",
24943
- host: amendment.host
24944
- }
24945
- }]
24946
- }]
24947
- }
24948
- ),
24949
- decision: {
24950
- applyNetworkPolicyAmendment: {
24951
- network_policy_amendment: amendment
24952
- }
24953
- }
24954
- });
24955
- });
24956
- options.push({
24957
- option: permissionOption(ApprovalOptionId.RejectOnce, "Reject", "reject_once", { decision: "decline" }),
24958
- decision: "decline"
24959
- });
24960
- return options;
24961
- }
24962
- buildFileChangeOptions(params) {
24963
- return [
24964
- {
24965
- option: permissionOption(ApprovalOptionId.AllowOnce, "Allow Once", "allow_once", { decision: "accept" }),
24966
- decision: "accept"
24967
- },
24968
- {
24969
- option: permissionOption(
24970
- ApprovalOptionId.AllowAlways,
24971
- params.grantRoot ? "Allow Root for Session" : "Allow for Session",
24972
- "allow_always",
24973
- { decision: "acceptForSession", grantRoot: params.grantRoot ?? null },
24974
- params.grantRoot ? {
24975
- version: 1,
24976
- changes: [{
24977
- type: "grant",
24978
- operation: "grant",
24979
- description: `Allow writes under ${params.grantRoot} for this session`,
24980
- lifetime: { scope: "session" },
24981
- targets: [{
24982
- type: "filesystem",
24983
- access: ["write"],
24984
- matcher: { type: "directory", path: params.grantRoot }
24985
- }]
24986
- }]
24987
- } : void 0
24988
- ),
24989
- decision: "acceptForSession"
24990
- },
24991
- {
24992
- option: permissionOption(ApprovalOptionId.RejectOnce, "Reject", "reject_once", { decision: "decline" }),
24993
- decision: "decline"
24994
- }
24995
- ];
25645
+ grantedPermissionsResponse(permissions, scope, strictAutoReview) {
25646
+ return { permissions: this.grantedPermissions(permissions), scope, strictAutoReview };
24996
25647
  }
24997
25648
  rejectPermissionsResponse() {
24998
- return {
24999
- permissions: {},
25000
- scope: "turn",
25001
- strictAutoReview: true
25002
- };
25649
+ return { permissions: {}, scope: "turn", strictAutoReview: false };
25003
25650
  }
25004
25651
  grantedPermissions(permissions) {
25005
25652
  return {
@@ -25007,117 +25654,131 @@ var CodexApprovalHandler = class {
25007
25654
  ...permissions.fileSystem ? { fileSystem: permissions.fileSystem } : {}
25008
25655
  };
25009
25656
  }
25010
- permissionGrantMetadata(permissions, scope) {
25011
- const changes = [];
25012
- const lifetime = { scope };
25013
- const suffix = scope === "session" ? " for this session" : " for this turn";
25014
- if (permissions.network?.enabled !== null && permissions.network?.enabled !== void 0) {
25015
- const allowed = permissions.network.enabled;
25016
- changes.push({
25017
- type: allowed ? "grant" : "policy_rule",
25018
- operation: allowed ? "grant" : "add",
25019
- ...allowed ? {} : { ruleBehavior: "deny" },
25020
- description: `${allowed ? "Allow" : "Deny"} network access${suffix}`,
25021
- lifetime,
25022
- targets: [{ type: "network", matcher: { type: "any" } }]
25023
- });
25024
- }
25025
- const fileSystem = permissions.fileSystem;
25026
- for (const path8 of fileSystem?.read ?? []) {
25027
- changes.push(this.fileSystemGrantChange(path8, "read", lifetime, suffix));
25028
- }
25029
- for (const path8 of fileSystem?.write ?? []) {
25030
- changes.push(this.fileSystemGrantChange(path8, "write", lifetime, suffix));
25031
- }
25032
- for (const entry of fileSystem?.entries ?? []) {
25033
- const matcher = (() => {
25034
- switch (entry.path.type) {
25035
- case "path":
25036
- return { type: "exact_path", path: entry.path.path };
25037
- case "glob_pattern":
25038
- return { type: "glob", pattern: entry.path.pattern };
25039
- case "special":
25040
- return { type: "special", provider: "codex", value: entry.path.value };
25041
- }
25042
- })();
25043
- const pathDescription = entry.path.type === "path" ? entry.path.path : entry.path.type === "glob_pattern" ? entry.path.pattern : JSON.stringify(entry.path.value);
25044
- changes.push({
25045
- type: entry.access === "deny" ? "policy_rule" : "grant",
25046
- operation: entry.access === "deny" ? "add" : "grant",
25047
- ...entry.access === "deny" ? { ruleBehavior: "deny" } : {},
25048
- description: entry.access === "deny" ? `Deny filesystem access to ${pathDescription}${suffix}` : `Allow ${entry.access} access to ${pathDescription}${suffix}`,
25049
- lifetime,
25050
- targets: [{
25051
- type: "filesystem",
25052
- ...entry.access === "deny" ? {} : { access: [entry.access] },
25053
- matcher
25054
- }]
25055
- });
25056
- }
25057
- return changes.length > 0 ? { version: 1, changes } : void 0;
25657
+ };
25658
+
25659
+ // src/permissions/lifecycle.ts
25660
+ var PermissionLifecycleContext = class {
25661
+ constructor(session) {
25662
+ this.session = session;
25058
25663
  }
25059
- fileSystemGrantChange(path8, access, lifetime, suffix) {
25060
- return {
25061
- type: "grant",
25062
- operation: "grant",
25063
- description: `Allow ${access} access to ${path8}${suffix}`,
25064
- lifetime,
25065
- targets: [{
25066
- type: "filesystem",
25067
- access: [access],
25068
- matcher: { type: "exact_path", path: path8 }
25069
- }]
25070
- };
25664
+ session;
25665
+ permissionRequestSequence = 0;
25666
+ beginPrompt() {
25667
+ return new PermissionPromptContext((serverName) => this.nextStandaloneMcpToolCallId(serverName));
25668
+ }
25669
+ nextStandaloneMcpToolCallId(serverName) {
25670
+ this.permissionRequestSequence += 1;
25671
+ return ["elicitation", this.session.sessionId, serverName, this.permissionRequestSequence].join(":");
25071
25672
  }
25072
- networkPolicyAmendmentOptionId(index) {
25073
- return `${ApprovalOptionId.ApplyNetworkPolicyAmendment}:${index}`;
25673
+ };
25674
+ var PermissionPromptContext = class {
25675
+ constructor(nextStandaloneId) {
25676
+ this.nextStandaloneId = nextStandaloneId;
25074
25677
  }
25075
- execpolicyAmendmentLabel(amendment) {
25076
- const commandPrefix = amendment.join(" ");
25077
- if (!commandPrefix || commandPrefix.includes("\n") || commandPrefix.includes("\r")) {
25078
- return "Allow and Remember Command Pattern";
25678
+ nextStandaloneId;
25679
+ fileChanges = /* @__PURE__ */ new Map();
25680
+ pendingMcpApprovals = /* @__PURE__ */ new Map();
25681
+ handleNotification(notification) {
25682
+ switch (notification.method) {
25683
+ case "item/started":
25684
+ this.handleItemStarted(notification.params.threadId, notification.params.item);
25685
+ return;
25686
+ case "item/completed":
25687
+ this.handleItemCompleted(notification.params.threadId, notification.params.item);
25688
+ return;
25689
+ case "turn/completed":
25690
+ this.clearTransientState(notification.params.threadId);
25691
+ return;
25692
+ case "serverRequest/resolved":
25693
+ this.pendingMcpApprovals.delete(notification.params.threadId);
25694
+ return;
25695
+ default:
25696
+ return;
25079
25697
  }
25080
- return `Allow Commands Starting With \`${commandPrefix}\``;
25081
25698
  }
25082
- networkPolicyAmendmentLabel(amendment) {
25083
- return amendment.action === "allow" ? `Allow ${amendment.host} in the Future` : `Block ${amendment.host} in the Future`;
25699
+ fileChange(threadId, itemId) {
25700
+ return this.fileChanges.get(threadId)?.get(itemId);
25084
25701
  }
25085
- formatRequestedPermissions(permissions) {
25086
- const content = [];
25087
- if (permissions.network?.enabled !== void 0 && permissions.network.enabled !== null) {
25088
- content.push(`Network Access: ${permissions.network.enabled}`);
25089
- }
25090
- if (permissions.fileSystem?.read?.length) {
25091
- content.push(`File System Read Access: ${permissions.fileSystem.read.join(", ")}`);
25092
- }
25093
- if (permissions.fileSystem?.write?.length) {
25094
- content.push(`File System Write Access: ${permissions.fileSystem.write.join(", ")}`);
25702
+ popPendingMcpApproval(threadId, serverName) {
25703
+ const byServer = this.pendingMcpApprovals.get(threadId);
25704
+ if (!byServer) return void 0;
25705
+ const pending = byServer.get(serverName);
25706
+ if (pending?.length !== 1) return void 0;
25707
+ const callId = pending[0];
25708
+ byServer.delete(serverName);
25709
+ if (byServer.size === 0) this.pendingMcpApprovals.delete(threadId);
25710
+ return callId;
25711
+ }
25712
+ nextStandaloneMcpToolCallId(serverName) {
25713
+ return this.nextStandaloneId(serverName);
25714
+ }
25715
+ handleItemStarted(threadId, item) {
25716
+ if (item.type === "fileChange") {
25717
+ const byItem = this.fileChanges.get(threadId) ?? /* @__PURE__ */ new Map();
25718
+ byItem.set(item.id, item);
25719
+ this.fileChanges.set(threadId, byItem);
25720
+ return;
25095
25721
  }
25096
- if (permissions.fileSystem?.entries?.length) {
25097
- content.push(`File System Entries: ${JSON.stringify(permissions.fileSystem.entries)}`);
25722
+ if (item.type !== "mcpToolCall") return;
25723
+ const byServer = this.pendingMcpApprovals.get(threadId) ?? /* @__PURE__ */ new Map();
25724
+ const pending = byServer.get(item.server);
25725
+ if (pending) pending.push(item.id);
25726
+ else byServer.set(item.server, [item.id]);
25727
+ this.pendingMcpApprovals.set(threadId, byServer);
25728
+ }
25729
+ handleItemCompleted(threadId, item) {
25730
+ if (item.type === "fileChange") {
25731
+ const byItem = this.fileChanges.get(threadId);
25732
+ byItem?.delete(item.id);
25733
+ if (byItem?.size === 0) this.fileChanges.delete(threadId);
25734
+ return;
25098
25735
  }
25099
- return content.length > 0 ? content.join("\n\n") : null;
25736
+ if (item.type !== "mcpToolCall") return;
25737
+ const byServer = this.pendingMcpApprovals.get(threadId);
25738
+ if (!byServer) return;
25739
+ const pending = byServer.get(item.server);
25740
+ if (!pending) return;
25741
+ const index = pending.indexOf(item.id);
25742
+ if (index >= 0) pending.splice(index, 1);
25743
+ if (pending.length === 0) byServer.delete(item.server);
25744
+ if (byServer.size === 0) this.pendingMcpApprovals.delete(threadId);
25100
25745
  }
25101
- createContent(lines) {
25102
- const text = lines.filter((line) => !!line).join("\n\n");
25103
- if (!text) return void 0;
25104
- return [{
25105
- type: "content",
25106
- content: {
25107
- type: "text",
25108
- text
25109
- }
25110
- }];
25746
+ clearTransientState(threadId) {
25747
+ this.fileChanges.delete(threadId);
25748
+ this.pendingMcpApprovals.delete(threadId);
25111
25749
  }
25112
25750
  };
25113
25751
 
25114
- // src/McpApprovalOptionId.ts
25115
- var McpApprovalOptionId = {
25116
- AllowOnce: "allow_once",
25117
- AllowSession: "allow_session",
25118
- AllowAlways: "allow_always",
25119
- Decline: "decline"
25120
- };
25752
+ // src/permissions/plan-review.ts
25753
+ var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
25754
+ var REVISE_PLAN_OPTION_ID = "revise_plan";
25755
+ function planImplementationPermissionRequest(sessionId, plan) {
25756
+ return {
25757
+ sessionId,
25758
+ toolCall: {
25759
+ toolCallId: planImplementationToolCallId(plan),
25760
+ title: "Implement this plan?",
25761
+ kind: "switch_mode",
25762
+ status: "pending",
25763
+ rawInput: { plan: plan.text }
25764
+ },
25765
+ options: [
25766
+ { optionId: IMPLEMENT_PLAN_OPTION_ID, name: "Yes, implement this plan", kind: "allow_once" },
25767
+ {
25768
+ optionId: REVISE_PLAN_OPTION_ID,
25769
+ name: "No, and tell Codex what to do differently",
25770
+ kind: "reject_once"
25771
+ }
25772
+ ],
25773
+ _meta: { codex: { kind: "plan_review", planItemId: plan.itemId } }
25774
+ };
25775
+ }
25776
+ function planImplementationApproved(response) {
25777
+ return response.outcome.outcome === "selected" && response.outcome.optionId === IMPLEMENT_PLAN_OPTION_ID;
25778
+ }
25779
+ function planImplementationToolCallId(plan) {
25780
+ return `plan-review:${plan.itemId}`;
25781
+ }
25121
25782
 
25122
25783
  // src/ElicitationCapabilities.ts
25123
25784
  function clientSupportsFormElicitation(clientCapabilities) {
@@ -25127,57 +25788,146 @@ function clientSupportsUrlElicitation(clientCapabilities) {
25127
25788
  return clientCapabilities?.elicitation?.url != null;
25128
25789
  }
25129
25790
 
25130
- // src/CodexElicitationHandler.ts
25131
- var ELICITATION_OPTIONS = [
25132
- { optionId: "accept", name: "Accept", kind: "allow_once" },
25133
- { optionId: "decline", name: "Decline", kind: "reject_once" }
25134
- ];
25135
- var USER_INPUT_OTHER_FIELD_SUFFIX = "__other";
25791
+ // src/permissions/mcp.ts
25136
25792
  function parsePersistOptions(meta3) {
25137
25793
  const result = /* @__PURE__ */ new Set();
25138
- if (!meta3 || typeof meta3 !== "object") return result;
25794
+ if (!isRecord2(meta3)) return result;
25139
25795
  const persist = meta3["persist"];
25140
- if (persist === "session") {
25141
- result.add("session");
25142
- } else if (persist === "always") {
25143
- result.add("always");
25144
- } else if (Array.isArray(persist)) {
25796
+ if (persist === "session") result.add("session");
25797
+ else if (persist === "always") result.add("always");
25798
+ else if (Array.isArray(persist)) {
25145
25799
  if (persist.includes("session")) result.add("session");
25146
25800
  if (persist.includes("always")) result.add("always");
25147
25801
  }
25148
25802
  return result;
25149
25803
  }
25150
25804
  function isMcpToolCallApproval(meta3) {
25151
- return meta3 !== null && typeof meta3 === "object" && meta3["codex_approval_kind"] === "mcp_tool_call";
25152
- }
25153
- function isRecord2(value) {
25154
- return value !== null && typeof value === "object" && !Array.isArray(value);
25155
- }
25156
- function normalizeJsonValue(value) {
25157
- if (value === null || value === void 0) {
25158
- return null;
25805
+ return isRecord2(meta3) && meta3["codex_approval_kind"] === "mcp_tool_call";
25806
+ }
25807
+ function buildMcpPermissionOptions(isToolApproval, persistOptions) {
25808
+ const options = [permissionOption2(
25809
+ isToolApproval ? McpApprovalOptionId.AllowOnce : "accept",
25810
+ "Allow",
25811
+ "allow_once",
25812
+ isToolApproval ? "Run the tool and continue." : "Allow this request and continue."
25813
+ )];
25814
+ if (persistOptions.has("session")) {
25815
+ options.push(permissionOption2(
25816
+ McpApprovalOptionId.AllowSession,
25817
+ "Allow for this session",
25818
+ "allow_always",
25819
+ isToolApproval ? "Run the tool and remember this choice for this session." : "Allow this request and remember this choice for this session."
25820
+ ));
25159
25821
  }
25160
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
25161
- return value;
25822
+ if (persistOptions.has("always")) {
25823
+ options.push(permissionOption2(
25824
+ McpApprovalOptionId.AllowAlways,
25825
+ "Always allow",
25826
+ "allow_always",
25827
+ isToolApproval ? "Run the tool and remember this choice for future tool calls." : "Allow this request and remember this choice for future requests."
25828
+ ));
25162
25829
  }
25163
- if (typeof value === "bigint") {
25164
- return Number(value);
25830
+ if (isToolApproval) {
25831
+ options.push(permissionOption2(
25832
+ McpApprovalOptionId.Cancel,
25833
+ "Cancel",
25834
+ "reject_once",
25835
+ "Cancel this tool call"
25836
+ ));
25837
+ } else {
25838
+ options.push(
25839
+ permissionOption2(
25840
+ McpApprovalOptionId.Decline,
25841
+ "Deny",
25842
+ "reject_once",
25843
+ "Decline this request and continue."
25844
+ ),
25845
+ permissionOption2(McpApprovalOptionId.Cancel, "Cancel", "reject_once", "Cancel this request")
25846
+ );
25165
25847
  }
25166
- if (Array.isArray(value)) {
25167
- return value.map(normalizeJsonValue);
25848
+ return options;
25849
+ }
25850
+ function buildMcpPermissionRequest(sessionId, params, context, nextStandaloneToolCallId) {
25851
+ const messageContent = {
25852
+ type: "content",
25853
+ content: { type: "text", text: params.message }
25854
+ };
25855
+ const options = buildMcpPermissionOptions(context.isToolApproval, context.persistOptions);
25856
+ if (params.mode === "form" || params.mode === "openai/form") {
25857
+ if (context.correlatedCallId !== void 0) {
25858
+ return {
25859
+ request: {
25860
+ sessionId,
25861
+ toolCall: {
25862
+ toolCallId: context.correlatedCallId,
25863
+ kind: "execute",
25864
+ status: "pending"
25865
+ },
25866
+ _meta: { is_mcp_tool_approval: true },
25867
+ options
25868
+ },
25869
+ correlatedCallId: context.correlatedCallId
25870
+ };
25871
+ }
25872
+ return {
25873
+ request: {
25874
+ sessionId,
25875
+ toolCall: {
25876
+ toolCallId: nextStandaloneToolCallId(),
25877
+ kind: context.isToolApproval ? "execute" : "other",
25878
+ status: "pending",
25879
+ content: [messageContent],
25880
+ rawInput: { serverName: params.serverName, schema: params.requestedSchema }
25881
+ },
25882
+ ...context.isToolApproval ? { _meta: { is_mcp_tool_approval: true } } : {},
25883
+ options
25884
+ },
25885
+ correlatedCallId: void 0
25886
+ };
25168
25887
  }
25169
- if (typeof value === "object") {
25170
- return Object.fromEntries(
25171
- Object.entries(value).filter(([, nested]) => nested !== void 0).map(([key, nested]) => [key, normalizeJsonValue(nested)])
25172
- );
25888
+ return {
25889
+ request: {
25890
+ sessionId,
25891
+ toolCall: {
25892
+ toolCallId: `elicitation-${params.elicitationId}`,
25893
+ kind: "fetch",
25894
+ status: "pending",
25895
+ content: [messageContent],
25896
+ rawInput: { serverName: params.serverName, url: params.url }
25897
+ },
25898
+ options
25899
+ },
25900
+ correlatedCallId: void 0
25901
+ };
25902
+ }
25903
+ function convertMcpPermissionResponse(response, isToolApproval, persistOptions) {
25904
+ if (response.outcome.outcome === "cancelled") return cancelledResponse();
25905
+ switch (response.outcome.optionId) {
25906
+ case McpApprovalOptionId.AllowSession:
25907
+ return persistOptions.has("session") ? { action: "accept", content: null, _meta: { persist: "session" } } : cancelledResponse();
25908
+ case McpApprovalOptionId.AllowAlways:
25909
+ return persistOptions.has("always") ? { action: "accept", content: null, _meta: { persist: "always" } } : cancelledResponse();
25910
+ case McpApprovalOptionId.AllowOnce:
25911
+ return isToolApproval ? { action: "accept", content: null, _meta: null } : cancelledResponse();
25912
+ case "accept":
25913
+ return !isToolApproval ? { action: "accept", content: null, _meta: null } : cancelledResponse();
25914
+ case McpApprovalOptionId.Decline:
25915
+ return !isToolApproval ? { action: "decline", content: null, _meta: null } : cancelledResponse();
25916
+ case McpApprovalOptionId.Cancel:
25917
+ default:
25918
+ return cancelledResponse();
25173
25919
  }
25174
- return String(value);
25175
25920
  }
25176
- function normalizeJsonObject(value) {
25177
- return Object.fromEntries(
25178
- Object.entries(value).filter(([, nested]) => nested !== void 0).map(([key, nested]) => [key, normalizeJsonValue(nested)])
25179
- );
25921
+ function permissionOption2(optionId, name, kind, description) {
25922
+ const meta3 = optionPermissionMeta(description);
25923
+ return { optionId, name, kind, ...meta3 ? { _meta: meta3 } : {} };
25924
+ }
25925
+ function cancelledResponse() {
25926
+ return { action: "cancel", content: null, _meta: null };
25180
25927
  }
25928
+
25929
+ // src/CodexElicitationHandler.ts
25930
+ var USER_INPUT_OTHER_FIELD_SUFFIX = "__other";
25181
25931
  function normalizeElicitationSchema(value) {
25182
25932
  const normalized = normalizeElicitationSchemaValue(value);
25183
25933
  if (!isRecord2(normalized)) {
@@ -25213,40 +25963,6 @@ function normalizeElicitationSchemaValue(value) {
25213
25963
  }
25214
25964
  return result;
25215
25965
  }
25216
- function metaRecord(meta3) {
25217
- return isRecord2(meta3) ? meta3 : null;
25218
- }
25219
- function persistChoiceOption(value) {
25220
- switch (value) {
25221
- case "once":
25222
- return { const: "once", title: "Allow once" };
25223
- case "session":
25224
- return { const: "session", title: "Allow for this session" };
25225
- case "always":
25226
- return { const: "always", title: "Allow and don't ask again" };
25227
- }
25228
- }
25229
- function addPersistChoiceToSchema(schema, persistOptions) {
25230
- if (persistOptions.size === 0) {
25231
- return schema;
25232
- }
25233
- const choices = ["once"];
25234
- if (persistOptions.has("session")) choices.push("session");
25235
- if (persistOptions.has("always")) choices.push("always");
25236
- return {
25237
- ...schema,
25238
- properties: {
25239
- ...schema.properties,
25240
- persist: {
25241
- type: "string",
25242
- title: "Approval scope",
25243
- oneOf: choices.map(persistChoiceOption),
25244
- default: "once"
25245
- }
25246
- },
25247
- required: Array.from(/* @__PURE__ */ new Set([...schema.required ?? [], "persist"]))
25248
- };
25249
- }
25250
25966
  function contentRecord(content) {
25251
25967
  return isRecord2(content) ? content : {};
25252
25968
  }
@@ -25258,7 +25974,7 @@ function jsonObjectOrNull(content) {
25258
25974
  return Object.fromEntries(entries.map(([key, value]) => [key, normalizeJsonValue(value)]));
25259
25975
  }
25260
25976
  function elicitationResponseMeta(response, context, persist = void 0) {
25261
- const responseMeta = metaRecord(response._meta);
25977
+ const responseMeta = recordOrNull(response._meta);
25262
25978
  const meta3 = responseMeta ? normalizeJsonObject(responseMeta) : {};
25263
25979
  if (context.isToolApproval) {
25264
25980
  delete meta3["persist"];
@@ -25289,22 +26005,9 @@ function userInputResponseValue(content, fieldId) {
25289
26005
  }
25290
26006
  return value;
25291
26007
  }
25292
- function buildToolApprovalOptions(persistOptions) {
25293
- const options = [
25294
- { optionId: McpApprovalOptionId.AllowOnce, name: "Allow", kind: "allow_once" }
25295
- ];
25296
- if (persistOptions.has("session")) {
25297
- options.push({ optionId: McpApprovalOptionId.AllowSession, name: "Allow for This Session", kind: "allow_always" });
25298
- }
25299
- if (persistOptions.has("always")) {
25300
- options.push({ optionId: McpApprovalOptionId.AllowAlways, name: "Allow and Don't Ask Again", kind: "allow_always" });
25301
- }
25302
- options.push({ optionId: McpApprovalOptionId.Decline, name: "Decline", kind: "reject_once" });
25303
- return options;
25304
- }
25305
26008
  var CodexElicitationHandler = class {
25306
26009
  connection;
25307
- sessionState;
26010
+ permissionContext;
25308
26011
  clientCapabilities;
25309
26012
  cancellationSignal;
25310
26013
  // In Rust, the MCP elicitation handler receives ElicitationRequestEvent directly from the MCP
@@ -25316,32 +26019,21 @@ var CodexElicitationHandler = class {
25316
26019
  // reaches the client.
25317
26020
  //
25318
26021
  // Workaround: before requesting approval, Codex emits an item/started notification with an
25319
- // mcpToolCall item carrying the call id and server name. We store (threadId, serverName) → callId
25320
- // here so the elicitation request can correlate back to the already-rendered tool call item.
26022
+ // mcpToolCall item carrying the call id and server name. The shared permission lifecycle stores
26023
+ // (threadId, serverName) → callId so this request can correlate to the rendered tool call item.
25321
26024
  //
25322
- // Multiple calls are safe because Codex requests approval synchronously — it blocks on one tool
25323
- // call's elicitation before starting the next, so there is at most one pending approval per
25324
- // (threadId, serverName).
25325
- pendingMcpApprovals = /* @__PURE__ */ new Map();
25326
26025
  // The app-server handler exposes URL elicitationId, while serverRequest/resolved only exposes
25327
26026
  // threadId here, so accepted URL elicitations are completed at thread scope.
25328
26027
  pendingUrlElicitations = /* @__PURE__ */ new Map();
25329
- constructor(connection, sessionState, clientCapabilities = null, cancellationSignal) {
26028
+ constructor(connection, permissionContext, clientCapabilities = null, cancellationSignal) {
25330
26029
  this.connection = connection;
25331
- this.sessionState = sessionState;
26030
+ this.permissionContext = permissionContext;
25332
26031
  this.clientCapabilities = clientCapabilities;
25333
26032
  this.cancellationSignal = cancellationSignal;
25334
26033
  }
25335
26034
  async handleNotification(notification) {
25336
26035
  switch (notification.method) {
25337
- case "item/started":
25338
- this.handleItemStarted(notification.params);
25339
- return;
25340
- case "item/completed":
25341
- this.handleItemCompleted(notification.params);
25342
- return;
25343
26036
  case "serverRequest/resolved":
25344
- this.clearThread(notification.params.threadId);
25345
26037
  await this.completeUrlElicitations(notification.params.threadId);
25346
26038
  return;
25347
26039
  default:
@@ -25357,29 +26049,39 @@ var CodexElicitationHandler = class {
25357
26049
  this.buildElicitationRequest(params, context),
25358
26050
  this.requestOptions()
25359
26051
  );
25360
- const result = this.convertElicitationResponse(response2, context);
25361
- if (params.mode === "url" && result.action === "accept") {
26052
+ const result2 = this.convertElicitationResponse(response2, context);
26053
+ if (params.mode === "url" && result2.action === "accept") {
25362
26054
  this.trackUrlElicitation(params.threadId, params.elicitationId);
25363
26055
  }
25364
- await this.publishAcceptedMcpToolApproval(context, result.action === "accept");
25365
- return result;
26056
+ await this.publishAcceptedMcpToolApproval(params.threadId, context, result2.action === "accept");
26057
+ return result2;
25366
26058
  }
25367
- const { request, correlatedCallId } = this.buildPermissionRequest(params, context);
26059
+ if (!this.canUsePermissionFallback(params)) {
26060
+ return { action: "cancel", content: null, _meta: null };
26061
+ }
26062
+ const { request, correlatedCallId } = buildMcpPermissionRequest(
26063
+ params.threadId,
26064
+ params,
26065
+ context,
26066
+ () => this.permissionContext.nextStandaloneMcpToolCallId(params.serverName)
26067
+ );
25368
26068
  const response = await this.connection.request(
25369
26069
  methods.client.session.requestPermission,
25370
26070
  request,
25371
26071
  this.requestOptions()
25372
26072
  );
25373
- if (correlatedCallId !== void 0 && response.outcome.outcome !== "cancelled") {
25374
- const optionId = response.outcome.optionId;
25375
- if (optionId !== McpApprovalOptionId.Decline) {
25376
- await this.connection.notify(methods.client.session.update, {
25377
- sessionId: this.sessionState.sessionId,
25378
- update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
25379
- });
25380
- }
26073
+ const result = convertMcpPermissionResponse(
26074
+ response,
26075
+ context.isToolApproval,
26076
+ context.persistOptions
26077
+ );
26078
+ if (correlatedCallId !== void 0 && result.action === "accept") {
26079
+ await this.connection.notify(methods.client.session.update, {
26080
+ sessionId: params.threadId,
26081
+ update: { sessionUpdate: "tool_call_update", toolCallId: correlatedCallId, status: "in_progress" }
26082
+ });
25381
26083
  }
25382
- return this.convertPermissionResponse(response);
26084
+ return result;
25383
26085
  } catch (error51) {
25384
26086
  logger.error("Error handling MCP elicitation request", error51);
25385
26087
  return { action: "cancel", content: null, _meta: null };
@@ -25449,12 +26151,17 @@ var CodexElicitationHandler = class {
25449
26151
  }
25450
26152
  }
25451
26153
  createMcpElicitationContext(params) {
25452
- const isToolApproval = isMcpToolCallApproval(params._meta);
26154
+ const isToolApproval = isMcpToolCallApproval(params._meta) && this.isMessageOnlyForm(params);
25453
26155
  const persistOptions = parsePersistOptions(params._meta);
25454
- const correlatedCallId = isToolApproval && (params.mode === "form" || params.mode === "openai/form") ? this.popPendingApproval(params.threadId, params.serverName) : void 0;
25455
- return { isToolApproval, persistOptions, correlatedCallId };
26156
+ const correlatedCallId = isToolApproval ? this.permissionContext.popPendingMcpApproval(params.threadId, params.serverName) : void 0;
26157
+ return {
26158
+ isToolApproval,
26159
+ persistOptions,
26160
+ correlatedCallId
26161
+ };
25456
26162
  }
25457
26163
  shouldUseAcpElicitation(params) {
26164
+ if (this.isMessageOnlyForm(params)) return false;
25458
26165
  switch (params.mode) {
25459
26166
  case "form":
25460
26167
  return clientSupportsFormElicitation(this.clientCapabilities);
@@ -25464,23 +26171,28 @@ var CodexElicitationHandler = class {
25464
26171
  return false;
25465
26172
  }
25466
26173
  }
26174
+ canUsePermissionFallback(params) {
26175
+ return params.mode === "url" || this.isMessageOnlyForm(params);
26176
+ }
26177
+ isMessageOnlyForm(params) {
26178
+ if (params.mode !== "form" && params.mode !== "openai/form") return false;
26179
+ if (params.requestedSchema === null) return true;
26180
+ if (!isRecord2(params.requestedSchema)) return false;
26181
+ return params.requestedSchema["type"] === "object" && isRecord2(params.requestedSchema["properties"]) && Object.keys(params.requestedSchema["properties"]).length === 0;
26182
+ }
25467
26183
  buildElicitationRequest(params, context) {
25468
26184
  const base = {
25469
- sessionId: this.sessionState.sessionId,
26185
+ sessionId: params.threadId,
25470
26186
  ...context.correlatedCallId ? { toolCallId: context.correlatedCallId } : {},
25471
26187
  message: params.message,
25472
- _meta: metaRecord(params._meta)
26188
+ _meta: recordOrNull(params._meta)
25473
26189
  };
25474
26190
  switch (params.mode) {
25475
26191
  case "form": {
25476
- const requestedSchema = context.isToolApproval ? addPersistChoiceToSchema(
25477
- normalizeElicitationSchema(params.requestedSchema),
25478
- context.persistOptions
25479
- ) : normalizeElicitationSchema(params.requestedSchema);
25480
26192
  return {
25481
26193
  ...base,
25482
26194
  mode: "form",
25483
- requestedSchema
26195
+ requestedSchema: normalizeElicitationSchema(params.requestedSchema)
25484
26196
  };
25485
26197
  }
25486
26198
  case "url":
@@ -25542,7 +26254,7 @@ var CodexElicitationHandler = class {
25542
26254
  }
25543
26255
  const firstQuestion = params.questions[0];
25544
26256
  return {
25545
- sessionId: this.sessionState.sessionId,
26257
+ sessionId: params.threadId,
25546
26258
  toolCallId: params.itemId,
25547
26259
  mode: "form",
25548
26260
  message: params.questions.length === 1 && firstQuestion ? firstQuestion.question : "Input requested",
@@ -25551,90 +26263,20 @@ var CodexElicitationHandler = class {
25551
26263
  properties,
25552
26264
  required: required2
25553
26265
  },
25554
- _meta: {
25555
- codex: {
25556
- autoResolutionMs: params.autoResolutionMs
25557
- }
25558
- }
25559
- };
25560
- }
25561
- buildPermissionRequest(params, context) {
25562
- const sessionId = this.sessionState.sessionId;
25563
- const messageContent = {
25564
- type: "content",
25565
- content: { type: "text", text: params.message }
25566
- };
25567
- const options = context.isToolApproval ? buildToolApprovalOptions(context.persistOptions) : ELICITATION_OPTIONS;
25568
- if (params.mode === "form" || params.mode === "openai/form") {
25569
- if (context.correlatedCallId !== void 0) {
25570
- return {
25571
- request: {
25572
- sessionId,
25573
- toolCall: {
25574
- toolCallId: context.correlatedCallId,
25575
- kind: "execute",
25576
- status: "pending"
25577
- // content: [messageContent], — omitted: already rendered via item/started
25578
- // rawInput: { ... } — omitted: same reason
25579
- },
25580
- _meta: { is_mcp_tool_approval: true },
25581
- options
25582
- },
25583
- correlatedCallId: context.correlatedCallId
25584
- };
25585
- }
25586
- return {
25587
- request: {
25588
- sessionId,
25589
- toolCall: {
25590
- toolCallId: `elicitation-${params.serverName}`,
25591
- kind: context.isToolApproval ? "execute" : "other",
25592
- status: "pending",
25593
- content: [messageContent],
25594
- rawInput: { serverName: params.serverName, schema: params.requestedSchema }
25595
- },
25596
- ...context.isToolApproval ? { _meta: { is_mcp_tool_approval: true } } : {},
25597
- options
25598
- },
25599
- correlatedCallId: void 0
25600
- };
25601
- } else {
25602
- return {
25603
- request: {
25604
- sessionId,
25605
- toolCall: {
25606
- toolCallId: `elicitation-${params.elicitationId}`,
25607
- kind: "fetch",
25608
- status: "pending",
25609
- content: [messageContent],
25610
- rawInput: { serverName: params.serverName, url: params.url }
25611
- },
25612
- options
25613
- },
25614
- correlatedCallId: void 0
25615
- };
25616
- }
25617
- }
25618
- convertPermissionResponse(response) {
25619
- if (response.outcome.outcome === "cancelled") {
25620
- return { action: "cancel", content: null, _meta: null };
25621
- }
25622
- const optionId = response.outcome.optionId;
25623
- if (optionId === McpApprovalOptionId.AllowSession) {
25624
- return { action: "accept", content: null, _meta: { persist: "session" } };
25625
- }
25626
- if (optionId === McpApprovalOptionId.AllowAlways) {
25627
- return { action: "accept", content: null, _meta: { persist: "always" } };
25628
- }
25629
- if (optionId === McpApprovalOptionId.AllowOnce || optionId === "accept") {
25630
- return { action: "accept", content: null, _meta: null };
25631
- }
25632
- return { action: "decline", content: null, _meta: null };
26266
+ _meta: {
26267
+ codex: {
26268
+ autoResolutionMs: params.autoResolutionMs
26269
+ }
26270
+ }
26271
+ };
25633
26272
  }
25634
26273
  convertElicitationResponse(response, context) {
25635
26274
  if (CreateElicitationResponse.isAccept(response)) {
25636
26275
  const content = contentRecord(response.content);
25637
26276
  const persist = context.isToolApproval ? content["persist"] : void 0;
26277
+ if (context.isToolApproval && !this.isAllowedToolApprovalPersist(persist, context.persistOptions)) {
26278
+ return { action: "cancel", content: null, _meta: null };
26279
+ }
25638
26280
  if (persist === "session" || persist === "always" || persist === "once") {
25639
26281
  delete content["persist"];
25640
26282
  }
@@ -25645,7 +26287,7 @@ var CodexElicitationHandler = class {
25645
26287
  };
25646
26288
  }
25647
26289
  if (CreateElicitationResponse.isDecline(response)) {
25648
- return { action: "decline", content: null, _meta: elicitationResponseMeta(response, context) };
26290
+ return context.isToolApproval ? { action: "cancel", content: null, _meta: elicitationResponseMeta(response, context) } : { action: "decline", content: null, _meta: elicitationResponseMeta(response, context) };
25649
26291
  }
25650
26292
  if (CreateElicitationResponse.isCancel(response)) {
25651
26293
  return { action: "cancel", content: null, _meta: elicitationResponseMeta(response, context) };
@@ -25655,6 +26297,9 @@ var CodexElicitationHandler = class {
25655
26297
  }
25656
26298
  return { action: "cancel", content: null, _meta: null };
25657
26299
  }
26300
+ isAllowedToolApprovalPersist(persist, persistOptions) {
26301
+ return persist === void 0 || persist === "once" || persist === "session" && persistOptions.has("session") || persist === "always" && persistOptions.has("always");
26302
+ }
25658
26303
  convertUserInputResponse(response, params) {
25659
26304
  if (!CreateElicitationResponse.isAccept(response)) {
25660
26305
  return { answers: {} };
@@ -25673,12 +26318,12 @@ var CodexElicitationHandler = class {
25673
26318
  }
25674
26319
  return { answers };
25675
26320
  }
25676
- async publishAcceptedMcpToolApproval(context, accepted) {
26321
+ async publishAcceptedMcpToolApproval(sessionId, context, accepted) {
25677
26322
  if (!accepted || context.correlatedCallId === void 0) {
25678
26323
  return;
25679
26324
  }
25680
26325
  await this.connection.notify(methods.client.session.update, {
25681
- sessionId: this.sessionState.sessionId,
26326
+ sessionId,
25682
26327
  update: { sessionUpdate: "tool_call_update", toolCallId: context.correlatedCallId, status: "in_progress" }
25683
26328
  });
25684
26329
  }
@@ -25702,37 +26347,6 @@ var CodexElicitationHandler = class {
25702
26347
  });
25703
26348
  }
25704
26349
  }
25705
- handleItemStarted(event) {
25706
- if (event.item.type !== "mcpToolCall") {
25707
- return;
25708
- }
25709
- this.pendingMcpApprovals.set(this.key(event.threadId, event.item.server), event.item.id);
25710
- }
25711
- handleItemCompleted(event) {
25712
- if (event.item.type !== "mcpToolCall") {
25713
- return;
25714
- }
25715
- this.popPendingApproval(event.threadId, event.item.server);
25716
- }
25717
- popPendingApproval(threadId, serverName) {
25718
- const key = this.key(threadId, serverName);
25719
- const callId = this.pendingMcpApprovals.get(key);
25720
- this.pendingMcpApprovals.delete(key);
25721
- return callId;
25722
- }
25723
- clearThread(threadId) {
25724
- for (const key of this.pendingMcpApprovals.keys()) {
25725
- if (this.belongsToThread(key, threadId)) {
25726
- this.pendingMcpApprovals.delete(key);
25727
- }
25728
- }
25729
- }
25730
- key(threadId, serverName) {
25731
- return `${threadId}:${serverName}`;
25732
- }
25733
- belongsToThread(key, threadId) {
25734
- return key.startsWith(`${threadId}:`);
25735
- }
25736
26350
  };
25737
26351
 
25738
26352
  // src/CodexAuthMethod.ts
@@ -25788,13 +26402,14 @@ function isCodexAuthRequest(request) {
25788
26402
  }
25789
26403
 
25790
26404
  // node_modules/open/index.js
25791
- import process9 from "node:process";
25792
- import path3 from "node:path";
26405
+ import process11 from "node:process";
26406
+ import path4 from "node:path";
25793
26407
  import { fileURLToPath } from "node:url";
25794
- import childProcess3 from "node:child_process";
26408
+ import childProcess4 from "node:child_process";
25795
26409
  import fs6, { constants as fsConstants2 } from "node:fs/promises";
25796
26410
 
25797
26411
  // node_modules/wsl-utils/index.js
26412
+ import path3 from "node:path";
25798
26413
  import { promisify as promisify2 } from "node:util";
25799
26414
  import childProcess2 from "node:child_process";
25800
26415
  import fs5, { constants as fsConstants } from "node:fs/promises";
@@ -25873,7 +26488,7 @@ var isWsl = () => {
25873
26488
  };
25874
26489
  var is_wsl_default = process3.env.__IS_WSL_TEST__ ? isWsl : isWsl();
25875
26490
 
25876
- // node_modules/powershell-utils/index.js
26491
+ // node_modules/wsl-utils/node_modules/powershell-utils/index.js
25877
26492
  import process4 from "node:process";
25878
26493
  import { Buffer as Buffer2 } from "node:buffer";
25879
26494
  import { promisify } from "node:util";
@@ -25972,20 +26587,73 @@ var canAccessPowerShell = async () => {
25972
26587
  var wslDefaultBrowser = async () => {
25973
26588
  const psPath = await powerShellPath2();
25974
26589
  const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
25975
- const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
26590
+ const { stdout } = await executePowerShell(command, {
26591
+ powerShellPath: psPath,
26592
+ cwd: path3.dirname(psPath)
26593
+ });
25976
26594
  return stdout.trim();
25977
26595
  };
25978
- var convertWslPathToWindows = async (path8) => {
25979
- if (/^[a-z]+:\/\//i.test(path8)) {
25980
- return path8;
25981
- }
25982
- try {
25983
- const { stdout } = await execFile2("wslpath", ["-aw", path8], { encoding: "utf8" });
25984
- return stdout.trim();
25985
- } catch {
25986
- return path8;
26596
+ var isUrl = (path9) => /^[a-z]+:\/\//i.test(path9);
26597
+ var convertWslPathToWindows = async (paths) => {
26598
+ const isBatch = Array.isArray(paths);
26599
+ const pathArray = isBatch ? paths : [paths];
26600
+ const indicesToConvert = [];
26601
+ const pathsToConvert = [];
26602
+ for (const [index, path9] of pathArray.entries()) {
26603
+ if (!isUrl(path9)) {
26604
+ indicesToConvert.push(index);
26605
+ pathsToConvert.push(path9);
26606
+ }
26607
+ }
26608
+ const results = [...pathArray];
26609
+ if (pathsToConvert.length > 0) {
26610
+ try {
26611
+ const { stdout } = await execFile2("wslpath", ["-aw", ...pathsToConvert], { encoding: "utf8" });
26612
+ const convertedPaths = stdout.split(/\r?\n/).filter(Boolean);
26613
+ for (const [index, originalIndex] of indicesToConvert.entries()) {
26614
+ results[originalIndex] = convertedPaths[index] ?? pathArray[originalIndex];
26615
+ }
26616
+ } catch {
26617
+ }
25987
26618
  }
26619
+ return isBatch ? results : results[0];
26620
+ };
26621
+
26622
+ // node_modules/powershell-utils/index.js
26623
+ import process5 from "node:process";
26624
+ import { Buffer as Buffer3 } from "node:buffer";
26625
+ import { promisify as promisify3 } from "node:util";
26626
+ import childProcess3 from "node:child_process";
26627
+ var execFile3 = promisify3(childProcess3.execFile);
26628
+ var powerShellPath3 = () => `${process5.env.SYSTEMROOT || process5.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
26629
+ var argumentsPrefix = [
26630
+ "-NoProfile",
26631
+ "-NonInteractive",
26632
+ "-ExecutionPolicy",
26633
+ "Bypass",
26634
+ "-EncodedCommand"
26635
+ ];
26636
+ var encodeCommand = (command) => Buffer3.from(command, "utf16le").toString("base64");
26637
+ var escapeArgument = (value) => `'${String(value).replaceAll("'", "''")}'`;
26638
+ var createArguments = (command) => [...argumentsPrefix, encodeCommand(command)];
26639
+ var executePowerShell2 = async (command, options = {}) => {
26640
+ const {
26641
+ powerShellPath: psPath,
26642
+ ...execFileOptions
26643
+ } = options;
26644
+ return execFile3(
26645
+ psPath ?? powerShellPath3(),
26646
+ createArguments(command),
26647
+ {
26648
+ encoding: "utf8",
26649
+ ...execFileOptions
26650
+ }
26651
+ );
25988
26652
  };
26653
+ executePowerShell2.argumentsPrefix = argumentsPrefix;
26654
+ executePowerShell2.encodeCommand = encodeCommand;
26655
+ executePowerShell2.escapeArgument = escapeArgument;
26656
+ executePowerShell2.createArguments = createArguments;
25989
26657
 
25990
26658
  // node_modules/define-lazy-prop/index.js
25991
26659
  function defineLazyProperty(object2, propertyName, valueGetter) {
@@ -26006,17 +26674,17 @@ function defineLazyProperty(object2, propertyName, valueGetter) {
26006
26674
  }
26007
26675
 
26008
26676
  // node_modules/default-browser/index.js
26009
- import { promisify as promisify6 } from "node:util";
26010
- import process7 from "node:process";
26011
- import { execFile as execFile6 } from "node:child_process";
26677
+ import { promisify as promisify7 } from "node:util";
26678
+ import process9 from "node:process";
26679
+ import { execFile as execFile7 } from "node:child_process";
26012
26680
 
26013
26681
  // node_modules/default-browser-id/index.js
26014
- import { promisify as promisify3 } from "node:util";
26015
- import process5 from "node:process";
26016
- import { execFile as execFile3 } from "node:child_process";
26017
- var execFileAsync = promisify3(execFile3);
26682
+ import { promisify as promisify4 } from "node:util";
26683
+ import process6 from "node:process";
26684
+ import { execFile as execFile4 } from "node:child_process";
26685
+ var execFileAsync = promisify4(execFile4);
26018
26686
  async function defaultBrowserId() {
26019
- if (process5.platform !== "darwin") {
26687
+ if (process6.platform !== "darwin") {
26020
26688
  throw new Error("macOS only");
26021
26689
  }
26022
26690
  const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
@@ -26029,12 +26697,12 @@ async function defaultBrowserId() {
26029
26697
  }
26030
26698
 
26031
26699
  // node_modules/run-applescript/index.js
26032
- import process6 from "node:process";
26033
- import { promisify as promisify4 } from "node:util";
26034
- import { execFile as execFile4, execFileSync } from "node:child_process";
26035
- var execFileAsync2 = promisify4(execFile4);
26700
+ import process7 from "node:process";
26701
+ import { promisify as promisify5 } from "node:util";
26702
+ import { execFile as execFile5, execFileSync } from "node:child_process";
26703
+ var execFileAsync2 = promisify5(execFile5);
26036
26704
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
26037
- if (process6.platform !== "darwin") {
26705
+ if (process7.platform !== "darwin") {
26038
26706
  throw new Error("macOS only");
26039
26707
  }
26040
26708
  const outputArguments = humanReadableOutput ? [] : ["-ss"];
@@ -26053,9 +26721,10 @@ tell application "System Events" to get value of property list item "CFBundleNam
26053
26721
  }
26054
26722
 
26055
26723
  // node_modules/default-browser/windows.js
26056
- import { promisify as promisify5 } from "node:util";
26057
- import { execFile as execFile5 } from "node:child_process";
26058
- var execFileAsync3 = promisify5(execFile5);
26724
+ import process8 from "node:process";
26725
+ import { promisify as promisify6 } from "node:util";
26726
+ import { execFile as execFile6 } from "node:child_process";
26727
+ var execFileAsync3 = promisify6(execFile6);
26059
26728
  var windowsBrowserProgIds = {
26060
26729
  MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
26061
26730
  // The missing `L` is correct.
@@ -26079,7 +26748,8 @@ var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
26079
26748
  var UnknownBrowserError = class extends Error {
26080
26749
  };
26081
26750
  async function defaultBrowser(_execFileAsync = execFileAsync3) {
26082
- const { stdout } = await _execFileAsync("reg", [
26751
+ const regPath = `${process8.env.SYSTEMROOT ?? process8.env.windir ?? "C:\\Windows"}\\System32\\reg.exe`;
26752
+ const { stdout } = await _execFileAsync(regPath, [
26083
26753
  "QUERY",
26084
26754
  " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
26085
26755
  "/v",
@@ -26098,36 +26768,36 @@ async function defaultBrowser(_execFileAsync = execFileAsync3) {
26098
26768
  }
26099
26769
 
26100
26770
  // node_modules/default-browser/index.js
26101
- var execFileAsync4 = promisify6(execFile6);
26771
+ var execFileAsync4 = promisify7(execFile7);
26102
26772
  var titleize = (string4) => string4.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
26103
26773
  async function defaultBrowser2() {
26104
- if (process7.platform === "darwin") {
26774
+ if (process9.platform === "darwin") {
26105
26775
  const id = await defaultBrowserId();
26106
26776
  const name = await bundleName(id);
26107
26777
  return { name, id };
26108
26778
  }
26109
- if (process7.platform === "linux") {
26779
+ if (process9.platform === "linux") {
26110
26780
  const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
26111
26781
  const id = stdout.trim();
26112
26782
  const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
26113
26783
  return { name, id };
26114
26784
  }
26115
- if (process7.platform === "win32") {
26785
+ if (process9.platform === "win32") {
26116
26786
  return defaultBrowser();
26117
26787
  }
26118
26788
  throw new Error("Only macOS, Linux, and Windows are supported");
26119
26789
  }
26120
26790
 
26121
26791
  // node_modules/is-in-ssh/index.js
26122
- import process8 from "node:process";
26123
- var isInSsh = Boolean(process8.env.SSH_CONNECTION || process8.env.SSH_CLIENT || process8.env.SSH_TTY);
26792
+ import process10 from "node:process";
26793
+ var isInSsh = Boolean(process10.env.SSH_CONNECTION || process10.env.SSH_CLIENT || process10.env.SSH_TTY);
26124
26794
  var is_in_ssh_default = isInSsh;
26125
26795
 
26126
26796
  // node_modules/open/index.js
26127
26797
  var fallbackAttemptSymbol = /* @__PURE__ */ Symbol("fallbackAttempt");
26128
- var __dirname = import.meta.url ? path3.dirname(fileURLToPath(import.meta.url)) : "";
26129
- var localXdgOpenPath = path3.join(__dirname, "xdg-open");
26130
- var { platform, arch } = process9;
26798
+ var __dirname = import.meta.url ? path4.dirname(fileURLToPath(import.meta.url)) : "";
26799
+ var localXdgOpenPath = path4.join(__dirname, "xdg-open");
26800
+ var { platform, arch } = process11;
26131
26801
  var tryEachApp = async (apps2, opener) => {
26132
26802
  if (apps2.length === 0) {
26133
26803
  return;
@@ -26240,7 +26910,7 @@ var baseOpen = async (options) => {
26240
26910
  }
26241
26911
  } else if (platform === "win32" || shouldUseWindowsInWsl) {
26242
26912
  command = await powerShellPath2();
26243
- cliArguments.push(...executePowerShell.argumentsPrefix);
26913
+ cliArguments.push(...executePowerShell2.argumentsPrefix);
26244
26914
  if (!is_wsl_default) {
26245
26915
  childProcessOptions.windowsVerbatimArguments = true;
26246
26916
  }
@@ -26252,21 +26922,24 @@ var baseOpen = async (options) => {
26252
26922
  encodedArguments.push("-Wait");
26253
26923
  }
26254
26924
  if (app) {
26255
- encodedArguments.push(executePowerShell.escapeArgument(app));
26925
+ encodedArguments.push(executePowerShell2.escapeArgument(app));
26256
26926
  if (options.target) {
26257
26927
  appArguments.push(options.target);
26258
26928
  }
26259
26929
  } else if (options.target) {
26260
- encodedArguments.push(executePowerShell.escapeArgument(options.target));
26930
+ encodedArguments.push(executePowerShell2.escapeArgument(options.target));
26261
26931
  }
26262
26932
  if (appArguments.length > 0) {
26263
- appArguments = appArguments.map((argument) => executePowerShell.escapeArgument(argument));
26933
+ appArguments = appArguments.map((argument) => executePowerShell2.escapeArgument(argument));
26264
26934
  encodedArguments.push("-ArgumentList", appArguments.join(","));
26265
26935
  }
26266
- options.target = executePowerShell.encodeCommand(encodedArguments.join(" "));
26936
+ options.target = executePowerShell2.encodeCommand(encodedArguments.join(" "));
26267
26937
  if (!options.wait) {
26268
26938
  childProcessOptions.stdio = "ignore";
26269
26939
  }
26940
+ if (is_wsl_default) {
26941
+ childProcessOptions.cwd = path4.dirname(command);
26942
+ }
26270
26943
  } else {
26271
26944
  if (app) {
26272
26945
  command = app;
@@ -26278,7 +26951,7 @@ var baseOpen = async (options) => {
26278
26951
  exeLocalXdgOpen = true;
26279
26952
  } catch {
26280
26953
  }
26281
- const useSystemXdgOpen = process9.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
26954
+ const useSystemXdgOpen = process11.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
26282
26955
  command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
26283
26956
  }
26284
26957
  if (appArguments.length > 0) {
@@ -26295,7 +26968,7 @@ var baseOpen = async (options) => {
26295
26968
  if (options.target) {
26296
26969
  cliArguments.push(options.target);
26297
26970
  }
26298
- const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
26971
+ const subprocess = childProcess4.spawn(command, cliArguments, childProcessOptions);
26299
26972
  if (options.wait) {
26300
26973
  return new Promise((resolve, reject) => {
26301
26974
  subprocess.once("error", reject);
@@ -26442,33 +27115,44 @@ var AgentMode = class _AgentMode {
26442
27115
  id;
26443
27116
  name;
26444
27117
  description;
27118
+ kind;
26445
27119
  approvalPolicy;
27120
+ approvalsReviewer;
26446
27121
  sandboxPolicy;
26447
27122
  sandboxMode;
26448
- constructor(id, name, description, approval, sandbox, sandboxMode) {
27123
+ constructor(id, name, description, kind, approvalPolicy, approvalsReviewer, sandboxPolicy, sandboxMode) {
26449
27124
  this.id = id;
26450
27125
  this.name = name;
26451
27126
  this.description = description;
26452
- this.approvalPolicy = approval;
26453
- this.sandboxPolicy = sandbox;
27127
+ this.kind = kind;
27128
+ this.approvalPolicy = approvalPolicy;
27129
+ this.approvalsReviewer = approvalsReviewer;
27130
+ this.sandboxPolicy = sandboxPolicy;
26454
27131
  this.sandboxMode = sandboxMode;
26455
27132
  }
26456
27133
  static ReadOnly = new _AgentMode(
26457
27134
  "read-only",
26458
- "Read-only",
26459
- "Requires approval to edit files and run commands.",
27135
+ "Ask for approval",
27136
+ "Always ask to edit external files and use the internet",
27137
+ "standard",
26460
27138
  "on-request",
27139
+ "user",
26461
27140
  {
26462
- "type": "readOnly",
26463
- "networkAccess": false
27141
+ type: "workspaceWrite",
27142
+ writableRoots: [],
27143
+ networkAccess: false,
27144
+ excludeTmpdirEnvVar: false,
27145
+ excludeSlashTmp: false
26464
27146
  },
26465
- "read-only"
27147
+ "workspace-write"
26466
27148
  );
26467
27149
  static Agent = new _AgentMode(
26468
27150
  "agent",
26469
- "Agent",
26470
- "Read and edit files, and run commands.",
27151
+ "Approve for me",
27152
+ "Only ask for actions detected as potentially unsafe",
27153
+ "auto_review",
26471
27154
  "on-request",
27155
+ "auto_review",
26472
27156
  {
26473
27157
  type: "workspaceWrite",
26474
27158
  writableRoots: [],
@@ -26480,9 +27164,11 @@ var AgentMode = class _AgentMode {
26480
27164
  );
26481
27165
  static AgentFullAccess = new _AgentMode(
26482
27166
  "agent-full-access",
26483
- "Agent (full access)",
26484
- "Codex can edit files outside this workspace and run commands with network access. Exercise caution when using.",
27167
+ "Full access",
27168
+ "Unrestricted access to the internet and any file on your computer",
27169
+ "full_access",
26485
27170
  "never",
27171
+ "user",
26486
27172
  { "type": "dangerFullAccess" },
26487
27173
  "danger-full-access"
26488
27174
  );
@@ -26491,7 +27177,8 @@ var AgentMode = class _AgentMode {
26491
27177
  return {
26492
27178
  id: this.id,
26493
27179
  name: this.name,
26494
- description: this.description
27180
+ description: this.description,
27181
+ _meta: { kind: this.kind }
26495
27182
  };
26496
27183
  }
26497
27184
  toSessionModeState() {
@@ -26511,7 +27198,8 @@ var AgentMode = class _AgentMode {
26511
27198
  options: _AgentMode.all().map((mode) => ({
26512
27199
  value: mode.id,
26513
27200
  name: mode.name,
26514
- description: mode.description
27201
+ description: mode.description,
27202
+ _meta: { kind: mode.kind }
26515
27203
  }))
26516
27204
  };
26517
27205
  }
@@ -26533,7 +27221,7 @@ var AgentMode = class _AgentMode {
26533
27221
  };
26534
27222
 
26535
27223
  // src/CodexAcpClient.ts
26536
- import path6 from "node:path";
27224
+ import path7 from "node:path";
26537
27225
 
26538
27226
  // src/McpServerName.ts
26539
27227
  var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
@@ -26547,7 +27235,7 @@ var package_default = {
26547
27235
  publishConfig: {
26548
27236
  access: "public"
26549
27237
  },
26550
- version: "1.6.2-agentconnect.1",
27238
+ version: "1.7.0-agentconnect.2",
26551
27239
  description: "",
26552
27240
  main: "dist/index.js",
26553
27241
  bin: {
@@ -26600,17 +27288,17 @@ var package_default = {
26600
27288
  type: "module",
26601
27289
  devDependencies: {
26602
27290
  "@types/node": "^26.1.0",
26603
- esbuild: "^0.28.1",
27291
+ esbuild: "^0.28.2",
26604
27292
  "mcp-hello-world": "^1.1.2",
26605
- tsx: "^4.23.1",
27293
+ tsx: "^4.23.12",
26606
27294
  typescript: "^7.0.2",
26607
- vitest: "^4.1.10"
27295
+ vitest: "^4.1.11"
26608
27296
  },
26609
27297
  dependencies: {
26610
- "@agentclientprotocol/sdk": "^1.3.0",
27298
+ "@agentclientprotocol/sdk": "^1.4.0",
26611
27299
  "@openai/codex": "^0.148.0",
26612
27300
  diff: "^9.0.0",
26613
- open: "^11.0.0",
27301
+ open: "^11.0.1",
26614
27302
  "vscode-jsonrpc": "^9.0.1",
26615
27303
  zod: "^4.0.0"
26616
27304
  }
@@ -26652,17 +27340,17 @@ function createCodexCollaborationMode(mode, currentModelId) {
26652
27340
  }
26653
27341
 
26654
27342
  // src/PathUtils.ts
26655
- import path4 from "node:path";
27343
+ import path5 from "node:path";
26656
27344
  function isAbsolutePathLike(value) {
26657
27345
  const trimmed = value.trim();
26658
- return path4.isAbsolute(trimmed) || isWindowsAbsolutePath(trimmed);
27346
+ return path5.isAbsolute(trimmed) || isWindowsAbsolutePath(trimmed);
26659
27347
  }
26660
27348
  function arePathsEqual(left, right) {
26661
27349
  return normalizePathForComparison(left) === normalizePathForComparison(right);
26662
27350
  }
26663
27351
  function arePathBasenamesEqual(left, right) {
26664
- const leftBase = path4.posix.basename(normalizePathForComparison(left));
26665
- const rightBase = path4.posix.basename(normalizePathForComparison(right));
27352
+ const leftBase = path5.posix.basename(normalizePathForComparison(left));
27353
+ const rightBase = path5.posix.basename(normalizePathForComparison(right));
26666
27354
  if (shouldComparePathCaseInsensitive(left) || shouldComparePathCaseInsensitive(right)) {
26667
27355
  return leftBase.toLowerCase() === rightBase.toLowerCase();
26668
27356
  }
@@ -26674,11 +27362,11 @@ function normalizePathForComparison(value) {
26674
27362
  return "";
26675
27363
  }
26676
27364
  if (isWindowsAbsolutePath(trimmed)) {
26677
- const normalized2 = path4.win32.normalize(trimmed).replace(/\\/g, "/");
27365
+ const normalized2 = path5.win32.normalize(trimmed).replace(/\\/g, "/");
26678
27366
  return trimTrailingPathSeparators(normalized2).toLowerCase();
26679
27367
  }
26680
- const pathForComparison = path4.isAbsolute(trimmed) ? trimmed : trimmed.replace(/\\/g, "/");
26681
- const normalized = path4.posix.normalize(pathForComparison);
27368
+ const pathForComparison = path5.isAbsolute(trimmed) ? trimmed : trimmed.replace(/\\/g, "/");
27369
+ const normalized = path5.posix.normalize(pathForComparison);
26682
27370
  return trimTrailingPathSeparators(normalized);
26683
27371
  }
26684
27372
  function isWindowsAbsolutePath(value) {
@@ -26701,7 +27389,7 @@ function isRootPath(value) {
26701
27389
 
26702
27390
  // src/AgentFileChangeReport.ts
26703
27391
  import fs7 from "node:fs";
26704
- import path5 from "node:path";
27392
+ import path6 from "node:path";
26705
27393
  var AGENT_FILE_CHANGE_REPORT_VERSION = 1;
26706
27394
  var AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS = 3e4;
26707
27395
  var AGENT_FILE_CHANGE_REPORT_MAX_PATHS = 1024;
@@ -26711,7 +27399,7 @@ var AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH = 2e3;
26711
27399
  var AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA = {
26712
27400
  type: "object",
26713
27401
  additionalProperties: false,
26714
- required: ["paths", "complete"],
27402
+ required: ["paths", "complete", "uncertainty"],
26715
27403
  properties: {
26716
27404
  paths: {
26717
27405
  type: "array",
@@ -26719,8 +27407,12 @@ var AGENT_FILE_CHANGE_REPORT_OUTPUT_SCHEMA = {
26719
27407
  },
26720
27408
  complete: { type: "boolean" },
26721
27409
  uncertainty: {
26722
- type: "string",
26723
- maxLength: AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH
27410
+ anyOf: [{
27411
+ type: "string",
27412
+ maxLength: AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH
27413
+ }, {
27414
+ type: "null"
27415
+ }]
26724
27416
  }
26725
27417
  }
26726
27418
  };
@@ -26780,7 +27472,10 @@ function parseModelFileChangeReport(turn) {
26780
27472
  case "interrupted":
26781
27473
  throw new AgentFileChangeReportError("cancelled", "The audit turn was interrupted");
26782
27474
  case "failed":
26783
- throw new AgentFileChangeReportError("providerError", "The audit turn failed");
27475
+ throw new AgentFileChangeReportError(
27476
+ "providerError",
27477
+ `The audit turn failed${turn.error?.message ? `: ${turn.error.message}` : ""}`
27478
+ );
26784
27479
  case "inProgress":
26785
27480
  throw new AgentFileChangeReportError("notReported", "The audit turn did not complete");
26786
27481
  case "completed":
@@ -26810,10 +27505,10 @@ function parseModelFileChangeReport(turn) {
26810
27505
  const paths = report["paths"];
26811
27506
  const complete = report["complete"];
26812
27507
  const uncertainty = report["uncertainty"];
26813
- if (!Array.isArray(paths) || !paths.every((item) => typeof item === "string") || typeof complete !== "boolean" || uncertainty !== void 0 && typeof uncertainty !== "string") {
27508
+ if (!Array.isArray(paths) || !paths.every((item) => typeof item === "string") || typeof complete !== "boolean" || uncertainty !== void 0 && uncertainty !== null && typeof uncertainty !== "string") {
26814
27509
  throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned invalid fields");
26815
27510
  }
26816
- const normalizedUncertainty = uncertainty?.trim();
27511
+ const normalizedUncertainty = typeof uncertainty === "string" ? uncertainty.trim() : void 0;
26817
27512
  if (normalizedUncertainty !== void 0 && normalizedUncertainty.length > AGENT_FILE_CHANGE_REPORT_MAX_UNCERTAINTY_LENGTH) {
26818
27513
  throw new AgentFileChangeReportError("invalidOutput", "The audit turn returned oversized uncertainty");
26819
27514
  }
@@ -26892,13 +27587,13 @@ function normalizeWorkspaceRoot(value) {
26892
27587
  }
26893
27588
  if (isWindowsAbsolutePath2(trimmed)) {
26894
27589
  return canonicalizeWorkspaceRoot({
26895
- value: path5.win32.normalize(trimmed.replace(/\//g, "\\")),
27590
+ value: path6.win32.normalize(trimmed.replace(/\//g, "\\")),
26896
27591
  flavor: "windows"
26897
27592
  });
26898
27593
  }
26899
- if (path5.posix.isAbsolute(trimmed)) {
27594
+ if (path6.posix.isAbsolute(trimmed)) {
26900
27595
  return canonicalizeWorkspaceRoot({
26901
- value: path5.posix.normalize(trimmed.replace(/\\/g, "/")),
27596
+ value: path6.posix.normalize(trimmed.replace(/\\/g, "/")),
26902
27597
  flavor: "posix"
26903
27598
  });
26904
27599
  }
@@ -26911,17 +27606,17 @@ function normalizeReportedPath(value, cwd, roots) {
26911
27606
  }
26912
27607
  let candidate;
26913
27608
  if (isWindowsAbsolutePath2(trimmed)) {
26914
- candidate = { value: path5.win32.normalize(trimmed.replace(/\//g, "\\")), flavor: "windows" };
26915
- } else if (path5.posix.isAbsolute(trimmed)) {
26916
- candidate = { value: path5.posix.normalize(trimmed.replace(/\\/g, "/")), flavor: "posix" };
27609
+ candidate = { value: path6.win32.normalize(trimmed.replace(/\//g, "\\")), flavor: "windows" };
27610
+ } else if (path6.posix.isAbsolute(trimmed)) {
27611
+ candidate = { value: path6.posix.normalize(trimmed.replace(/\\/g, "/")), flavor: "posix" };
26917
27612
  } else if (cwd.flavor === "windows") {
26918
27613
  candidate = {
26919
- value: path5.win32.resolve(cwd.value, trimmed.replace(/\//g, "\\")),
27614
+ value: path6.win32.resolve(cwd.value, trimmed.replace(/\//g, "\\")),
26920
27615
  flavor: "windows"
26921
27616
  };
26922
27617
  } else {
26923
27618
  candidate = {
26924
- value: path5.posix.resolve(cwd.value, trimmed.replace(/\\/g, "/")),
27619
+ value: path6.posix.resolve(cwd.value, trimmed.replace(/\\/g, "/")),
26925
27620
  flavor: "posix"
26926
27621
  };
26927
27622
  }
@@ -26938,23 +27633,23 @@ function canonicalizeReportedPath(candidate) {
26938
27633
  if (!isNativePathFlavor(candidate.flavor)) {
26939
27634
  return candidate;
26940
27635
  }
26941
- const parent = canonicalizeFromExistingAncestor(path5.dirname(candidate.value));
26942
- return { ...candidate, value: path5.resolve(parent, path5.basename(candidate.value)) };
27636
+ const parent = canonicalizeFromExistingAncestor(path6.dirname(candidate.value));
27637
+ return { ...candidate, value: path6.resolve(parent, path6.basename(candidate.value)) };
26943
27638
  }
26944
27639
  function canonicalizeFromExistingAncestor(value) {
26945
- const original = path5.resolve(value);
27640
+ const original = path6.resolve(value);
26946
27641
  let current = original;
26947
27642
  const missingSegments = [];
26948
27643
  while (true) {
26949
27644
  try {
26950
27645
  const canonical = fs7.realpathSync.native(current);
26951
- return path5.resolve(canonical, ...missingSegments.reverse());
27646
+ return path6.resolve(canonical, ...missingSegments.reverse());
26952
27647
  } catch (error51) {
26953
27648
  if (!isMissingPathError(error51)) return original;
26954
27649
  }
26955
- const parent = path5.dirname(current);
27650
+ const parent = path6.dirname(current);
26956
27651
  if (parent === current) return original;
26957
- missingSegments.push(path5.basename(current));
27652
+ missingSegments.push(path6.basename(current));
26958
27653
  current = parent;
26959
27654
  }
26960
27655
  }
@@ -26969,7 +27664,7 @@ function pathIsStrictlyInside(root, candidate) {
26969
27664
  if (root.flavor !== candidate.flavor) {
26970
27665
  return false;
26971
27666
  }
26972
- const pathImplementation = root.flavor === "windows" ? path5.win32 : path5.posix;
27667
+ const pathImplementation = root.flavor === "windows" ? path6.win32 : path6.posix;
26973
27668
  const relative = pathImplementation.relative(root.value, candidate.value);
26974
27669
  return relative.length > 0 && !pathImplementation.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${pathImplementation.sep}`);
26975
27670
  }
@@ -26988,40 +27683,119 @@ function hasOnlyKeys(value, allowed) {
26988
27683
  return Object.keys(value).every((key) => allowedKeys.has(key));
26989
27684
  }
26990
27685
 
26991
- // src/ApprovalsReviewerConfig.ts
26992
- var APPROVALS_REVIEWER_CONFIG_ID = "approvals_reviewer";
26993
- var USER_APPROVALS_REVIEWER = "user";
26994
- var AUTO_APPROVALS_REVIEWER = "auto_review";
26995
- function createApprovalsReviewerConfigOption(currentValue) {
26996
- return {
26997
- id: APPROVALS_REVIEWER_CONFIG_ID,
26998
- name: "Approval reviewer",
26999
- description: "Who reviews eligible approval requests",
27000
- category: "_approvals_reviewer",
27001
- type: "select",
27002
- currentValue,
27003
- options: [
27004
- {
27005
- value: USER_APPROVALS_REVIEWER,
27006
- name: "User",
27007
- description: "Ask the user to approve or deny requests"
27686
+ // src/subagents/CodexSubagentSubscriptions.ts
27687
+ var CodexSubagentSubscriptions = class {
27688
+ constructor(client) {
27689
+ this.client = client;
27690
+ }
27691
+ client;
27692
+ sessions = /* @__PURE__ */ new Map();
27693
+ subscribe(subscription) {
27694
+ const existing = this.sessions.get(subscription.rootSessionId);
27695
+ if (existing) {
27696
+ existing.current = subscription;
27697
+ return;
27698
+ }
27699
+ const session = { current: subscription, children: /* @__PURE__ */ new Set() };
27700
+ this.sessions.set(subscription.rootSessionId, session);
27701
+ this.client.onServerNotification(subscription.rootSessionId, (event) => {
27702
+ this.discover(session, event);
27703
+ session.current.dispatch(event);
27704
+ });
27705
+ this.registerInteractiveHandlers(session, subscription.rootSessionId);
27706
+ }
27707
+ clear(rootSessionId) {
27708
+ for (const childSessionId of this.sessions.get(rootSessionId)?.children ?? []) {
27709
+ this.client.clearThreadHandlers(childSessionId);
27710
+ }
27711
+ this.sessions.delete(rootSessionId);
27712
+ }
27713
+ discover(session, event) {
27714
+ if (event.method !== "item/started" && event.method !== "item/completed") {
27715
+ return;
27716
+ }
27717
+ const item = event.params.item;
27718
+ const childSessionIds = item.type === "collabAgentToolCall" && item.tool === "spawnAgent" ? item.receiverThreadIds : item.type === "subAgentActivity" && item.kind !== "interrupted" && !isRootAgentPath(item.agentPath) ? [item.agentThreadId] : [];
27719
+ for (const childSessionId of childSessionIds) {
27720
+ if (childSessionId.trim() === "") continue;
27721
+ if (childSessionId === session.current.rootSessionId || childSessionId === event.params.threadId || session.children.has(childSessionId)) {
27722
+ continue;
27723
+ }
27724
+ session.children.add(childSessionId);
27725
+ this.client.onServerNotification(childSessionId, (childEvent) => {
27726
+ const eventThreadId = childEvent.params.threadId;
27727
+ if (eventThreadId !== childSessionId) return;
27728
+ this.discover(session, childEvent);
27729
+ if (session.current.supportsSubagents) session.current.dispatch(childEvent);
27730
+ else session.current.enqueueInteraction(this.rootAttributed(childEvent, session.current.rootSessionId));
27731
+ });
27732
+ this.registerInteractiveHandlers(session, childSessionId);
27733
+ }
27734
+ }
27735
+ registerInteractiveHandlers(session, targetSessionId) {
27736
+ this.client.onApprovalRequest(targetSessionId, {
27737
+ handleCommandExecution: async (params) => {
27738
+ const current = session.current;
27739
+ await current.waitForRootNotifications();
27740
+ const sessionId = await this.interactionSessionId(current, targetSessionId);
27741
+ if (sessionId === null) return { decision: "cancel" };
27742
+ return await current.approvalHandler.handleCommandExecution(
27743
+ { ...params, threadId: sessionId }
27744
+ );
27008
27745
  },
27009
- {
27010
- value: AUTO_APPROVALS_REVIEWER,
27011
- name: "Auto-review",
27012
- description: "Use a reviewer agent to assess eligible requests"
27746
+ handleFileChange: async (params) => {
27747
+ const current = session.current;
27748
+ await current.waitForRootNotifications();
27749
+ const sessionId = await this.interactionSessionId(current, targetSessionId);
27750
+ if (sessionId === null) return { decision: "cancel" };
27751
+ return await current.approvalHandler.handleFileChange(
27752
+ { ...params, threadId: sessionId }
27753
+ );
27754
+ },
27755
+ handlePermissionsRequest: async (params) => {
27756
+ const current = session.current;
27757
+ await current.waitForRootNotifications();
27758
+ const sessionId = await this.interactionSessionId(current, targetSessionId);
27759
+ if (sessionId === null) return { permissions: {}, scope: "turn", strictAutoReview: false };
27760
+ return await current.approvalHandler.handlePermissionsRequest(
27761
+ { ...params, threadId: sessionId }
27762
+ );
27013
27763
  }
27014
- ]
27015
- };
27016
- }
27017
- function parseApprovalsReviewer(value) {
27018
- if (value === USER_APPROVALS_REVIEWER) return USER_APPROVALS_REVIEWER;
27019
- if (value === AUTO_APPROVALS_REVIEWER) return AUTO_APPROVALS_REVIEWER;
27020
- return null;
27021
- }
27022
- function normalizeApprovalsReviewer(value) {
27023
- return value === AUTO_APPROVALS_REVIEWER || value === "guardian_subagent" ? AUTO_APPROVALS_REVIEWER : USER_APPROVALS_REVIEWER;
27024
- }
27764
+ });
27765
+ this.client.onElicitationRequest(targetSessionId, {
27766
+ handleElicitation: async (params) => {
27767
+ const current = session.current;
27768
+ await current.waitForRootNotifications();
27769
+ const sessionId = await this.interactionSessionId(current, targetSessionId);
27770
+ if (sessionId === null) return { action: "cancel", content: null, _meta: null };
27771
+ return await current.elicitationHandler.handleElicitation(
27772
+ { ...params, threadId: sessionId }
27773
+ );
27774
+ },
27775
+ handleUserInput: async (params) => {
27776
+ const current = session.current;
27777
+ await current.waitForRootNotifications();
27778
+ const sessionId = await this.interactionSessionId(current, targetSessionId);
27779
+ if (sessionId === null) return { answers: {} };
27780
+ return await current.elicitationHandler.handleUserInput(
27781
+ { ...params, threadId: sessionId }
27782
+ );
27783
+ }
27784
+ });
27785
+ }
27786
+ async interactionSessionId(subscription, targetSessionId) {
27787
+ if (targetSessionId === subscription.rootSessionId) return targetSessionId;
27788
+ if (!subscription.supportsSubagents) return subscription.rootSessionId;
27789
+ return await subscription.waitForChildSession(targetSessionId);
27790
+ }
27791
+ rootAttributed(event, rootSessionId) {
27792
+ if (typeof event.params.threadId !== "string") return event;
27793
+ return {
27794
+ ...event,
27795
+ params: { ...event.params, threadId: rootSessionId }
27796
+ };
27797
+ }
27798
+ };
27025
27799
 
27026
27800
  // src/PermissionProfileConfig.ts
27027
27801
  var PERMISSION_PROFILE_CONFIG_ENV = "CODEX_ACP_PERMISSION_PROFILE_CONFIG";
@@ -27066,6 +27840,7 @@ var CodexAcpClient = class {
27066
27840
  pendingLoginCompleted = null;
27067
27841
  pendingAccountUpdated = null;
27068
27842
  sessionNotificationQueues = /* @__PURE__ */ new Map();
27843
+ subagents;
27069
27844
  skillExtraRoots = [];
27070
27845
  configPath = null;
27071
27846
  constructor(codexClient, codexConfig, modelProvider, permissionProfileConfig) {
@@ -27074,6 +27849,7 @@ var CodexAcpClient = class {
27074
27849
  this.modelProvider = modelProvider ?? null;
27075
27850
  this.permissionProfileConfig = permissionProfileConfig;
27076
27851
  this.gatewayConfig = null;
27852
+ this.subagents = new CodexSubagentSubscriptions(codexClient);
27077
27853
  }
27078
27854
  defaultClientInfo = {
27079
27855
  name: `${package_default.name}`,
@@ -27149,16 +27925,31 @@ var CodexAcpClient = class {
27149
27925
  if (loginResponse.type !== "chatgptDeviceCode") {
27150
27926
  return false;
27151
27927
  }
27152
- const elicitationResponse = await urlElicitationRequester.elicitUrl({
27928
+ const elicitationResponsePromise = Promise.resolve(urlElicitationRequester.elicitUrl({
27153
27929
  url: loginResponse.verificationUrl,
27154
27930
  message: `Sign in to ChatGPT and enter this code: ${loginResponse.userCode}`,
27155
27931
  elicitationId: loginResponse.loginId
27156
- });
27157
- if (!CreateElicitationResponse.isAccept(elicitationResponse)) {
27932
+ }));
27933
+ const first = await Promise.race([
27934
+ loginCompletedPromise.then((result2) => ({
27935
+ type: "loginCompleted",
27936
+ result: result2
27937
+ })),
27938
+ elicitationResponsePromise.then((response) => ({
27939
+ type: "elicitationResponse",
27940
+ response
27941
+ }))
27942
+ ]);
27943
+ if (first.type === "loginCompleted") {
27944
+ await urlElicitationRequester.completeElicitation();
27945
+ return first.result.success;
27946
+ }
27947
+ if (!CreateElicitationResponse.isAccept(first.response)) {
27158
27948
  await this.codexClient.accountLoginCancel({ loginId: loginResponse.loginId });
27159
27949
  return false;
27160
27950
  }
27161
27951
  const result = await loginCompletedPromise;
27952
+ await urlElicitationRequester.completeElicitation();
27162
27953
  return result.success;
27163
27954
  }
27164
27955
  authenticateWithGateway(authRequest) {
@@ -27374,7 +28165,6 @@ var CodexAcpClient = class {
27374
28165
  currentModelId,
27375
28166
  models: codexModels,
27376
28167
  collaborationMode: this.getCollaborationMode(response.thread.id),
27377
- approvalsReviewer: normalizeApprovalsReviewer(response.approvalsReviewer),
27378
28168
  modelProvider: response.modelProvider,
27379
28169
  currentServiceTier: response.serviceTier ?? null,
27380
28170
  additionalDirectories
@@ -27403,13 +28193,18 @@ var CodexAcpClient = class {
27403
28193
  currentModelId,
27404
28194
  models: codexModels,
27405
28195
  collaborationMode: this.getCollaborationMode(response.thread.id),
27406
- approvalsReviewer: normalizeApprovalsReviewer(response.approvalsReviewer),
27407
28196
  modelProvider: response.modelProvider,
27408
28197
  currentServiceTier: response.serviceTier ?? null,
27409
28198
  thread: historyResponse.thread,
27410
28199
  additionalDirectories
27411
28200
  };
27412
28201
  }
28202
+ async readSessionThread(sessionId) {
28203
+ return (await this.codexClient.threadRead({
28204
+ threadId: sessionId,
28205
+ includeTurns: true
28206
+ })).thread;
28207
+ }
27413
28208
  async newSession(request) {
27414
28209
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27415
28210
  const initialAgentMode = AgentMode.getInitialAgentMode();
@@ -27430,7 +28225,6 @@ var CodexAcpClient = class {
27430
28225
  currentModelId,
27431
28226
  models: codexModels,
27432
28227
  collaborationMode: this.getCollaborationMode(response.thread.id),
27433
- approvalsReviewer: normalizeApprovalsReviewer(response.approvalsReviewer),
27434
28228
  modelProvider: response.modelProvider,
27435
28229
  currentServiceTier: response.serviceTier ?? null,
27436
28230
  additionalDirectories
@@ -27441,6 +28235,7 @@ var CodexAcpClient = class {
27441
28235
  await this.codexClient.threadUnsubscribe({ threadId: sessionId });
27442
28236
  } finally {
27443
28237
  this.codexClient.clearThreadHandlers(sessionId);
28238
+ this.subagents.clear(sessionId);
27444
28239
  }
27445
28240
  }
27446
28241
  permissionProfileSelection(agentMode, cwd, additionalDirectories) {
@@ -27575,7 +28370,7 @@ var CodexAcpClient = class {
27575
28370
  if (!cwd) {
27576
28371
  return;
27577
28372
  }
27578
- const skillExtraRoots = additionalRoots.map((root) => path6.join(root, ".agents", "skills"));
28373
+ const skillExtraRoots = additionalRoots.map((root) => path7.join(root, ".agents", "skills"));
27579
28374
  if (!arraysEqual(this.skillExtraRoots, skillExtraRoots)) {
27580
28375
  await this.codexClient.skillsExtraRootsSet({ extraRoots: skillExtraRoots });
27581
28376
  this.skillExtraRoots = skillExtraRoots;
@@ -27626,33 +28421,21 @@ var CodexAcpClient = class {
27626
28421
  }
27627
28422
  return ModelId.create(defaultModel.id, reasoningEffort ?? defaultModel.defaultReasoningEffort);
27628
28423
  }
27629
- async subscribeToSessionEvents(sessionId, eventHandler, approvalHandler, elicitationHandler) {
27630
- this.codexClient.onServerNotification(sessionId, (event) => {
28424
+ async subscribeToSessionEvents(sessionId, eventHandler, approvalHandler, elicitationHandler, supportsSubagents, observeInteraction, waitForChildSession) {
28425
+ const dispatch = (event) => {
27631
28426
  this.enqueueSessionNotification(sessionId, () => eventHandler(event));
27632
- });
27633
- this.codexClient.onApprovalRequest(sessionId, {
27634
- handleCommandExecution: async (params) => {
27635
- await this.waitForSessionNotifications(sessionId);
27636
- return await approvalHandler.handleCommandExecution(params);
27637
- },
27638
- handleFileChange: async (params) => {
27639
- await this.waitForSessionNotifications(sessionId);
27640
- return await approvalHandler.handleFileChange(params);
27641
- },
27642
- handlePermissionsRequest: async (params) => {
27643
- await this.waitForSessionNotifications(sessionId);
27644
- return await approvalHandler.handlePermissionsRequest(params);
27645
- }
27646
- });
27647
- this.codexClient.onElicitationRequest(sessionId, {
27648
- handleElicitation: async (params) => {
27649
- await this.waitForSessionNotifications(sessionId);
27650
- return await elicitationHandler.handleElicitation(params);
28427
+ };
28428
+ this.subagents.subscribe({
28429
+ rootSessionId: sessionId,
28430
+ supportsSubagents,
28431
+ dispatch,
28432
+ enqueueInteraction: (event) => {
28433
+ this.enqueueSessionNotification(sessionId, () => observeInteraction(event));
27651
28434
  },
27652
- handleUserInput: async (params) => {
27653
- await this.waitForSessionNotifications(sessionId);
27654
- return await elicitationHandler.handleUserInput(params);
27655
- }
28435
+ approvalHandler,
28436
+ elicitationHandler,
28437
+ waitForRootNotifications: () => this.waitForSessionNotifications(sessionId),
28438
+ waitForChildSession
27656
28439
  });
27657
28440
  }
27658
28441
  async waitForSessionNotifications(sessionId) {
@@ -27679,7 +28462,7 @@ var CodexAcpClient = class {
27679
28462
  }
27680
28463
  });
27681
28464
  }
27682
- async sendPrompt(request, agentMode, approvalsReviewer, modelId, serviceTier, disableSummary, cwd, additionalDirectories, onTurnStarted, shouldCancel) {
28465
+ async sendPrompt(request, agentMode, modelId, serviceTier, disableSummary, cwd, additionalDirectories, onTurnStarted, shouldCancel) {
27683
28466
  const input = buildPromptItems(request.prompt);
27684
28467
  const effort = modelId.effort;
27685
28468
  await this.refreshSkills(cwd, additionalDirectories);
@@ -27695,7 +28478,7 @@ var CodexAcpClient = class {
27695
28478
  threadId: request.sessionId,
27696
28479
  input,
27697
28480
  approvalPolicy: agentMode.approvalPolicy,
27698
- approvalsReviewer,
28481
+ approvalsReviewer: agentMode.approvalsReviewer,
27699
28482
  ...sandboxSelection,
27700
28483
  summary: disableSummary ? "none" : "auto",
27701
28484
  effort,
@@ -27750,22 +28533,9 @@ var CodexAcpClient = class {
27750
28533
  });
27751
28534
  const outcome = await budget.wait(turnPromise);
27752
28535
  auditTurnCompleted = true;
27753
- const thread = await budget.wait(this.codexClient.threadRead({
27754
- threadId: forkThreadId,
27755
- includeTurns: true
27756
- }));
27757
- const completedTurn = thread.thread.turns.find(
27758
- (turn) => turn.id === outcome.turn.id
27759
- );
27760
- if (completedTurn === void 0) {
27761
- throw new AgentFileChangeReportError(
27762
- "notReported",
27763
- "The completed audit turn was not present in thread history"
27764
- );
27765
- }
27766
28536
  return createReportedAgentFileChangeReport(
27767
28537
  params.requestId,
27768
- completedTurn,
28538
+ outcome.turn,
27769
28539
  params.workspace
27770
28540
  );
27771
28541
  } catch (error51) {
@@ -27782,7 +28552,7 @@ var CodexAcpClient = class {
27782
28552
  return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
27783
28553
  }
27784
28554
  if (error51 instanceof AgentFileChangeReportError) {
27785
- logger.log("Agent file-change report unavailable", { reason: error51.reason });
28555
+ logger.error("Agent file-change report unavailable", error51);
27786
28556
  return createUnavailableAgentFileChangeReport(params.requestId, error51.reason);
27787
28557
  }
27788
28558
  logger.error("Agent file-change report failed", error51);
@@ -28080,8 +28850,8 @@ function formatUriAsLink(name, uri) {
28080
28850
  return `[@${name}](${uri})`;
28081
28851
  }
28082
28852
  if (uri.startsWith("file://")) {
28083
- const path8 = uri.replace("file://", "");
28084
- const fileName = path8.split("/").pop() ?? path8;
28853
+ const path9 = uri.replace("file://", "");
28854
+ const fileName = path9.split("/").pop() ?? path9;
28085
28855
  return `[@${fileName}](${uri})`;
28086
28856
  }
28087
28857
  return uri;
@@ -28111,7 +28881,7 @@ function readAdditionalDirectories(cwd, additionalDirectories, meta3) {
28111
28881
  if (directory.length === 0) {
28112
28882
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must not be empty");
28113
28883
  }
28114
- if (!path6.isAbsolute(directory)) {
28884
+ if (!path7.isAbsolute(directory)) {
28115
28885
  throw RequestError.invalidParams(void 0, "additionalDirectories entries must be absolute paths");
28116
28886
  }
28117
28887
  if (!seen.has(directory)) {
@@ -28269,11 +29039,11 @@ var CodexAppServerClient = class {
28269
29039
  });
28270
29040
  this.connection.onRequest(PermissionsApprovalRequest, async (params) => {
28271
29041
  if (this.isStaleTurn(params.threadId, params.turnId)) {
28272
- return { permissions: {}, scope: "turn", strictAutoReview: true };
29042
+ return { permissions: {}, scope: "turn", strictAutoReview: false };
28273
29043
  }
28274
29044
  const handler = this.approvalHandlers.get(params.threadId);
28275
29045
  if (!handler) {
28276
- return { permissions: {}, scope: "turn", strictAutoReview: true };
29046
+ return { permissions: {}, scope: "turn", strictAutoReview: false };
28277
29047
  }
28278
29048
  return await handler.handlePermissionsRequest(params);
28279
29049
  });
@@ -29036,11 +29806,14 @@ var CodexCommands = class {
29036
29806
  this.runWithProcessCheck = runWithProcessCheck;
29037
29807
  this.onLogout = onLogout;
29038
29808
  }
29039
- async publish(sessionState) {
29809
+ async publish(sessionState, shouldPublish = () => true) {
29040
29810
  try {
29811
+ if (!shouldPublish()) {
29812
+ return;
29813
+ }
29041
29814
  const skillsResponse = await this.runWithProcessCheck(() => this.codexAcpClient.listSkills(this.createSkillsListParams(sessionState)));
29042
29815
  const availableCommands = this.buildAvailableCommands(skillsResponse?.data ?? []);
29043
- if (availableCommands.length === 0) {
29816
+ if (availableCommands.length === 0 || !shouldPublish()) {
29044
29817
  return;
29045
29818
  }
29046
29819
  const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
@@ -29049,7 +29822,9 @@ var CodexCommands = class {
29049
29822
  availableCommands
29050
29823
  });
29051
29824
  } catch (err) {
29052
- logger.error(`Failed to publish available commands for session ${sessionState.sessionId}`, err);
29825
+ if (shouldPublish()) {
29826
+ logger.error(`Failed to publish available commands for session ${sessionState.sessionId}`, err);
29827
+ }
29053
29828
  }
29054
29829
  }
29055
29830
  createSkillsListParams(sessionState) {
@@ -29508,7 +30283,7 @@ var SteeringQueue = class {
29508
30283
 
29509
30284
  // src/ResponseItemHistoryFallback.ts
29510
30285
  import { readFile as readFile2 } from "node:fs/promises";
29511
- import path7 from "node:path";
30286
+ import path8 from "node:path";
29512
30287
  function historyFallbackUpdateKey(update) {
29513
30288
  switch (update.sessionUpdate) {
29514
30289
  case "user_message_chunk":
@@ -29748,8 +30523,8 @@ function imageBlocks(images) {
29748
30523
  return [{ type: "text", text: `[@image](${image})` }];
29749
30524
  }
29750
30525
  const record2 = asRecord2(image);
29751
- const path8 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
29752
- return path8 ? [{ type: "text", text: `[@image](${path8})` }] : [];
30526
+ const path9 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
30527
+ return path9 ? [{ type: "text", text: `[@image](${path9})` }] : [];
29753
30528
  });
29754
30529
  }
29755
30530
  function contentBlocksFromResponseContent(content) {
@@ -30381,10 +31156,10 @@ function baseCommandName(commandName) {
30381
31156
  return commandName.split(/[\\/]/).pop() ?? commandName;
30382
31157
  }
30383
31158
  function absolutizePath(cwd, targetPath) {
30384
- if (path7.isAbsolute(targetPath) || cwd.length === 0) {
31159
+ if (path8.isAbsolute(targetPath) || cwd.length === 0) {
30385
31160
  return targetPath;
30386
31161
  }
30387
- return path7.join(cwd, targetPath);
31162
+ return path8.join(cwd, targetPath);
30388
31163
  }
30389
31164
  function withTerminalContent(event, terminalId, cwd) {
30390
31165
  const { rawInput, ...eventWithoutRawInput } = event;
@@ -30542,16 +31317,7 @@ function clientSupportsPlanUpdates(clientCapabilities) {
30542
31317
  // src/CodexAcpServer.ts
30543
31318
  import { randomUUID as randomUUID2 } from "node:crypto";
30544
31319
  import { once } from "node:events";
30545
- var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
30546
- var REVISE_PLAN_OPTION_ID = "revise_plan";
30547
31320
  var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
30548
- function clientSupportsAirCapability(capabilities, capability) {
30549
- const jetbrains = capabilities?._meta?.[JETBRAINS_META_KEY];
30550
- const air = jetbrains?.[AIR_META_KEY];
30551
- const version2 = air?.[AIR_EXTENSION_VERSION_KEY];
30552
- const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
30553
- return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
30554
- }
30555
31321
  function clientSupportsTypedSessionFailures(capabilities) {
30556
31322
  return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
30557
31323
  }
@@ -30584,6 +31350,7 @@ var CodexAcpServer = class _CodexAcpServer {
30584
31350
  sessionGenerations;
30585
31351
  sessionOpenGenerations;
30586
31352
  goalControlGenerations;
31353
+ permissionLifecycleContexts;
30587
31354
  codexProcessState;
30588
31355
  initializeRequest = null;
30589
31356
  providerUpdate = null;
@@ -30597,6 +31364,7 @@ var CodexAcpServer = class _CodexAcpServer {
30597
31364
  this.sessionGenerations = /* @__PURE__ */ new Map();
30598
31365
  this.sessionOpenGenerations = /* @__PURE__ */ new Map();
30599
31366
  this.goalControlGenerations = /* @__PURE__ */ new Map();
31367
+ this.permissionLifecycleContexts = /* @__PURE__ */ new WeakMap();
30600
31368
  this.connection = connection;
30601
31369
  this.codexAcpClient = codexAcpClient;
30602
31370
  this.defaultAuthRequest = defaultAuthRequest ?? null;
@@ -30627,6 +31395,14 @@ var CodexAcpServer = class _CodexAcpServer {
30627
31395
  this.terminalOutputMode = resolveTerminalOutputMode(_params.clientCapabilities);
30628
31396
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
30629
31397
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
31398
+ const sessionCapabilities = {
31399
+ resume: {},
31400
+ list: {},
31401
+ close: {},
31402
+ delete: {},
31403
+ additionalDirectories: {},
31404
+ subagents: {}
31405
+ };
30630
31406
  return {
30631
31407
  protocolVersion: PROTOCOL_VERSION,
30632
31408
  agentInfo: {
@@ -30644,13 +31420,7 @@ var CodexAcpServer = class _CodexAcpServer {
30644
31420
  embeddedContext: true,
30645
31421
  image: true
30646
31422
  },
30647
- sessionCapabilities: {
30648
- resume: {},
30649
- list: {},
30650
- close: {},
30651
- delete: {},
30652
- additionalDirectories: {}
30653
- },
31423
+ sessionCapabilities,
30654
31424
  mcpCapabilities: {
30655
31425
  acp: false,
30656
31426
  http: true,
@@ -30672,7 +31442,8 @@ var CodexAcpServer = class _CodexAcpServer {
30672
31442
  [AIR_EXTENSION_VERSION_KEY]: AIR_EXTENSION_VERSION,
30673
31443
  [AIR_EXTENSION_CAPABILITIES_KEY]: [
30674
31444
  AIR_SESSION_FAILURE_KEY,
30675
- AIR_AGENT_FILE_CHANGE_REPORT_KEY
31445
+ AIR_AGENT_FILE_CHANGE_REPORT_KEY,
31446
+ AIR_NATIVE_SUBAGENT_SESSIONS_KEY
30676
31447
  ]
30677
31448
  }
30678
31449
  }
@@ -30724,7 +31495,7 @@ var CodexAcpServer = class _CodexAcpServer {
30724
31495
  }
30725
31496
  } else if (methodRequest.params.action === "pause") {
30726
31497
  const goal = await this.runWithProcessCheck(() => this.codexAcpClient.setGoalStatus(sessionState.sessionId, "paused"));
30727
- if (this.goalPublishIsCurrent(sessionState, sessionGeneration)) {
31498
+ if (this.sessionPublishIsCurrent(sessionState, sessionGeneration)) {
30728
31499
  await this.publishGoalSnapshot(sessionState, toThreadGoalSnapshot(goal), false);
30729
31500
  }
30730
31501
  } else if (methodRequest.params.action === "resume") {
@@ -30736,7 +31507,7 @@ var CodexAcpServer = class _CodexAcpServer {
30736
31507
  updatedGoal = goal;
30737
31508
  }
30738
31509
  ));
30739
- if (updatedGoal !== null && this.goalPublishIsCurrent(sessionState, sessionGeneration)) {
31510
+ if (updatedGoal !== null && this.sessionPublishIsCurrent(sessionState, sessionGeneration)) {
30740
31511
  await this.publishGoalSnapshot(sessionState, toThreadGoalSnapshot(updatedGoal), false);
30741
31512
  }
30742
31513
  if (turnCompleted === null && updatedGoal !== null) {
@@ -30749,7 +31520,7 @@ var CodexAcpServer = class _CodexAcpServer {
30749
31520
  }
30750
31521
  } else if (methodRequest.params.action === "clear") {
30751
31522
  await this.runWithProcessCheck(() => this.codexAcpClient.clearGoal(sessionState.sessionId));
30752
- if (this.goalPublishIsCurrent(sessionState, sessionGeneration)) {
31523
+ if (this.sessionPublishIsCurrent(sessionState, sessionGeneration)) {
30753
31524
  await this.publishGoalSnapshot(sessionState, null, false);
30754
31525
  }
30755
31526
  }
@@ -30907,7 +31678,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
30907
31678
  supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
30908
31679
  supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
30909
31680
  agentMode: AgentMode.getInitialAgentMode(),
30910
- approvalsReviewer: sessionMetadata.approvalsReviewer,
30911
31681
  collaborationMode: sessionMetadata.collaborationMode,
30912
31682
  currentTurnId: null,
30913
31683
  lastTokenUsage: null,
@@ -30926,7 +31696,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
30926
31696
  terminalOutputMode: this.terminalOutputMode,
30927
31697
  goalRevision: 0,
30928
31698
  sessionTitle: null,
30929
- sessionTitleSource: "sessionId" in request ? "unknown" : "unset"
31699
+ sessionTitleSource: "sessionId" in request ? "unknown" : "unset",
31700
+ subagents: new CodexSubagentEventRouter(
31701
+ sessionId,
31702
+ clientSupportsSubagents(this.clientCapabilities),
31703
+ new ACPSessionConnection(this.connection, sessionId)
31704
+ )
30930
31705
  };
30931
31706
  this.sessions.set(sessionId, sessionState);
30932
31707
  resumeSubscribed = false;
@@ -30937,7 +31712,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30937
31712
  });
30938
31713
  this.publishMcpStartupStatusAsync(sessionId);
30939
31714
  }
30940
- this.publishAvailableCommandsAsync(sessionState);
31715
+ this.publishAvailableCommandsAsync(sessionState, sessionGeneration);
30941
31716
  if ("sessionId" in request) {
30942
31717
  this.publishCurrentGoalAsync(sessionState, sessionGeneration);
30943
31718
  }
@@ -31120,12 +31895,24 @@ Check ${configPath} and project .codex directories, especially their config.toml
31120
31895
  if (requestId == null || !clientSupportsUrlElicitation(this.clientCapabilities)) {
31121
31896
  return void 0;
31122
31897
  }
31898
+ let elicitationId = null;
31123
31899
  return {
31124
- elicitUrl: (request) => this.connection.request(methods.client.elicitation.create, {
31125
- mode: "url",
31126
- requestId,
31127
- ...request
31128
- })
31900
+ elicitUrl: (request) => {
31901
+ elicitationId = request.elicitationId;
31902
+ return this.connection.request(methods.client.elicitation.create, {
31903
+ mode: "url",
31904
+ requestId,
31905
+ ...request
31906
+ });
31907
+ },
31908
+ completeElicitation: async () => {
31909
+ if (elicitationId === null) {
31910
+ return;
31911
+ }
31912
+ await this.connection.notify(methods.client.elicitation.complete, {
31913
+ elicitationId
31914
+ });
31915
+ }
31129
31916
  };
31130
31917
  }
31131
31918
  async logout(_params) {
@@ -31279,9 +32066,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
31279
32066
  case MODE_CONFIG_ID:
31280
32067
  await this.applyModeChange(sessionState, this.stringConfigValue(params));
31281
32068
  break;
31282
- case APPROVALS_REVIEWER_CONFIG_ID:
31283
- this.applyApprovalsReviewerChange(sessionState, this.stringConfigValue(params));
31284
- break;
31285
32069
  case COLLABORATION_MODE_CONFIG_ID:
31286
32070
  await this.applyCollaborationModeChange(sessionState, this.stringConfigValue(params));
31287
32071
  break;
@@ -31323,13 +32107,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
31323
32107
  );
31324
32108
  sessionState.agentMode = newMode;
31325
32109
  }
31326
- applyApprovalsReviewerChange(sessionState, value) {
31327
- const approvalsReviewer = parseApprovalsReviewer(value);
31328
- if (approvalsReviewer === null) {
31329
- throw RequestError.invalidParams();
31330
- }
31331
- sessionState.approvalsReviewer = approvalsReviewer;
31332
- }
31333
32110
  async applyCollaborationModeChange(sessionState, value) {
31334
32111
  const mode = parseCollaborationMode(value);
31335
32112
  if (mode === null) {
@@ -31533,7 +32310,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31533
32310
  sessionId: sessionState.sessionId,
31534
32311
  prompt: GOAL_CONTINUATION_PROMPT
31535
32312
  }, "Goal continuation", async () => {
31536
- if (!this.goalPublishIsCurrent(sessionState, sessionGeneration) || this.goalControlGenerations.get(sessionState.sessionId) !== goalControlGeneration) {
32313
+ if (!this.sessionPublishIsCurrent(sessionState, sessionGeneration) || this.goalControlGenerations.get(sessionState.sessionId) !== goalControlGeneration) {
31537
32314
  return false;
31538
32315
  }
31539
32316
  const currentGoal = await this.runWithProcessCheck(() => this.codexAcpClient.getGoal(sessionState.sessionId));
@@ -31617,7 +32394,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
31617
32394
  const currentModelId = ModelId.fromString(sessionState.currentModelId);
31618
32395
  const configOptions = [
31619
32396
  sessionState.agentMode.toConfigOption(),
31620
- createApprovalsReviewerConfigOption(sessionState.approvalsReviewer),
31621
32397
  createCollaborationModeConfigOption(sessionState.collaborationMode),
31622
32398
  createModelConfigOption(sessionState.availableModels, currentModelId.model)
31623
32399
  ];
@@ -31645,8 +32421,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
31645
32421
  isSessionConfigEnabled() {
31646
32422
  return !isJetBrains2026_1Client(this.clientInfo);
31647
32423
  }
31648
- publishAvailableCommandsAsync(sessionState) {
31649
- void this.availableCommands.publish(sessionState);
32424
+ publishAvailableCommandsAsync(sessionState, sessionGeneration) {
32425
+ void this.publishAvailableCommands(sessionState, sessionGeneration);
32426
+ }
32427
+ async publishAvailableCommands(sessionState, sessionGeneration) {
32428
+ await this.availableCommands.publish(
32429
+ sessionState,
32430
+ () => this.sessionPublishIsCurrent(sessionState, sessionGeneration)
32431
+ );
31650
32432
  }
31651
32433
  publishCurrentGoalAsync(sessionState, sessionGeneration) {
31652
32434
  void this.publishCurrentGoalBestEffort(sessionState, sessionGeneration, true);
@@ -31662,12 +32444,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
31662
32444
  const requestRevision = ++sessionState.goalRevision;
31663
32445
  const goal = await this.runWithProcessCheck(() => this.codexAcpClient.getGoal(sessionState.sessionId));
31664
32446
  const snapshot = goal === null ? null : toThreadGoalSnapshot(goal);
31665
- if (!this.goalPublishIsCurrent(sessionState, sessionGeneration) || sessionState.goalRevision !== requestRevision) {
32447
+ if (!this.sessionPublishIsCurrent(sessionState, sessionGeneration) || sessionState.goalRevision !== requestRevision) {
31666
32448
  return;
31667
32449
  }
31668
32450
  await this.publishGoalSnapshot(sessionState, snapshot, force, false);
31669
32451
  }
31670
- goalPublishIsCurrent(sessionState, sessionGeneration) {
32452
+ sessionPublishIsCurrent(sessionState, sessionGeneration) {
31671
32453
  return this.sessions.get(sessionState.sessionId) === sessionState && this.getSessionGeneration(sessionState.sessionId) === sessionGeneration && !this.sessionIsClosing(sessionState.sessionId);
31672
32454
  }
31673
32455
  async publishGoalSnapshot(sessionState, snapshot, force, incrementRevision = true) {
@@ -31751,7 +32533,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
31751
32533
  supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
31752
32534
  supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
31753
32535
  agentMode: AgentMode.getInitialAgentMode(),
31754
- approvalsReviewer: sessionMetadata.approvalsReviewer,
31755
32536
  collaborationMode: sessionMetadata.collaborationMode,
31756
32537
  currentTurnId: null,
31757
32538
  lastTokenUsage: null,
@@ -31770,7 +32551,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
31770
32551
  terminalOutputMode: this.terminalOutputMode,
31771
32552
  goalRevision: 0,
31772
32553
  sessionTitle: null,
31773
- sessionTitleSource: "unset"
32554
+ sessionTitleSource: "unset",
32555
+ subagents: new CodexSubagentEventRouter(
32556
+ sessionId,
32557
+ clientSupportsSubagents(this.clientCapabilities),
32558
+ new ACPSessionConnection(this.connection, sessionId)
32559
+ )
31774
32560
  };
31775
32561
  this.sessions.set(sessionId, sessionState);
31776
32562
  subscribed = false;
@@ -31781,7 +32567,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31781
32567
  });
31782
32568
  this.publishMcpStartupStatusAsync(sessionId);
31783
32569
  }
31784
- await this.availableCommands.publish(sessionState);
32570
+ await this.publishAvailableCommands(sessionState, requestedSessionGeneration);
31785
32571
  await this.publishCurrentGoalBestEffort(sessionState, requestedSessionGeneration, true);
31786
32572
  const sessionModelState = this.createModelState(models, currentModelId);
31787
32573
  const sessionModeState = sessionState.agentMode.toSessionModeState();
@@ -31796,6 +32582,16 @@ Check ${configPath} and project .codex directories, especially their config.toml
31796
32582
  const session = new ACPSessionConnection(this.connection, sessionId);
31797
32583
  const sessionState = this.getSessionState(sessionId);
31798
32584
  await this.publishThreadHistoryTitle(session, sessionState, thread);
32585
+ if (clientSupportsSubagents(this.clientCapabilities)) {
32586
+ await this.streamNativeThreadHistory(
32587
+ sessionId,
32588
+ thread,
32589
+ sessionState,
32590
+ /* @__PURE__ */ new Set([sessionId]),
32591
+ /* @__PURE__ */ new Map([[sessionId, thread]])
32592
+ );
32593
+ return;
32594
+ }
31799
32595
  const responseItemFallbackUpdates = await createResponseItemHistoryFallbackUpdates(
31800
32596
  thread,
31801
32597
  sessionState.terminalOutputMode
@@ -31812,6 +32608,93 @@ Check ${configPath} and project .codex directories, especially their config.toml
31812
32608
  await session.update(update);
31813
32609
  }
31814
32610
  }
32611
+ async streamNativeThreadHistory(sessionId, thread, sessionState, ancestry, threadCache) {
32612
+ const session = new ACPSessionConnection(this.connection, sessionId);
32613
+ const announced = /* @__PURE__ */ new Map();
32614
+ for (const turn of thread.turns) {
32615
+ for (const item of turn.items) {
32616
+ if (item.type === "subAgentActivity") {
32617
+ const activityKind = item.kind;
32618
+ if (activityKind === "started") {
32619
+ const previous = announced.get(item.agentThreadId);
32620
+ if (previous && !previous.terminal) continue;
32621
+ const generation = (previous?.generation ?? 0) + 1;
32622
+ const childSessionId = generation === 1 ? item.agentThreadId : `${item.agentThreadId}:generation:${generation}`;
32623
+ const name = nameFromAgentPath(item.agentPath, `Agent ${item.agentThreadId.slice(-8)}`);
32624
+ await session.update({
32625
+ sessionUpdate: "subagent_spawned",
32626
+ subagentSessionId: childSessionId,
32627
+ name,
32628
+ task: `Delegated task for ${name}`,
32629
+ capabilities: {}
32630
+ });
32631
+ announced.set(item.agentThreadId, { generation, sessionId: childSessionId, terminal: false });
32632
+ if (!ancestry.has(item.agentThreadId)) {
32633
+ let child = threadCache.get(item.agentThreadId);
32634
+ if (child === void 0) {
32635
+ try {
32636
+ child = await this.codexAcpClient.readSessionThread(item.agentThreadId);
32637
+ threadCache.set(item.agentThreadId, child);
32638
+ } catch (error51) {
32639
+ threadCache.set(item.agentThreadId, null);
32640
+ logger.error(`Failed to read subagent history ${item.agentThreadId}`, error51);
32641
+ child = null;
32642
+ }
32643
+ }
32644
+ const childTurn = child?.turns[generation - 1];
32645
+ if (child && childTurn) {
32646
+ await this.streamNativeThreadHistory(
32647
+ childSessionId,
32648
+ { ...child, turns: [childTurn] },
32649
+ sessionState,
32650
+ /* @__PURE__ */ new Set([...ancestry, item.agentThreadId]),
32651
+ threadCache
32652
+ );
32653
+ }
32654
+ }
32655
+ } else if (activityKind === "completed" || activityKind === "interrupted") {
32656
+ const child = announced.get(item.agentThreadId);
32657
+ if (!child) {
32658
+ const name = nameFromAgentPath(item.agentPath, `Agent ${item.agentThreadId.slice(-8)}`);
32659
+ await session.update({
32660
+ sessionUpdate: "subagent_spawned",
32661
+ subagentSessionId: item.agentThreadId,
32662
+ name,
32663
+ task: `Delegated task for ${name}`,
32664
+ capabilities: {}
32665
+ });
32666
+ announced.set(item.agentThreadId, {
32667
+ generation: 1,
32668
+ sessionId: item.agentThreadId,
32669
+ terminal: false
32670
+ });
32671
+ continue;
32672
+ }
32673
+ if (child.terminal) continue;
32674
+ await session.update({
32675
+ sessionUpdate: "subagent_state_update",
32676
+ subagentSessionId: child.sessionId,
32677
+ state: activityKind === "completed" ? "completed" : "cancelled"
32678
+ });
32679
+ child.terminal = true;
32680
+ }
32681
+ continue;
32682
+ }
32683
+ if (item.type === "collabAgentToolCall") continue;
32684
+ for (const update of await this.createHistoryUpdates(item, sessionState)) {
32685
+ await session.update(update);
32686
+ }
32687
+ }
32688
+ }
32689
+ for (const child of announced.values()) {
32690
+ if (child.terminal) continue;
32691
+ await session.update({
32692
+ sessionUpdate: "subagent_state_update",
32693
+ subagentSessionId: child.sessionId,
32694
+ state: "disconnected"
32695
+ });
32696
+ }
32697
+ }
31815
32698
  async publishThreadHistoryTitle(session, sessionState, thread) {
31816
32699
  const explicitTitle = this.normalizeSessionTitle(thread.name);
31817
32700
  if (explicitTitle) {
@@ -32016,8 +32899,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
32016
32899
  return `[@${name}](${uri})`;
32017
32900
  }
32018
32901
  if (uri.startsWith("file://")) {
32019
- const path8 = uri.replace("file://", "");
32020
- const fileName = path8.split("/").pop() ?? path8;
32902
+ const path9 = uri.replace("file://", "");
32903
+ const fileName = path9.split("/").pop() ?? path9;
32021
32904
  return `[@${fileName}](${uri})`;
32022
32905
  }
32023
32906
  return uri;
@@ -32029,6 +32912,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
32029
32912
  }
32030
32913
  return sessionState;
32031
32914
  }
32915
+ permissionLifecycleContext(sessionState) {
32916
+ const existing = this.permissionLifecycleContexts.get(sessionState);
32917
+ if (existing) return existing;
32918
+ const context = new PermissionLifecycleContext(sessionState);
32919
+ this.permissionLifecycleContexts.set(sessionState, context);
32920
+ return context;
32921
+ }
32032
32922
  resolveSessionMcpServers(mcpServers, recoverFromStartup) {
32033
32923
  const requestedServerNames = getRequestedMcpServerNames(mcpServers);
32034
32924
  if (requestedServerNames.length > 0) {
@@ -32276,6 +33166,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32276
33166
  const agentFileChangeReportRequest = clientSupportsAgentFileChangeReports(this.clientCapabilities) ? parseAgentFileChangeReportRequest(params._meta) : null;
32277
33167
  let agentFileChangeReportTurnId = null;
32278
33168
  let agentFileChangeReportUnavailableReason = "providerError";
33169
+ let promptWasCancelled = false;
32279
33170
  let recoverableSessionFailure = sessionState.sessionFailure;
32280
33171
  const promptStartTotalTokenUsage = sessionState.totalTokenUsage;
32281
33172
  sessionState.currentTurnId = null;
@@ -32300,6 +33191,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32300
33191
  }
32301
33192
  };
32302
33193
  const cancelledPromptResponse = () => {
33194
+ promptWasCancelled = true;
32303
33195
  agentFileChangeReportTurnId = null;
32304
33196
  agentFileChangeReportUnavailableReason = "cancelled";
32305
33197
  return this.cancelledPromptResponse(sessionState, promptStartTotalTokenUsage);
@@ -32310,32 +33202,46 @@ Check ${configPath} and project .codex directories, especially their config.toml
32310
33202
  sessionState,
32311
33203
  clientSupportsPlanUpdates(this.clientCapabilities),
32312
33204
  clientSupportsTypedSessionFailures(this.clientCapabilities),
32313
- this.sessionFailureEpoch
33205
+ this.sessionFailureEpoch,
33206
+ sessionState.subagents
32314
33207
  );
32315
33208
  eventHandler = promptEventHandler;
32316
- const approvalHandler = new CodexApprovalHandler(this.connection, sessionState, activePrompt.signal);
33209
+ const permissionLifecycle = this.permissionLifecycleContext(sessionState);
33210
+ const permissionContext = permissionLifecycle.beginPrompt();
33211
+ const approvalHandler = new CodexApprovalHandler(
33212
+ this.connection,
33213
+ permissionContext,
33214
+ activePrompt.signal
33215
+ );
32317
33216
  const elicitationHandler = new CodexElicitationHandler(
32318
33217
  this.connection,
32319
- sessionState,
33218
+ permissionContext,
32320
33219
  this.clientCapabilities,
32321
33220
  activePrompt.signal
32322
33221
  );
33222
+ const observeInteraction = async (event) => {
33223
+ permissionContext.handleNotification(event);
33224
+ await elicitationHandler.handleNotification(event);
33225
+ };
32323
33226
  await this.codexAcpClient.subscribeToSessionEvents(
32324
33227
  params.sessionId,
32325
33228
  async (event) => {
33229
+ await observeInteraction(event);
32326
33230
  if (!promptNotificationsActive) {
32327
33231
  await promptEventHandler.handleSessionScopedNotification(event);
32328
33232
  return;
32329
33233
  }
32330
- const completesActiveTurn = event.method === "turn/completed" && event.params.turn.id === sessionState.currentTurnId;
32331
- await elicitationHandler.handleNotification(event);
33234
+ const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && event.params.turn.id === sessionState.currentTurnId;
32332
33235
  await promptEventHandler.handleNotification(event);
32333
33236
  if (completesActiveTurn) {
32334
33237
  promptNotificationsActive = false;
32335
33238
  }
32336
33239
  },
32337
33240
  approvalHandler,
32338
- elicitationHandler
33241
+ elicitationHandler,
33242
+ clientSupportsSubagents(this.clientCapabilities),
33243
+ observeInteraction,
33244
+ (childThreadId) => promptEventHandler.waitForNativeSubagentSession(childThreadId)
32339
33245
  );
32340
33246
  if (activePrompt.signal.aborted) {
32341
33247
  return cancelledPromptResponse();
@@ -32444,7 +33350,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
32444
33350
  () => this.codexAcpClient.sendPrompt(
32445
33351
  effectiveParams,
32446
33352
  agentMode,
32447
- sessionState.approvalsReviewer,
32448
33353
  modelId,
32449
33354
  serviceTier,
32450
33355
  disableSummary,
@@ -32478,6 +33383,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
32478
33383
  return cancelledPromptResponse();
32479
33384
  }
32480
33385
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33386
+ if (turnCompleted.turn.status === "completed") {
33387
+ await eventHandler.waitForNativeSubagents(activePrompt.signal);
33388
+ if (activePrompt.signal.aborted) return cancelledPromptResponse();
33389
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33390
+ } else {
33391
+ await eventHandler.finishOutstandingNativeSubagents(
33392
+ turnCompleted.turn.status === "interrupted" ? "cancelled" : "failed"
33393
+ );
33394
+ }
32481
33395
  await eventHandler.flushPendingErrors();
32482
33396
  await eventHandler.handleFailedTurn(turnCompleted.turn);
32483
33397
  promptNotificationsActive = false;
@@ -32526,7 +33440,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
32526
33440
  () => this.codexAcpClient.sendPrompt(
32527
33441
  implementationRequest,
32528
33442
  agentMode,
32529
- sessionState.approvalsReviewer,
32530
33443
  modelId,
32531
33444
  serviceTier,
32532
33445
  disableSummary,
@@ -32560,6 +33473,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
32560
33473
  return cancelledPromptResponse();
32561
33474
  }
32562
33475
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33476
+ if (turnCompleted.turn.status === "completed") {
33477
+ await eventHandler.waitForNativeSubagents(activePrompt.signal);
33478
+ if (activePrompt.signal.aborted) return cancelledPromptResponse();
33479
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33480
+ } else {
33481
+ await eventHandler.finishOutstandingNativeSubagents(
33482
+ turnCompleted.turn.status === "interrupted" ? "cancelled" : "failed"
33483
+ );
33484
+ }
32563
33485
  await eventHandler.flushPendingErrors();
32564
33486
  await eventHandler.handleFailedTurn(turnCompleted.turn);
32565
33487
  promptNotificationsActive = false;
@@ -32623,6 +33545,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
32623
33545
  throw err;
32624
33546
  } finally {
32625
33547
  promptNotificationsActive = false;
33548
+ try {
33549
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33550
+ await eventHandler?.finishOutstandingNativeSubagents(
33551
+ promptWasCancelled || activePrompt.signal.aborted || this.sessionIsClosing(params.sessionId) ? "cancelled" : "failed"
33552
+ );
33553
+ } catch (error51) {
33554
+ logger.error("Failed to publish terminal subagent state during prompt cleanup", error51);
33555
+ }
32626
33556
  if (agentFileChangeReportRequest !== null) {
32627
33557
  await this.publishAgentFileChangeReport(
32628
33558
  sessionState,
@@ -32645,41 +33575,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
32645
33575
  }
32646
33576
  }
32647
33577
  async requestPlanImplementationPermission(sessionState, plan, cancellationSignal) {
32648
- const toolCallId = `plan-review:${plan.itemId}`;
33578
+ const toolCallId = planImplementationToolCallId(plan);
32649
33579
  try {
32650
33580
  const response = await this.connection.request(
32651
33581
  methods.client.session.requestPermission,
32652
- {
32653
- sessionId: sessionState.sessionId,
32654
- toolCall: {
32655
- toolCallId,
32656
- title: "Implement this plan?",
32657
- kind: "switch_mode",
32658
- status: "pending",
32659
- rawInput: { plan: plan.text }
32660
- },
32661
- options: [
32662
- {
32663
- optionId: IMPLEMENT_PLAN_OPTION_ID,
32664
- name: "Yes, implement this plan",
32665
- kind: "allow_once"
32666
- },
32667
- {
32668
- optionId: REVISE_PLAN_OPTION_ID,
32669
- name: "No, and tell Codex what to do differently",
32670
- kind: "reject_once"
32671
- }
32672
- ],
32673
- _meta: {
32674
- codex: {
32675
- kind: "plan_review",
32676
- planItemId: plan.itemId
32677
- }
32678
- }
32679
- },
33582
+ planImplementationPermissionRequest(sessionState.sessionId, plan),
32680
33583
  { cancellationSignal }
32681
33584
  );
32682
- const approved = response.outcome.outcome === "selected" && response.outcome.optionId === IMPLEMENT_PLAN_OPTION_ID;
33585
+ const approved = planImplementationApproved(response);
32683
33586
  await this.connection.notify(methods.client.session.update, {
32684
33587
  sessionId: sessionState.sessionId,
32685
33588
  update: {