@alas-ide/codex-acp 2.5.0-alas.1 → 2.5.0-alas.3

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 +1 -0
  2. package/dist/index.js +479 -143
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -17,6 +17,7 @@ Use [OpenAI Codex](https://github.com/openai/codex) from [Agent Client Protocol]
17
17
  - For AIR, one tool call shape with each fact in one field, as the [tool call contract](docs/air-extensions.md#tool-call-contract) defines. Other clients keep the earlier fields.
18
18
  - [Native ACP subagent sessions](docs/subagent-sessions.md) (after capability negotiation) with separate child histories and root-routed permissions; a legacy tool-call fallback otherwise.
19
19
  - Mid-turn input through the [_session/steering extension](docs/steering.md), with an opt-in host-owned fallback when no turn can accept it.
20
+ - [Async Codex questions](docs/async-questions.md) as non-blocking form elicitations, with answers steered into the turn that asked them.
20
21
  - [Background terminal tasks](docs/air-extensions.md#async-tasks) in AIR, with task status and targeted stop support after capability negotiation.
21
22
  - Session-scoped long-running goals in AIR through the [goal extension](docs/air-extensions.md#goal).
22
23
  - Typed warnings and errors through the opt-in [AIR session failure extension](docs/air-extensions.md#session-failure).
package/dist/index.js CHANGED
@@ -32389,8 +32389,19 @@ var AcpV2Connection = class {
32389
32389
  return toV1RequestPermissionResponse(response);
32390
32390
  }, options?.cancellationSignal);
32391
32391
  }
32392
- /** Request-scoped login elicitations and idle background elicitations never change state. */
32393
- async createElicitation(request, options) {
32392
+ /**
32393
+ * Sends `elicitation/create`. Request-scoped login elicitations and idle background elicitations never change
32394
+ * the session state.
32395
+ *
32396
+ * @param request The elicitation to show.
32397
+ * @param options Request options, such as the signal that cancels the request.
32398
+ * @param interaction.userAction Whether the agent waits for the answer, `true` by default. While such an
32399
+ * elicitation is open in a running turn, the session reports `requires_action` whenever none of its items
32400
+ * is in progress. Pass `false` for a question the agent keeps working through, so the session state is left
32401
+ * as it is.
32402
+ */
32403
+ async createElicitation(request, options, interaction = {}) {
32404
+ const userAction = interaction.userAction ?? true;
32394
32405
  const sendRequest = async () => {
32395
32406
  const response = await this.client.request(
32396
32407
  methods2.client.elicitation.create,
@@ -32400,7 +32411,7 @@ var AcpV2Connection = class {
32400
32411
  return toV1CreateElicitationResponse(response);
32401
32412
  };
32402
32413
  const sessionId = elicitationSessionId(request);
32403
- if (sessionId === void 0) {
32414
+ if (sessionId === void 0 || !userAction) {
32404
32415
  return sendRequest();
32405
32416
  }
32406
32417
  const toolCallId = "toolCallId" in request && typeof request.toolCallId === "string" ? request.toolCallId : void 0;
@@ -37649,12 +37660,16 @@ ${params.turnId}`;
37649
37660
  }
37650
37661
  return this.createAgentMessageText(citations.push(event.delta), event.itemId, event.threadId);
37651
37662
  }
37652
- /** Sends the text a file citation held back, once the message is complete. */
37653
- flushAgentMessageText(itemId, threadId) {
37663
+ /**
37664
+ * Sends the text a file citation held back, once the message is complete. A message that
37665
+ * arrives whole, without deltas (an async question, for one), is sent in full.
37666
+ */
37667
+ flushAgentMessageText(item, threadId) {
37654
37668
  const streams = this.streamsFor(threadId);
37655
- const rest = streams.agentMessageCitations.get(itemId)?.flush() ?? "";
37656
- streams.agentMessageCitations.delete(itemId);
37657
- return this.createAgentMessageText(rest, itemId, threadId);
37669
+ const citations = streams.agentMessageCitations.get(item.id);
37670
+ streams.agentMessageCitations.delete(item.id);
37671
+ const text = citations ? citations.flush() : renderFileCitations(item.text, this.sessionState.cwd);
37672
+ return this.createAgentMessageText(text, item.id, threadId);
37658
37673
  }
37659
37674
  createAgentMessageText(text, itemId, threadId) {
37660
37675
  if (text.length === 0) return null;
@@ -37809,7 +37824,7 @@ ${event.details}` : event.summary;
37809
37824
  return this.renderer.render(this.subagents.legacyCollaborationCompleted(event.item));
37810
37825
  case "agentMessage":
37811
37826
  this.rememberAgentMessagePhase(event.item, event.threadId);
37812
- return this.flushAgentMessageText(event.item.id, event.threadId);
37827
+ return this.flushAgentMessageText(event.item, event.threadId);
37813
37828
  case "plan":
37814
37829
  return await this.createCompletedPlanEvent(event.item);
37815
37830
  case "exitedReviewMode":
@@ -38989,6 +39004,119 @@ function userInputResponseValue(content, fieldId) {
38989
39004
  }
38990
39005
  return value;
38991
39006
  }
39007
+ function buildUserInputSchema(questions, clientCapabilities, answersRequired) {
39008
+ const properties = {};
39009
+ const required2 = [];
39010
+ const questionIds = new Set(questions.map((question) => question.id));
39011
+ const airClient = isAirClient(clientCapabilities);
39012
+ for (const question of questions) {
39013
+ const options = question.options ?? [];
39014
+ const hasOptions = options.length > 0;
39015
+ const hasOtherAnswer = question.isOther && hasOptions;
39016
+ const base = {
39017
+ title: question.question || question.header || question.id,
39018
+ ...question.header ? { description: question.header } : {},
39019
+ _meta: {
39020
+ codex: {
39021
+ isOther: question.isOther,
39022
+ isSecret: question.isSecret
39023
+ }
39024
+ }
39025
+ };
39026
+ if (answersRequired) {
39027
+ required2.push(question.id);
39028
+ }
39029
+ properties[question.id] = hasOptions ? {
39030
+ ...base,
39031
+ type: "string",
39032
+ oneOf: [
39033
+ ...options.map((option) => ({
39034
+ const: option.label,
39035
+ title: option.label,
39036
+ ...option.description ? { description: option.description } : {}
39037
+ })),
39038
+ ...hasOtherAnswer && !airClient && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
39039
+ const: USER_INPUT_OTHER_OPTION,
39040
+ title: USER_INPUT_OTHER_OPTION,
39041
+ description: "Provide a different answer in the note field."
39042
+ }] : []
39043
+ ]
39044
+ } : {
39045
+ ...base,
39046
+ type: "string"
39047
+ };
39048
+ if (hasOtherAnswer) {
39049
+ const noteMeta = {
39050
+ codex: {
39051
+ questionId: question.id,
39052
+ role: "user_note",
39053
+ isSecret: question.isSecret
39054
+ }
39055
+ };
39056
+ properties[userInputNoteFieldId(question.id, questionIds)] = {
39057
+ type: "string",
39058
+ title: "Additional answer or note",
39059
+ _meta: airClient ? withAirMeta({ ...noteMeta, [LEGACY_AIR_CUSTOM_ANSWER_KEY]: true }, AIR_CUSTOM_ANSWER_KEY, true) : noteMeta
39060
+ };
39061
+ }
39062
+ }
39063
+ return {
39064
+ type: "object",
39065
+ properties,
39066
+ ...answersRequired ? { required: required2 } : {}
39067
+ };
39068
+ }
39069
+ function buildUserInputRequest(params, clientCapabilities) {
39070
+ return {
39071
+ sessionId: params.threadId,
39072
+ toolCallId: params.itemId,
39073
+ mode: "form",
39074
+ message: "Codex needs your input to continue.",
39075
+ requestedSchema: buildUserInputSchema(params.questions, clientCapabilities, true),
39076
+ _meta: {
39077
+ codex: {
39078
+ autoResolutionMs: params.autoResolutionMs
39079
+ }
39080
+ }
39081
+ };
39082
+ }
39083
+ function userInputAnswers(response, questions, clientCapabilities) {
39084
+ if (!CreateElicitationResponse.isAccept(response)) {
39085
+ return {};
39086
+ }
39087
+ const answers = {};
39088
+ const content = contentRecord(response.content);
39089
+ const questionIds = new Set(questions.map((question) => question.id));
39090
+ const airClient = isAirClient(clientCapabilities);
39091
+ for (const question of questions) {
39092
+ const answerValues = [];
39093
+ const hasOtherAnswer = question.isOther && question.options != null && question.options.length > 0;
39094
+ const value = userInputResponseValue(content, question.id);
39095
+ const typedAnswer = airClient && hasOtherAnswer ? typedChoiceAnswer(value, question) : void 0;
39096
+ if (typedAnswer !== void 0) {
39097
+ answerValues.push(USER_INPUT_OTHER_OPTION, `${USER_INPUT_NOTE_PREFIX}${typedAnswer.trim()}`);
39098
+ } else if (value !== void 0) {
39099
+ answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
39100
+ }
39101
+ if (hasOtherAnswer) {
39102
+ const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
39103
+ if (note !== void 0) {
39104
+ const notes = Array.isArray(note) ? note : [note];
39105
+ answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
39106
+ }
39107
+ }
39108
+ if (answerValues.length === 0) {
39109
+ continue;
39110
+ }
39111
+ answers[question.id] = {
39112
+ answers: answerValues
39113
+ };
39114
+ }
39115
+ return answers;
39116
+ }
39117
+ function userInputAnswerText(answers) {
39118
+ return answers.filter((answer) => answer !== USER_INPUT_OTHER_OPTION).map((answer) => answer.startsWith(USER_INPUT_NOTE_PREFIX) ? answer.slice(USER_INPUT_NOTE_PREFIX.length) : answer).filter((answer) => answer.trim().length > 0).join("\n\n");
39119
+ }
38992
39120
  var CodexElicitationHandler = class {
38993
39121
  connection;
38994
39122
  renderer;
@@ -39097,7 +39225,7 @@ var CodexElicitationHandler = class {
39097
39225
  if (response === null) {
39098
39226
  return { answers: {} };
39099
39227
  }
39100
- return this.convertUserInputResponse(response, params);
39228
+ return { answers: userInputAnswers(response, params.questions, this.clientCapabilities) };
39101
39229
  } catch (error62) {
39102
39230
  logger.error("Error handling Codex user input request", error62);
39103
39231
  return { answers: {} };
@@ -39107,7 +39235,7 @@ var CodexElicitationHandler = class {
39107
39235
  return cancellationSignal ? { cancellationSignal } : void 0;
39108
39236
  }
39109
39237
  async requestUserInputElicitation(params) {
39110
- const request = this.buildUserInputRequest(params);
39238
+ const request = buildUserInputRequest(params, this.clientCapabilities);
39111
39239
  const signal = subagentInteractionSignal(params.threadId, this.cancellationSignal);
39112
39240
  if (params.autoResolutionMs === null) {
39113
39241
  return await withSubagentUserAction(params.threadId, () => this.connection.request(
@@ -39207,77 +39335,6 @@ var CodexElicitationHandler = class {
39207
39335
  };
39208
39336
  }
39209
39337
  }
39210
- buildUserInputRequest(params) {
39211
- const properties = {};
39212
- const required2 = [];
39213
- const questionIds = new Set(params.questions.map((question) => question.id));
39214
- const airClient = isAirClient(this.clientCapabilities);
39215
- for (const question of params.questions) {
39216
- const options = question.options ?? [];
39217
- const hasOptions = options.length > 0;
39218
- const hasOtherAnswer = question.isOther && hasOptions;
39219
- const base = {
39220
- title: question.question || question.header || question.id,
39221
- ...question.header ? { description: question.header } : {},
39222
- _meta: {
39223
- codex: {
39224
- isOther: question.isOther,
39225
- isSecret: question.isSecret
39226
- }
39227
- }
39228
- };
39229
- required2.push(question.id);
39230
- properties[question.id] = hasOptions ? {
39231
- ...base,
39232
- type: "string",
39233
- oneOf: [
39234
- ...options.map((option) => ({
39235
- const: option.label,
39236
- title: option.label,
39237
- ...option.description ? { description: option.description } : {}
39238
- })),
39239
- ...hasOtherAnswer && !airClient && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
39240
- const: USER_INPUT_OTHER_OPTION,
39241
- title: USER_INPUT_OTHER_OPTION,
39242
- description: "Provide a different answer in the note field."
39243
- }] : []
39244
- ]
39245
- } : {
39246
- ...base,
39247
- type: "string"
39248
- };
39249
- if (hasOtherAnswer) {
39250
- const noteMeta = {
39251
- codex: {
39252
- questionId: question.id,
39253
- role: "user_note",
39254
- isSecret: question.isSecret
39255
- }
39256
- };
39257
- properties[userInputNoteFieldId(question.id, questionIds)] = {
39258
- type: "string",
39259
- title: "Additional answer or note",
39260
- _meta: airClient ? withAirMeta({ ...noteMeta, [LEGACY_AIR_CUSTOM_ANSWER_KEY]: true }, AIR_CUSTOM_ANSWER_KEY, true) : noteMeta
39261
- };
39262
- }
39263
- }
39264
- return {
39265
- sessionId: params.threadId,
39266
- toolCallId: params.itemId,
39267
- mode: "form",
39268
- message: "Codex needs your input to continue.",
39269
- requestedSchema: {
39270
- type: "object",
39271
- properties,
39272
- required: required2
39273
- },
39274
- _meta: {
39275
- codex: {
39276
- autoResolutionMs: params.autoResolutionMs
39277
- }
39278
- }
39279
- };
39280
- }
39281
39338
  convertElicitationResponse(response, context) {
39282
39339
  if (CreateElicitationResponse.isAccept(response)) {
39283
39340
  const content = contentRecord(response.content);
@@ -39308,40 +39365,6 @@ var CodexElicitationHandler = class {
39308
39365
  isAllowedToolApprovalPersist(persist, persistOptions) {
39309
39366
  return persist === void 0 || persist === "once" || persist === "session" && persistOptions.has("session") || persist === "always" && persistOptions.has("always");
39310
39367
  }
39311
- convertUserInputResponse(response, params) {
39312
- if (!CreateElicitationResponse.isAccept(response)) {
39313
- return { answers: {} };
39314
- }
39315
- const answers = {};
39316
- const content = contentRecord(response.content);
39317
- const questionIds = new Set(params.questions.map((question) => question.id));
39318
- const airClient = isAirClient(this.clientCapabilities);
39319
- for (const question of params.questions) {
39320
- const answerValues = [];
39321
- const hasOtherAnswer = question.isOther && question.options != null && question.options.length > 0;
39322
- const value = userInputResponseValue(content, question.id);
39323
- const typedAnswer = airClient && hasOtherAnswer ? typedChoiceAnswer(value, question) : void 0;
39324
- if (typedAnswer !== void 0) {
39325
- answerValues.push(USER_INPUT_OTHER_OPTION, `${USER_INPUT_NOTE_PREFIX}${typedAnswer.trim()}`);
39326
- } else if (value !== void 0) {
39327
- answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
39328
- }
39329
- if (hasOtherAnswer) {
39330
- const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
39331
- if (note !== void 0) {
39332
- const notes = Array.isArray(note) ? note : [note];
39333
- answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
39334
- }
39335
- }
39336
- if (answerValues.length === 0) {
39337
- continue;
39338
- }
39339
- answers[question.id] = {
39340
- answers: answerValues
39341
- };
39342
- }
39343
- return { answers };
39344
- }
39345
39368
  async publishAcceptedMcpToolApproval(sessionId, context, accepted) {
39346
39369
  if (!accepted || context.correlatedCallId === void 0) {
39347
39370
  return;
@@ -40588,7 +40611,7 @@ var package_default = {
40588
40611
  publishConfig: {
40589
40612
  access: "public"
40590
40613
  },
40591
- version: "2.5.0-alas.1",
40614
+ version: "2.5.0-alas.3",
40592
40615
  description: "",
40593
40616
  main: "dist/index.js",
40594
40617
  bin: {
@@ -40666,7 +40689,7 @@ var package_default = {
40666
40689
  alasDownstream: {
40667
40690
  upstreamVersion: "2.5.0",
40668
40691
  upstreamCommit: "34e08970aa324a2be985f5080674d5aa91d7666f",
40669
- sourceCommit: "845c14e611fff5b534ecd38847d0b3dc1b69d0e6"
40692
+ sourceCommit: "de8d4c961871fc0584ca1bd89f31a8de95066c65"
40670
40693
  }
40671
40694
  };
40672
40695
 
@@ -40896,9 +40919,31 @@ var CodexSubagentSubscriptions = class {
40896
40919
 
40897
40920
  // src/SessionFork.ts
40898
40921
  import { createHash } from "node:crypto";
40922
+ var NEUTRAL_FORK_POINT_META_KEY = "forkPoint";
40923
+ var NEUTRAL_FORK_POINT_VERSION = 1;
40924
+ function neutralForkPointCapability() {
40925
+ return { version: NEUTRAL_FORK_POINT_VERSION, boundary: "turn" };
40926
+ }
40927
+ function clientRequestsNeutralForkPoint(capabilities) {
40928
+ const declared = capabilities?._meta?.[NEUTRAL_FORK_POINT_META_KEY];
40929
+ if (!isUnknownRecord(declared)) return false;
40930
+ const version2 = declared["version"];
40931
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= 1;
40932
+ }
40933
+ function hasNeutralForkPoint(meta3) {
40934
+ return meta3 != null && meta3[NEUTRAL_FORK_POINT_META_KEY] !== void 0;
40935
+ }
40936
+ function forkPointNotNegotiatedError() {
40937
+ return RequestError.invalidParams(
40938
+ { [NEUTRAL_FORK_POINT_META_KEY]: { reason: "notNegotiated" } },
40939
+ "session/fork _meta.forkPoint needs initialize clientCapabilities._meta.forkPoint"
40940
+ );
40941
+ }
40899
40942
  async function forkSession(request, additionalDirectories, dependencies, instructions) {
40943
+ const forkPoint = readForkPoint(request._meta);
40900
40944
  await dependencies.refreshSkills(request.cwd, additionalDirectories);
40901
- const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
40945
+ const match = forkPoint ? await resolveForkPoint(request, forkPoint, dependencies.codexClient) : void 0;
40946
+ const lastTurnId = match?.turnId;
40902
40947
  const sessionConfig = await dependencies.createSessionConfig(
40903
40948
  request.cwd,
40904
40949
  additionalDirectories,
@@ -40930,26 +40975,83 @@ async function forkSession(request, additionalDirectories, dependencies, instruc
40930
40975
  currentServiceTier: response.serviceTier ?? null,
40931
40976
  additionalDirectories,
40932
40977
  skippedMcpServers: sessionConfig.skippedMcpServers,
40933
- ...instructions ? { instructions } : {}
40978
+ ...instructions ? { instructions } : {},
40979
+ ...forkPoint?.kind === "neutral" && match ? {
40980
+ forkPoint: {
40981
+ boundary: "turn",
40982
+ messageId: match.messageId,
40983
+ turnId: match.turnId,
40984
+ lastItemId: match.lastItemId,
40985
+ matchedBy: match.matchedBy
40986
+ }
40987
+ } : {}
40934
40988
  };
40935
40989
  }
40936
- async function resolveForkTurnId(request, codexClient) {
40937
- const forkPoint = readAirForkPoint(request._meta);
40938
- if (!forkPoint) return void 0;
40990
+ function readForkPoint(meta3) {
40991
+ if (hasNeutralForkPoint(meta3)) return { kind: "neutral", ...readNeutralForkPoint(meta3[NEUTRAL_FORK_POINT_META_KEY]) };
40992
+ const air = readAirForkPoint(meta3);
40993
+ return air ? { kind: "air", ...air } : void 0;
40994
+ }
40995
+ async function resolveForkPoint(request, forkPoint, codexClient) {
40939
40996
  const history = await codexClient.threadReadWithHistory(request.sessionId);
40940
- const candidateIds = airForkMessageIdCandidates(forkPoint.messageId);
40941
- const itemTurnId = candidateIds.map((candidateId) => history.thread.turns.find((turn) => turn.items.some((item) => item.id === candidateId))?.id).find((turnId) => turnId !== void 0);
40942
- if (itemTurnId) return itemTurnId;
40997
+ const turns = history.thread.turns;
40998
+ const matchById = (candidateId) => {
40999
+ const turn = turns.find((turn2) => turn2.items.some((item) => item.id === candidateId));
41000
+ return turn ? toMatch(turn, candidateId, "id") : void 0;
41001
+ };
41002
+ const candidateIds = forkPoint.kind === "air" ? airForkMessageIdCandidates(forkPoint.messageId) : [forkPoint.messageId];
41003
+ const idMatch = candidateIds.map(matchById).find((match) => match !== void 0);
41004
+ if (idMatch) return idMatch;
40943
41005
  if (forkPoint.messageFingerprint) {
40944
- const matchingTurns = history.thread.turns.flatMap((turn) => turn.items.filter((item) => item.type === "agentMessage" && fingerprintAgentMessage(item.text) === forkPoint.messageFingerprint).map(() => turn.id));
40945
- const fingerprintTurnId = matchingTurns[forkPoint.messageOccurrence - 1];
40946
- if (fingerprintTurnId) return fingerprintTurnId;
41006
+ const renderCwd = history.thread.cwd ?? request.cwd;
41007
+ const clientText = forkPoint.kind === "neutral" ? (text) => renderFileCitations(text, renderCwd) : (text) => text;
41008
+ const matches = turns.flatMap((turn) => turn.items.filter((item) => item.type === "agentMessage" && fingerprintAgentMessage(clientText(item.text)) === forkPoint.messageFingerprint).map((item) => toMatch(turn, item.id, "fingerprint")));
41009
+ const fingerprintMatch = matches[forkPoint.messageOccurrence - 1];
41010
+ if (fingerprintMatch) return fingerprintMatch;
41011
+ }
41012
+ if (forkPoint.kind === "neutral") {
41013
+ throw RequestError.invalidParams(
41014
+ { [NEUTRAL_FORK_POINT_META_KEY]: { messageId: forkPoint.messageId, reason: "notFound" } },
41015
+ `Fork point message ${forkPoint.messageId} was not found in session ${request.sessionId}`
41016
+ );
40947
41017
  }
40948
41018
  throw RequestError.invalidParams(
40949
41019
  { messageId: forkPoint.messageId },
40950
41020
  `Fork point message ${forkPoint.messageId} was not found in session ${request.sessionId}`
40951
41021
  );
40952
41022
  }
41023
+ function toMatch(turn, messageId, matchedBy) {
41024
+ return { messageId, turnId: turn.id, lastItemId: turn.items.at(-1).id, matchedBy };
41025
+ }
41026
+ function readNeutralForkPoint(forkPoint) {
41027
+ const invalid = (field, message) => RequestError.invalidParams(
41028
+ { [NEUTRAL_FORK_POINT_META_KEY]: { field } },
41029
+ `session/fork _meta.forkPoint: ${message}`
41030
+ );
41031
+ if (!isUnknownRecord(forkPoint)) throw invalid(NEUTRAL_FORK_POINT_META_KEY, "must be an object");
41032
+ const version2 = forkPoint["version"];
41033
+ if (version2 !== void 0 && version2 !== NEUTRAL_FORK_POINT_VERSION) {
41034
+ throw invalid("version", `version must be ${NEUTRAL_FORK_POINT_VERSION} when present`);
41035
+ }
41036
+ const messageId = forkPoint["messageId"];
41037
+ if (typeof messageId !== "string" || messageId.trim().length === 0) {
41038
+ throw invalid("messageId", "messageId must be a non-empty string");
41039
+ }
41040
+ const messageFingerprint = forkPoint["messageFingerprint"];
41041
+ if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !SHA256_FINGERPRINT.test(messageFingerprint))) {
41042
+ throw invalid("messageFingerprint", "messageFingerprint must be sha256: and 64 lowercase hex digits");
41043
+ }
41044
+ const messageOccurrence = forkPoint["messageOccurrence"] ?? 1;
41045
+ if (!Number.isSafeInteger(messageOccurrence) || messageOccurrence < 1) {
41046
+ throw invalid("messageOccurrence", "messageOccurrence must be a positive integer");
41047
+ }
41048
+ return {
41049
+ messageId: messageId.trim(),
41050
+ ...typeof messageFingerprint === "string" && { messageFingerprint },
41051
+ messageOccurrence
41052
+ };
41053
+ }
41054
+ var SHA256_FINGERPRINT = /^sha256:[0-9a-f]{64}$/;
40953
41055
  function readAirForkPoint(meta3) {
40954
41056
  const jetbrains = meta3?.["jetbrains"];
40955
41057
  if (!isUnknownRecord(jetbrains)) return void 0;
@@ -40962,7 +41064,7 @@ function readAirForkPoint(meta3) {
40962
41064
  throw RequestError.invalidParams(void 0, "AIR fork messageId must be a non-empty string");
40963
41065
  }
40964
41066
  const messageFingerprint = fork["messageFingerprint"];
40965
- if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(messageFingerprint))) {
41067
+ if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !SHA256_FINGERPRINT.test(messageFingerprint))) {
40966
41068
  throw RequestError.invalidParams(void 0, "AIR fork messageFingerprint must be a SHA-256 fingerprint");
40967
41069
  }
40968
41070
  const messageOccurrence = fork["messageOccurrence"] ?? 1;
@@ -48951,6 +49053,198 @@ function isJetBrains2026_1Client(clientInfo) {
48951
49053
  return (isIntelliJPlatform || isJetBrainsClient) && clientInfo.version.startsWith("2026.1");
48952
49054
  }
48953
49055
 
49056
+ // src/CodexAsyncQuestions.ts
49057
+ import { randomUUID as randomUUID5 } from "node:crypto";
49058
+ var TOOL_NAME = "request_user_input_async";
49059
+ var REPLY_START = "<send_user_message_question_reply>";
49060
+ var REPLY_END = "</send_user_message_question_reply>";
49061
+ var MAX_REPLY_FIELD_LENGTH = 512;
49062
+ function asyncQuestionItemId(messageId, index) {
49063
+ return JSON.stringify([TOOL_NAME, messageId, index]);
49064
+ }
49065
+ function formatAsyncQuestionReply(replies) {
49066
+ const bounded = replies.map((reply) => ({
49067
+ ...reply,
49068
+ question: [...reply.question].slice(0, MAX_REPLY_FIELD_LENGTH).join("").replace(/[\r\n]/g, " ")
49069
+ }));
49070
+ if (bounded.some((reply) => reply.questionItemId.length > MAX_REPLY_FIELD_LENGTH)) {
49071
+ return displayReplies(bounded);
49072
+ }
49073
+ const body = bounded.map(({ answer, question, questionItemId }) => ({ answer, question, questionItemId }));
49074
+ return `${REPLY_START}
49075
+ ${JSON.stringify(body)}
49076
+ ${REPLY_END}`;
49077
+ }
49078
+ function parseAsyncQuestionReply(text) {
49079
+ const trimmed = text.trim();
49080
+ if (!trimmed.startsWith(REPLY_START) || !trimmed.endsWith(REPLY_END)) return null;
49081
+ let body;
49082
+ try {
49083
+ body = JSON.parse(trimmed.slice(REPLY_START.length, trimmed.length - REPLY_END.length));
49084
+ } catch {
49085
+ return null;
49086
+ }
49087
+ const replies = Array.isArray(body) ? body : [body];
49088
+ if (replies.length === 0 || !replies.every(isReply)) return null;
49089
+ return replies.map(({ questionItemId, question, answer }) => ({ questionItemId, question, answer }));
49090
+ }
49091
+ function renderAsyncQuestionReply(text) {
49092
+ const replies = parseAsyncQuestionReply(text);
49093
+ return replies === null ? null : displayReplies(replies);
49094
+ }
49095
+ function displayReplies(replies) {
49096
+ return replies.map((reply) => `> ${reply.question}
49097
+
49098
+ ${reply.answer}`).join("\n\n");
49099
+ }
49100
+ function isReply(value) {
49101
+ if (typeof value !== "object" || value === null) return false;
49102
+ const reply = value;
49103
+ return typeof reply["questionItemId"] === "string" && typeof reply["question"] === "string" && typeof reply["answer"] === "string";
49104
+ }
49105
+ var AsyncQuestionClient = class {
49106
+ /**
49107
+ * @param sessionId The session that the questions belong to.
49108
+ * @param connection The connection to the ACP client.
49109
+ * @param clientCapabilities The capabilities that the ACP client declared.
49110
+ * @param codexAcpClient Returns the current Codex client, which changes when the app-server restarts.
49111
+ */
49112
+ constructor(sessionId, connection, clientCapabilities, codexAcpClient) {
49113
+ this.sessionId = sessionId;
49114
+ this.connection = connection;
49115
+ this.clientCapabilities = clientCapabilities;
49116
+ this.codexAcpClient = codexAcpClient;
49117
+ }
49118
+ sessionId;
49119
+ connection;
49120
+ clientCapabilities;
49121
+ codexAcpClient;
49122
+ /** Sends the elicitation without marking the session as waiting for the user. */
49123
+ createElicitation(request, signal) {
49124
+ const v2 = AcpV2Connection.of(this.connection);
49125
+ return v2 !== null ? v2.createElicitation(request, { cancellationSignal: signal }, { userAction: false }) : this.connection.request(methods.client.elicitation.create, request, { cancellationSignal: signal });
49126
+ }
49127
+ /** Steers the text into the turn; rejects if that turn is no longer running. */
49128
+ async steer(turnId, text) {
49129
+ await this.codexAcpClient().steerTurn({
49130
+ threadId: this.sessionId,
49131
+ turnId,
49132
+ prompt: [{ type: "text", text }],
49133
+ clientUserMessageId: randomUUID5()
49134
+ });
49135
+ }
49136
+ /** Tells the user that an answer was not sent, if the client supports notices. */
49137
+ async reportDroppedAnswer() {
49138
+ if (!clientSupportsNotices(this.clientCapabilities)) return;
49139
+ await this.connection.notify(methods.client.session.update, {
49140
+ sessionId: this.sessionId,
49141
+ update: createSessionNotice(
49142
+ "warning",
49143
+ "Answer not sent",
49144
+ "Codex finished before your answer arrived. Send it as a new message if it still matters."
49145
+ )
49146
+ });
49147
+ }
49148
+ };
49149
+ var AsyncQuestionElicitations = class {
49150
+ constructor(sessionId, clientCapabilities, client) {
49151
+ this.sessionId = sessionId;
49152
+ this.clientCapabilities = clientCapabilities;
49153
+ this.client = client;
49154
+ }
49155
+ sessionId;
49156
+ clientCapabilities;
49157
+ client;
49158
+ // The running turns that asked a question, each with the signal that cancels its questions.
49159
+ turns = /* @__PURE__ */ new Map();
49160
+ /** Asks the questions of a completed async message and cancels them when their turn ends; never waits. */
49161
+ handleNotification(notification) {
49162
+ switch (notification.method) {
49163
+ case "item/completed": {
49164
+ const { threadId, turnId, item } = notification.params;
49165
+ if (threadId === this.sessionId && item.type === "agentMessage" && item.delivery === "async" && (item.questions?.length ?? 0) > 0) {
49166
+ this.ask(turnId, item);
49167
+ }
49168
+ return;
49169
+ }
49170
+ case "turn/completed":
49171
+ if (notification.params.threadId === this.sessionId) {
49172
+ this.endTurn(notification.params.turn.id);
49173
+ }
49174
+ return;
49175
+ default:
49176
+ return;
49177
+ }
49178
+ }
49179
+ /** Cancels every open question, when the session closes or its app-server is gone. */
49180
+ dispose() {
49181
+ for (const turnId of [...this.turns.keys()]) {
49182
+ this.endTurn(turnId);
49183
+ }
49184
+ }
49185
+ endTurn(turnId) {
49186
+ this.turns.get(turnId)?.abort();
49187
+ this.turns.delete(turnId);
49188
+ }
49189
+ ask(turnId, message) {
49190
+ if (!clientSupportsFormElicitation(this.clientCapabilities)) return;
49191
+ let turn = this.turns.get(turnId);
49192
+ if (turn === void 0) {
49193
+ turn = new AbortController();
49194
+ this.turns.set(turnId, turn);
49195
+ }
49196
+ const questions = formQuestions(message);
49197
+ const request = {
49198
+ sessionId: this.sessionId,
49199
+ mode: "form",
49200
+ message: "Codex keeps working while you answer. An answer sent after it finishes is not delivered.",
49201
+ // Questions are answered independently, as in Codex.
49202
+ requestedSchema: buildUserInputSchema(questions, this.clientCapabilities, false),
49203
+ _meta: { codex: { delivery: "async" } }
49204
+ };
49205
+ void this.client.createElicitation(request, turn.signal).then(
49206
+ (response) => this.answer(turnId, message, questions, response).catch((error62) => logger.error("Failed to deliver an async question answer", error62)),
49207
+ (error62) => logger.log("Async question closed without an answer", { sessionId: this.sessionId, turnId, error: error62 })
49208
+ );
49209
+ }
49210
+ async answer(turnId, message, questions, response) {
49211
+ const answers = userInputAnswers(response, questions, this.clientCapabilities);
49212
+ const replies = questions.flatMap((question, index) => {
49213
+ const answer = userInputAnswerText(answers[question.id]?.answers ?? []);
49214
+ return answer.length === 0 ? [] : [{
49215
+ questionItemId: asyncQuestionItemId(message.id, index),
49216
+ question: question.question,
49217
+ answer
49218
+ }];
49219
+ });
49220
+ if (replies.length === 0) return;
49221
+ if (this.turns.has(turnId)) {
49222
+ try {
49223
+ await this.client.steer(turnId, formatAsyncQuestionReply(replies));
49224
+ return;
49225
+ } catch (error62) {
49226
+ logger.log("Async question answer was not steered into its turn", { sessionId: this.sessionId, turnId, error: error62 });
49227
+ }
49228
+ }
49229
+ logger.log("Dropped an async question answer: its turn has ended", { sessionId: this.sessionId, turnId });
49230
+ await this.client.reportDroppedAnswer();
49231
+ }
49232
+ };
49233
+ function formQuestions(message) {
49234
+ return (message.questions ?? []).map((question, index) => {
49235
+ const options = question.options ?? [];
49236
+ return {
49237
+ id: `question_${index}`,
49238
+ header: "",
49239
+ question: question.title,
49240
+ // Codex always accepts an own answer.
49241
+ isOther: options.length > 0,
49242
+ isSecret: false,
49243
+ options: options.length > 0 ? options.map((label) => ({ label, description: "" })) : null
49244
+ };
49245
+ });
49246
+ }
49247
+
48954
49248
  // src/subagents/AcpSubagents.ts
48955
49249
  function subagentProtocol(capabilities, version2 = 1) {
48956
49250
  const subagents = capabilities?.subagents;
@@ -48979,7 +49273,7 @@ var SubagentInteractionHandlers = class {
48979
49273
  };
48980
49274
 
48981
49275
  // src/CodexAcpServer.ts
48982
- import { randomUUID as randomUUID6 } from "node:crypto";
49276
+ import { randomUUID as randomUUID7 } from "node:crypto";
48983
49277
 
48984
49278
  // src/TitleGenerator.ts
48985
49279
  var TITLE_MODEL = "gpt-5.6-luna";
@@ -50067,7 +50361,7 @@ function remember(cache, key, value) {
50067
50361
  }
50068
50362
 
50069
50363
  // src/SessionListSubscriptions.ts
50070
- import { randomUUID as randomUUID5 } from "node:crypto";
50364
+ import { randomUUID as randomUUID6 } from "node:crypto";
50071
50365
  import path14 from "node:path";
50072
50366
 
50073
50367
  // src/CodexHomeWatcher.ts
@@ -50475,7 +50769,7 @@ var SessionListSubscriptions = class {
50475
50769
  this.startWatching();
50476
50770
  created.ready = this.makeReady(created);
50477
50771
  }
50478
- const subscription = { id: randomUUID5(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
50772
+ const subscription = { id: randomUUID6(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
50479
50773
  this.subscriptions.set(subscription.id, subscription);
50480
50774
  group.subscriptions.add(subscription);
50481
50775
  this.startWatching();
@@ -51846,6 +52140,8 @@ var CodexAcpServer = class {
51846
52140
  /** The capability choices of the client for tool call and plan reports. */
51847
52141
  capabilities;
51848
52142
  booleanConfigOptionsSupported;
52143
+ /** Whether the client opted into the client-neutral `session/fork` `_meta.forkPoint`. */
52144
+ neutralForkPointSupported = false;
51849
52145
  /** Last `authStatus` pushed to the client; used to suppress duplicates. */
51850
52146
  currentAuthStatus;
51851
52147
  /**
@@ -51867,6 +52163,8 @@ var CodexAcpServer = class {
51867
52163
  /** Tail of the per-session turn-start FIFO; see `acquireTurnStartReservation`. */
51868
52164
  turnStartQueueTail;
51869
52165
  steeringQueues;
52166
+ /** The async questions of each session, shown while the turn that asked them runs. */
52167
+ asyncQuestions = /* @__PURE__ */ new Map();
51870
52168
  /**
51871
52169
  * Steers awaiting their `userMessage` landing, keyed by the minted `clientUserMessageId`
51872
52170
  * passed to `turn/steer`. Used only to show a v2 live `user_message` when a steer injected
@@ -51959,7 +52257,7 @@ var CodexAcpServer = class {
51959
52257
  this.captureStderr();
51960
52258
  this.getExitCode = getExitCode ?? (() => this.codexProcessState?.connection.process.exitCode ?? null);
51961
52259
  this.getRecentStderr = getRecentStderr ?? (() => this.codexProcessState?.stderr ?? "");
51962
- this.sessionFailureEpoch = randomUUID6();
52260
+ this.sessionFailureEpoch = randomUUID7();
51963
52261
  this.clientInfo = null;
51964
52262
  this.clientCapabilities = null;
51965
52263
  this.capabilities = ClientCapabilities.DEFAULT;
@@ -52106,6 +52404,7 @@ var CodexAcpServer = class {
52106
52404
  if (appServer) await appServer;
52107
52405
  this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
52108
52406
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
52407
+ this.neutralForkPointSupported = clientRequestsNeutralForkPoint(_params.clientCapabilities);
52109
52408
  this.sessionIndex.configure(_params.clientCapabilities);
52110
52409
  this.hookReview.configure(_params.clientCapabilities?._meta?.["codexHooks"], this.capabilities.airClient);
52111
52410
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
@@ -52165,6 +52464,7 @@ var CodexAcpServer = class {
52165
52464
  if (appServer) await appServer;
52166
52465
  this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
52167
52466
  this.booleanConfigOptionsSupported = true;
52467
+ this.neutralForkPointSupported = clientRequestsNeutralForkPoint(clientCapabilities);
52168
52468
  this.sessionIndex.configure(clientCapabilities, { neutral: false });
52169
52469
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize({ clientInfo: params.info }));
52170
52470
  this.sessionIndex.observe(this.codexAcpClient);
@@ -52249,6 +52549,10 @@ var CodexAcpServer = class {
52249
52549
  "async-tasks": asyncTaskCapability()
52250
52550
  } : {},
52251
52551
  ...this.hookReview.enabled ? { codexHooks: { version: 1, reviewToken: true } } : {},
52552
+ // The client-neutral fork point, see `docs/air-extensions.md`.
52553
+ ...this.neutralForkPointSupported ? {
52554
+ [NEUTRAL_FORK_POINT_META_KEY]: neutralForkPointCapability()
52555
+ } : {},
52252
52556
  // The session index for a client that opted in without AIR, see `docs/air-extensions.md`.
52253
52557
  ...neutralSessionIndex ? {
52254
52558
  [NEUTRAL_SESSION_INDEX_META_KEY]: neutralSessionIndex
@@ -52542,7 +52846,7 @@ You have been logged out. Please try again.`);
52542
52846
  this.sessionGenerations.set(sessionId, generation);
52543
52847
  return generation;
52544
52848
  }
52545
- async tryCreateSession(request, operation = "sessionId" in request ? "resume" : "new") {
52849
+ async tryCreateSession(request, operation = "sessionId" in request ? "resume" : "new", onForked) {
52546
52850
  clientSystemPromptText(request.systemPrompt);
52547
52851
  const existingSessionRequest = request;
52548
52852
  const previousInstructions = "sessionId" in request ? this.sessions.get(request.sessionId)?.instructions : void 0;
@@ -52593,6 +52897,7 @@ You have been logged out. Please try again.`);
52593
52897
  previousInstructions
52594
52898
  );
52595
52899
  });
52900
+ onForked?.(sessionMetadata);
52596
52901
  } else {
52597
52902
  logger.log(`Create new session...`);
52598
52903
  sessionMetadata = await this.runWithProcessCheck(() => {
@@ -53161,6 +53466,9 @@ You have been logged out. Please try again.`);
53161
53466
  async forkSession(params) {
53162
53467
  params = this.withV1SessionInstructionAppend(params);
53163
53468
  clientSystemPromptText(params.systemPrompt);
53469
+ if (hasNeutralForkPoint(params._meta) && !this.neutralForkPointSupported) {
53470
+ throw forkPointNotNegotiatedError();
53471
+ }
53164
53472
  this.assertInstructionsReady(params.sessionId);
53165
53473
  return this.hookReview.run(
53166
53474
  params.cwd,
@@ -53176,12 +53484,16 @@ You have been logged out. Please try again.`);
53176
53484
  if (appServer) await appServer;
53177
53485
  logger.log("Forking session...", { sessionId: params.sessionId });
53178
53486
  try {
53179
- const [sessionId, , modeState] = await this.tryCreateSession(params, "fork");
53487
+ let forkPoint;
53488
+ const [sessionId, , modeState] = await this.tryCreateSession(params, "fork", (metadata) => {
53489
+ forkPoint = metadata.forkPoint;
53490
+ });
53180
53491
  logger.log("Session forked", { sourceSessionId: params.sessionId, sessionId });
53181
53492
  return {
53182
53493
  sessionId,
53183
53494
  modes: modeState,
53184
- ...this.createSessionConfigOptionsResponse(this.getSessionState(sessionId))
53495
+ ...this.createSessionConfigOptionsResponse(this.getSessionState(sessionId)),
53496
+ ...forkPoint ? { _meta: { [NEUTRAL_FORK_POINT_META_KEY]: forkPoint } } : {}
53185
53497
  };
53186
53498
  } catch (e) {
53187
53499
  const error62 = e instanceof Error ? e : new Error(String(e));
@@ -53192,10 +53504,11 @@ You have been logged out. Please try again.`);
53192
53504
  async forkSessionV2(params) {
53193
53505
  const { systemPrompt, ...rest } = params;
53194
53506
  const request = { ...rest, systemPrompt: toV1SystemPrompt(systemPrompt) };
53195
- const { sessionId } = await this.forkSession(request);
53507
+ const { sessionId, _meta } = await this.forkSession(request);
53196
53508
  return {
53197
53509
  sessionId,
53198
- ...this.createSessionSetupResponseV2(this.getSessionState(sessionId))
53510
+ ...this.createSessionSetupResponseV2(this.getSessionState(sessionId)),
53511
+ ..._meta ? { _meta } : {}
53199
53512
  };
53200
53513
  }
53201
53514
  async listSessions(params) {
@@ -53252,6 +53565,8 @@ You have been logged out. Please try again.`);
53252
53565
  } finally {
53253
53566
  if (this.getSessionGeneration(params.sessionId) === closeGeneration) {
53254
53567
  this.sessions.delete(params.sessionId);
53568
+ this.asyncQuestions.get(params.sessionId)?.dispose();
53569
+ this.asyncQuestions.delete(params.sessionId);
53255
53570
  if (!this.v2PromptsInFlight.has(params.sessionId)) {
53256
53571
  AcpV2Connection.of(this.connection)?.forgetSession(params.sessionId);
53257
53572
  }
@@ -54028,7 +54343,7 @@ You have been logged out. Please try again.`);
54028
54343
  });
54029
54344
  const sessionState = this.getSessionState(params.sessionId);
54030
54345
  this.assertSteerInputSupported(params, sessionState);
54031
- const clientUserMessageId = randomUUID6();
54346
+ const clientUserMessageId = randomUUID7();
54032
54347
  const turnId = await this.getSteerableTurnId(sessionState);
54033
54348
  if (turnId) {
54034
54349
  const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState, clientUserMessageId);
@@ -55059,8 +55374,11 @@ You have been logged out. Please try again.`);
55059
55374
  }
55060
55375
  userInputToContentBlocks(input2) {
55061
55376
  switch (input2.type) {
55062
- case "text":
55377
+ case "text": {
55378
+ const asyncQuestionReply = renderAsyncQuestionReply(input2.text);
55379
+ if (asyncQuestionReply !== null) return [{ type: "text", text: asyncQuestionReply }];
55063
55380
  return desktopAttachmentHistory(input2.text) ?? (input2.text.length > 0 ? [{ type: "text", text: input2.text }] : []);
55381
+ }
55064
55382
  case "image":
55065
55383
  return [{
55066
55384
  type: "text",
@@ -55439,7 +55757,7 @@ You have been logged out. Please try again.`);
55439
55757
  }
55440
55758
  }
55441
55759
  const promptKind = this.availableCommands.classifyPrompt(request.prompt);
55442
- const messageId = randomUUID6();
55760
+ const messageId = randomUUID7();
55443
55761
  const session = new ACPSessionConnection(this.connection, sessionId);
55444
55762
  let failure3 = null;
55445
55763
  this.v2PromptsInFlight.add(sessionId);
@@ -55763,11 +56081,13 @@ You have been logged out. Please try again.`);
55763
56081
  await observeInteraction(event);
55764
56082
  if (!promptNotificationsActive) {
55765
56083
  await promptEventHandler.handleSessionScopedNotification(event);
56084
+ this.asyncQuestionsFor(sessionState).handleNotification(event);
55766
56085
  await this.trackCodexTurnCompletion(sessionState, event, promptEventHandler);
55767
56086
  return;
55768
56087
  }
55769
56088
  const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && event.params.turn.id === sessionState.currentTurnId;
55770
56089
  await promptEventHandler.handleNotification(event);
56090
+ this.asyncQuestionsFor(sessionState).handleNotification(event);
55771
56091
  if (completesActiveTurn) {
55772
56092
  promptNotificationsActive = false;
55773
56093
  }
@@ -56000,7 +56320,7 @@ You have been logged out. Please try again.`);
56000
56320
  activePrompt.currentTurn = null;
56001
56321
  sessionState.currentTurnId = null;
56002
56322
  sessionState.interruptTurnId = null;
56003
- const implementationClientUserMessageId = insertion !== void 0 ? randomUUID6() : void 0;
56323
+ const implementationClientUserMessageId = insertion !== void 0 ? randomUUID7() : void 0;
56004
56324
  if (implementationClientUserMessageId !== void 0) {
56005
56325
  registerSyntheticInsertion(implementationClientUserMessageId, implementationRequest.prompt);
56006
56326
  }
@@ -56211,12 +56531,28 @@ You have been logged out. Please try again.`);
56211
56531
  if (this.recovery === null || !lost.aborted || !sessionState.subagents.hasOutstanding()) return;
56212
56532
  throw this.recovery.errorForGeneration(generation);
56213
56533
  }
56534
+ /** The async questions of a session, created on first use. */
56535
+ asyncQuestionsFor(sessionState) {
56536
+ const sessionId = sessionState.sessionId;
56537
+ let questions = this.asyncQuestions.get(sessionId);
56538
+ if (questions === void 0) {
56539
+ questions = new AsyncQuestionElicitations(sessionId, this.clientCapabilities, new AsyncQuestionClient(
56540
+ sessionId,
56541
+ this.connection,
56542
+ this.clientCapabilities,
56543
+ () => this.codexAcpClient
56544
+ ));
56545
+ this.asyncQuestions.set(sessionId, questions);
56546
+ }
56547
+ return questions;
56548
+ }
56214
56549
  /**
56215
56550
  * Ends what a prompt showed as running when its app-server died: open tool calls of the session and of its native
56216
56551
  * subagent sessions, the subagent sessions themselves and compactions. Nothing will ever complete them.
56217
56552
  */
56218
56553
  async finishPromptAfterAppServerLoss(sessionState, eventHandler, promptClient, elicitations, state) {
56219
56554
  try {
56555
+ this.asyncQuestions.get(sessionState.sessionId)?.dispose();
56220
56556
  await elicitations?.completeAllUrlElicitations();
56221
56557
  const sessionIds = /* @__PURE__ */ new Set([sessionState.sessionId, ...sessionState.subagents.childSessionIds()]);
56222
56558
  await this.failUnfinishedToolCalls(sessionIds);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.5.0-alas.1",
6
+ "version": "2.5.0-alas.3",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -81,6 +81,6 @@
81
81
  "alasDownstream": {
82
82
  "upstreamVersion": "2.5.0",
83
83
  "upstreamCommit": "34e08970aa324a2be985f5080674d5aa91d7666f",
84
- "sourceCommit": "845c14e611fff5b534ecd38847d0b3dc1b69d0e6"
84
+ "sourceCommit": "de8d4c961871fc0584ca1bd89f31a8de95066c65"
85
85
  }
86
86
  }