@agentclientprotocol/codex-acp 2.5.0 → 2.5.1-preview.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.
- package/README.md +1 -0
- package/dist/index.js +365 -125
- package/package.json +1 -1
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
|
@@ -32366,8 +32366,19 @@ var AcpV2Connection = class {
|
|
|
32366
32366
|
return toV1RequestPermissionResponse(response);
|
|
32367
32367
|
}, options?.cancellationSignal);
|
|
32368
32368
|
}
|
|
32369
|
-
/**
|
|
32370
|
-
|
|
32369
|
+
/**
|
|
32370
|
+
* Sends `elicitation/create`. Request-scoped login elicitations and idle background elicitations never change
|
|
32371
|
+
* the session state.
|
|
32372
|
+
*
|
|
32373
|
+
* @param request The elicitation to show.
|
|
32374
|
+
* @param options Request options, such as the signal that cancels the request.
|
|
32375
|
+
* @param interaction.userAction Whether the agent waits for the answer, `true` by default. While such an
|
|
32376
|
+
* elicitation is open in a running turn, the session reports `requires_action` whenever none of its items
|
|
32377
|
+
* is in progress. Pass `false` for a question the agent keeps working through, so the session state is left
|
|
32378
|
+
* as it is.
|
|
32379
|
+
*/
|
|
32380
|
+
async createElicitation(request, options, interaction = {}) {
|
|
32381
|
+
const userAction = interaction.userAction ?? true;
|
|
32371
32382
|
const sendRequest = async () => {
|
|
32372
32383
|
const response = await this.client.request(
|
|
32373
32384
|
methods2.client.elicitation.create,
|
|
@@ -32377,7 +32388,7 @@ var AcpV2Connection = class {
|
|
|
32377
32388
|
return toV1CreateElicitationResponse(response);
|
|
32378
32389
|
};
|
|
32379
32390
|
const sessionId = elicitationSessionId(request);
|
|
32380
|
-
if (sessionId === void 0) {
|
|
32391
|
+
if (sessionId === void 0 || !userAction) {
|
|
32381
32392
|
return sendRequest();
|
|
32382
32393
|
}
|
|
32383
32394
|
const toolCallId = "toolCallId" in request && typeof request.toolCallId === "string" ? request.toolCallId : void 0;
|
|
@@ -37613,12 +37624,16 @@ ${params.turnId}`;
|
|
|
37613
37624
|
}
|
|
37614
37625
|
return this.createAgentMessageText(citations.push(event.delta), event.itemId, event.threadId);
|
|
37615
37626
|
}
|
|
37616
|
-
/**
|
|
37617
|
-
|
|
37627
|
+
/**
|
|
37628
|
+
* Sends the text a file citation held back, once the message is complete. A message that
|
|
37629
|
+
* arrives whole, without deltas (an async question, for one), is sent in full.
|
|
37630
|
+
*/
|
|
37631
|
+
flushAgentMessageText(item, threadId) {
|
|
37618
37632
|
const streams = this.streamsFor(threadId);
|
|
37619
|
-
const
|
|
37620
|
-
streams.agentMessageCitations.delete(
|
|
37621
|
-
|
|
37633
|
+
const citations = streams.agentMessageCitations.get(item.id);
|
|
37634
|
+
streams.agentMessageCitations.delete(item.id);
|
|
37635
|
+
const text = citations ? citations.flush() : renderFileCitations(item.text, this.sessionState.cwd);
|
|
37636
|
+
return this.createAgentMessageText(text, item.id, threadId);
|
|
37622
37637
|
}
|
|
37623
37638
|
createAgentMessageText(text, itemId, threadId) {
|
|
37624
37639
|
if (text.length === 0) return null;
|
|
@@ -37773,7 +37788,7 @@ ${event.details}` : event.summary;
|
|
|
37773
37788
|
return this.renderer.render(this.subagents.legacyCollaborationCompleted(event.item));
|
|
37774
37789
|
case "agentMessage":
|
|
37775
37790
|
this.rememberAgentMessagePhase(event.item, event.threadId);
|
|
37776
|
-
return this.flushAgentMessageText(event.item
|
|
37791
|
+
return this.flushAgentMessageText(event.item, event.threadId);
|
|
37777
37792
|
case "plan":
|
|
37778
37793
|
return await this.createCompletedPlanEvent(event.item);
|
|
37779
37794
|
case "exitedReviewMode":
|
|
@@ -38953,6 +38968,119 @@ function userInputResponseValue(content, fieldId) {
|
|
|
38953
38968
|
}
|
|
38954
38969
|
return value;
|
|
38955
38970
|
}
|
|
38971
|
+
function buildUserInputSchema(questions, clientCapabilities, answersRequired) {
|
|
38972
|
+
const properties = {};
|
|
38973
|
+
const required2 = [];
|
|
38974
|
+
const questionIds = new Set(questions.map((question) => question.id));
|
|
38975
|
+
const airClient = isAirClient(clientCapabilities);
|
|
38976
|
+
for (const question of questions) {
|
|
38977
|
+
const options = question.options ?? [];
|
|
38978
|
+
const hasOptions = options.length > 0;
|
|
38979
|
+
const hasOtherAnswer = question.isOther && hasOptions;
|
|
38980
|
+
const base = {
|
|
38981
|
+
title: question.question || question.header || question.id,
|
|
38982
|
+
...question.header ? { description: question.header } : {},
|
|
38983
|
+
_meta: {
|
|
38984
|
+
codex: {
|
|
38985
|
+
isOther: question.isOther,
|
|
38986
|
+
isSecret: question.isSecret
|
|
38987
|
+
}
|
|
38988
|
+
}
|
|
38989
|
+
};
|
|
38990
|
+
if (answersRequired) {
|
|
38991
|
+
required2.push(question.id);
|
|
38992
|
+
}
|
|
38993
|
+
properties[question.id] = hasOptions ? {
|
|
38994
|
+
...base,
|
|
38995
|
+
type: "string",
|
|
38996
|
+
oneOf: [
|
|
38997
|
+
...options.map((option) => ({
|
|
38998
|
+
const: option.label,
|
|
38999
|
+
title: option.label,
|
|
39000
|
+
...option.description ? { description: option.description } : {}
|
|
39001
|
+
})),
|
|
39002
|
+
...hasOtherAnswer && !airClient && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
|
|
39003
|
+
const: USER_INPUT_OTHER_OPTION,
|
|
39004
|
+
title: USER_INPUT_OTHER_OPTION,
|
|
39005
|
+
description: "Provide a different answer in the note field."
|
|
39006
|
+
}] : []
|
|
39007
|
+
]
|
|
39008
|
+
} : {
|
|
39009
|
+
...base,
|
|
39010
|
+
type: "string"
|
|
39011
|
+
};
|
|
39012
|
+
if (hasOtherAnswer) {
|
|
39013
|
+
const noteMeta = {
|
|
39014
|
+
codex: {
|
|
39015
|
+
questionId: question.id,
|
|
39016
|
+
role: "user_note",
|
|
39017
|
+
isSecret: question.isSecret
|
|
39018
|
+
}
|
|
39019
|
+
};
|
|
39020
|
+
properties[userInputNoteFieldId(question.id, questionIds)] = {
|
|
39021
|
+
type: "string",
|
|
39022
|
+
title: "Additional answer or note",
|
|
39023
|
+
_meta: airClient ? withAirMeta({ ...noteMeta, [LEGACY_AIR_CUSTOM_ANSWER_KEY]: true }, AIR_CUSTOM_ANSWER_KEY, true) : noteMeta
|
|
39024
|
+
};
|
|
39025
|
+
}
|
|
39026
|
+
}
|
|
39027
|
+
return {
|
|
39028
|
+
type: "object",
|
|
39029
|
+
properties,
|
|
39030
|
+
...answersRequired ? { required: required2 } : {}
|
|
39031
|
+
};
|
|
39032
|
+
}
|
|
39033
|
+
function buildUserInputRequest(params, clientCapabilities) {
|
|
39034
|
+
return {
|
|
39035
|
+
sessionId: params.threadId,
|
|
39036
|
+
toolCallId: params.itemId,
|
|
39037
|
+
mode: "form",
|
|
39038
|
+
message: "Codex needs your input to continue.",
|
|
39039
|
+
requestedSchema: buildUserInputSchema(params.questions, clientCapabilities, true),
|
|
39040
|
+
_meta: {
|
|
39041
|
+
codex: {
|
|
39042
|
+
autoResolutionMs: params.autoResolutionMs
|
|
39043
|
+
}
|
|
39044
|
+
}
|
|
39045
|
+
};
|
|
39046
|
+
}
|
|
39047
|
+
function userInputAnswers(response, questions, clientCapabilities) {
|
|
39048
|
+
if (!CreateElicitationResponse.isAccept(response)) {
|
|
39049
|
+
return {};
|
|
39050
|
+
}
|
|
39051
|
+
const answers = {};
|
|
39052
|
+
const content = contentRecord(response.content);
|
|
39053
|
+
const questionIds = new Set(questions.map((question) => question.id));
|
|
39054
|
+
const airClient = isAirClient(clientCapabilities);
|
|
39055
|
+
for (const question of questions) {
|
|
39056
|
+
const answerValues = [];
|
|
39057
|
+
const hasOtherAnswer = question.isOther && question.options != null && question.options.length > 0;
|
|
39058
|
+
const value = userInputResponseValue(content, question.id);
|
|
39059
|
+
const typedAnswer = airClient && hasOtherAnswer ? typedChoiceAnswer(value, question) : void 0;
|
|
39060
|
+
if (typedAnswer !== void 0) {
|
|
39061
|
+
answerValues.push(USER_INPUT_OTHER_OPTION, `${USER_INPUT_NOTE_PREFIX}${typedAnswer.trim()}`);
|
|
39062
|
+
} else if (value !== void 0) {
|
|
39063
|
+
answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
|
|
39064
|
+
}
|
|
39065
|
+
if (hasOtherAnswer) {
|
|
39066
|
+
const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
|
|
39067
|
+
if (note !== void 0) {
|
|
39068
|
+
const notes = Array.isArray(note) ? note : [note];
|
|
39069
|
+
answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
|
|
39070
|
+
}
|
|
39071
|
+
}
|
|
39072
|
+
if (answerValues.length === 0) {
|
|
39073
|
+
continue;
|
|
39074
|
+
}
|
|
39075
|
+
answers[question.id] = {
|
|
39076
|
+
answers: answerValues
|
|
39077
|
+
};
|
|
39078
|
+
}
|
|
39079
|
+
return answers;
|
|
39080
|
+
}
|
|
39081
|
+
function userInputAnswerText(answers) {
|
|
39082
|
+
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");
|
|
39083
|
+
}
|
|
38956
39084
|
var CodexElicitationHandler = class {
|
|
38957
39085
|
connection;
|
|
38958
39086
|
renderer;
|
|
@@ -39061,7 +39189,7 @@ var CodexElicitationHandler = class {
|
|
|
39061
39189
|
if (response === null) {
|
|
39062
39190
|
return { answers: {} };
|
|
39063
39191
|
}
|
|
39064
|
-
return
|
|
39192
|
+
return { answers: userInputAnswers(response, params.questions, this.clientCapabilities) };
|
|
39065
39193
|
} catch (error62) {
|
|
39066
39194
|
logger.error("Error handling Codex user input request", error62);
|
|
39067
39195
|
return { answers: {} };
|
|
@@ -39071,7 +39199,7 @@ var CodexElicitationHandler = class {
|
|
|
39071
39199
|
return cancellationSignal ? { cancellationSignal } : void 0;
|
|
39072
39200
|
}
|
|
39073
39201
|
async requestUserInputElicitation(params) {
|
|
39074
|
-
const request =
|
|
39202
|
+
const request = buildUserInputRequest(params, this.clientCapabilities);
|
|
39075
39203
|
const signal = subagentInteractionSignal(params.threadId, this.cancellationSignal);
|
|
39076
39204
|
if (params.autoResolutionMs === null) {
|
|
39077
39205
|
return await withSubagentUserAction(params.threadId, () => this.connection.request(
|
|
@@ -39171,77 +39299,6 @@ var CodexElicitationHandler = class {
|
|
|
39171
39299
|
};
|
|
39172
39300
|
}
|
|
39173
39301
|
}
|
|
39174
|
-
buildUserInputRequest(params) {
|
|
39175
|
-
const properties = {};
|
|
39176
|
-
const required2 = [];
|
|
39177
|
-
const questionIds = new Set(params.questions.map((question) => question.id));
|
|
39178
|
-
const airClient = isAirClient(this.clientCapabilities);
|
|
39179
|
-
for (const question of params.questions) {
|
|
39180
|
-
const options = question.options ?? [];
|
|
39181
|
-
const hasOptions = options.length > 0;
|
|
39182
|
-
const hasOtherAnswer = question.isOther && hasOptions;
|
|
39183
|
-
const base = {
|
|
39184
|
-
title: question.question || question.header || question.id,
|
|
39185
|
-
...question.header ? { description: question.header } : {},
|
|
39186
|
-
_meta: {
|
|
39187
|
-
codex: {
|
|
39188
|
-
isOther: question.isOther,
|
|
39189
|
-
isSecret: question.isSecret
|
|
39190
|
-
}
|
|
39191
|
-
}
|
|
39192
|
-
};
|
|
39193
|
-
required2.push(question.id);
|
|
39194
|
-
properties[question.id] = hasOptions ? {
|
|
39195
|
-
...base,
|
|
39196
|
-
type: "string",
|
|
39197
|
-
oneOf: [
|
|
39198
|
-
...options.map((option) => ({
|
|
39199
|
-
const: option.label,
|
|
39200
|
-
title: option.label,
|
|
39201
|
-
...option.description ? { description: option.description } : {}
|
|
39202
|
-
})),
|
|
39203
|
-
...hasOtherAnswer && !airClient && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
|
|
39204
|
-
const: USER_INPUT_OTHER_OPTION,
|
|
39205
|
-
title: USER_INPUT_OTHER_OPTION,
|
|
39206
|
-
description: "Provide a different answer in the note field."
|
|
39207
|
-
}] : []
|
|
39208
|
-
]
|
|
39209
|
-
} : {
|
|
39210
|
-
...base,
|
|
39211
|
-
type: "string"
|
|
39212
|
-
};
|
|
39213
|
-
if (hasOtherAnswer) {
|
|
39214
|
-
const noteMeta = {
|
|
39215
|
-
codex: {
|
|
39216
|
-
questionId: question.id,
|
|
39217
|
-
role: "user_note",
|
|
39218
|
-
isSecret: question.isSecret
|
|
39219
|
-
}
|
|
39220
|
-
};
|
|
39221
|
-
properties[userInputNoteFieldId(question.id, questionIds)] = {
|
|
39222
|
-
type: "string",
|
|
39223
|
-
title: "Additional answer or note",
|
|
39224
|
-
_meta: airClient ? withAirMeta({ ...noteMeta, [LEGACY_AIR_CUSTOM_ANSWER_KEY]: true }, AIR_CUSTOM_ANSWER_KEY, true) : noteMeta
|
|
39225
|
-
};
|
|
39226
|
-
}
|
|
39227
|
-
}
|
|
39228
|
-
return {
|
|
39229
|
-
sessionId: params.threadId,
|
|
39230
|
-
toolCallId: params.itemId,
|
|
39231
|
-
mode: "form",
|
|
39232
|
-
message: "Codex needs your input to continue.",
|
|
39233
|
-
requestedSchema: {
|
|
39234
|
-
type: "object",
|
|
39235
|
-
properties,
|
|
39236
|
-
required: required2
|
|
39237
|
-
},
|
|
39238
|
-
_meta: {
|
|
39239
|
-
codex: {
|
|
39240
|
-
autoResolutionMs: params.autoResolutionMs
|
|
39241
|
-
}
|
|
39242
|
-
}
|
|
39243
|
-
};
|
|
39244
|
-
}
|
|
39245
39302
|
convertElicitationResponse(response, context) {
|
|
39246
39303
|
if (CreateElicitationResponse.isAccept(response)) {
|
|
39247
39304
|
const content = contentRecord(response.content);
|
|
@@ -39272,40 +39329,6 @@ var CodexElicitationHandler = class {
|
|
|
39272
39329
|
isAllowedToolApprovalPersist(persist, persistOptions) {
|
|
39273
39330
|
return persist === void 0 || persist === "once" || persist === "session" && persistOptions.has("session") || persist === "always" && persistOptions.has("always");
|
|
39274
39331
|
}
|
|
39275
|
-
convertUserInputResponse(response, params) {
|
|
39276
|
-
if (!CreateElicitationResponse.isAccept(response)) {
|
|
39277
|
-
return { answers: {} };
|
|
39278
|
-
}
|
|
39279
|
-
const answers = {};
|
|
39280
|
-
const content = contentRecord(response.content);
|
|
39281
|
-
const questionIds = new Set(params.questions.map((question) => question.id));
|
|
39282
|
-
const airClient = isAirClient(this.clientCapabilities);
|
|
39283
|
-
for (const question of params.questions) {
|
|
39284
|
-
const answerValues = [];
|
|
39285
|
-
const hasOtherAnswer = question.isOther && question.options != null && question.options.length > 0;
|
|
39286
|
-
const value = userInputResponseValue(content, question.id);
|
|
39287
|
-
const typedAnswer = airClient && hasOtherAnswer ? typedChoiceAnswer(value, question) : void 0;
|
|
39288
|
-
if (typedAnswer !== void 0) {
|
|
39289
|
-
answerValues.push(USER_INPUT_OTHER_OPTION, `${USER_INPUT_NOTE_PREFIX}${typedAnswer.trim()}`);
|
|
39290
|
-
} else if (value !== void 0) {
|
|
39291
|
-
answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
|
|
39292
|
-
}
|
|
39293
|
-
if (hasOtherAnswer) {
|
|
39294
|
-
const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
|
|
39295
|
-
if (note !== void 0) {
|
|
39296
|
-
const notes = Array.isArray(note) ? note : [note];
|
|
39297
|
-
answerValues.push(...notes.map((item) => `${USER_INPUT_NOTE_PREFIX}${String(item).trim()}`));
|
|
39298
|
-
}
|
|
39299
|
-
}
|
|
39300
|
-
if (answerValues.length === 0) {
|
|
39301
|
-
continue;
|
|
39302
|
-
}
|
|
39303
|
-
answers[question.id] = {
|
|
39304
|
-
answers: answerValues
|
|
39305
|
-
};
|
|
39306
|
-
}
|
|
39307
|
-
return { answers };
|
|
39308
|
-
}
|
|
39309
39332
|
async publishAcceptedMcpToolApproval(sessionId, context, accepted) {
|
|
39310
39333
|
if (!accepted || context.correlatedCallId === void 0) {
|
|
39311
39334
|
return;
|
|
@@ -40552,7 +40575,7 @@ var package_default = {
|
|
|
40552
40575
|
publishConfig: {
|
|
40553
40576
|
access: "public"
|
|
40554
40577
|
},
|
|
40555
|
-
version: "2.5.
|
|
40578
|
+
version: "2.5.1-preview.2",
|
|
40556
40579
|
description: "",
|
|
40557
40580
|
main: "dist/index.js",
|
|
40558
40581
|
bin: {
|
|
@@ -46567,6 +46590,198 @@ function isJetBrains2026_1Client(clientInfo) {
|
|
|
46567
46590
|
return (isIntelliJPlatform || isJetBrainsClient) && clientInfo.version.startsWith("2026.1");
|
|
46568
46591
|
}
|
|
46569
46592
|
|
|
46593
|
+
// src/CodexAsyncQuestions.ts
|
|
46594
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
46595
|
+
var TOOL_NAME = "request_user_input_async";
|
|
46596
|
+
var REPLY_START = "<send_user_message_question_reply>";
|
|
46597
|
+
var REPLY_END = "</send_user_message_question_reply>";
|
|
46598
|
+
var MAX_REPLY_FIELD_LENGTH = 512;
|
|
46599
|
+
function asyncQuestionItemId(messageId, index) {
|
|
46600
|
+
return JSON.stringify([TOOL_NAME, messageId, index]);
|
|
46601
|
+
}
|
|
46602
|
+
function formatAsyncQuestionReply(replies) {
|
|
46603
|
+
const bounded = replies.map((reply) => ({
|
|
46604
|
+
...reply,
|
|
46605
|
+
question: [...reply.question].slice(0, MAX_REPLY_FIELD_LENGTH).join("").replace(/[\r\n]/g, " ")
|
|
46606
|
+
}));
|
|
46607
|
+
if (bounded.some((reply) => reply.questionItemId.length > MAX_REPLY_FIELD_LENGTH)) {
|
|
46608
|
+
return displayReplies(bounded);
|
|
46609
|
+
}
|
|
46610
|
+
const body = bounded.map(({ answer, question, questionItemId }) => ({ answer, question, questionItemId }));
|
|
46611
|
+
return `${REPLY_START}
|
|
46612
|
+
${JSON.stringify(body)}
|
|
46613
|
+
${REPLY_END}`;
|
|
46614
|
+
}
|
|
46615
|
+
function parseAsyncQuestionReply(text) {
|
|
46616
|
+
const trimmed = text.trim();
|
|
46617
|
+
if (!trimmed.startsWith(REPLY_START) || !trimmed.endsWith(REPLY_END)) return null;
|
|
46618
|
+
let body;
|
|
46619
|
+
try {
|
|
46620
|
+
body = JSON.parse(trimmed.slice(REPLY_START.length, trimmed.length - REPLY_END.length));
|
|
46621
|
+
} catch {
|
|
46622
|
+
return null;
|
|
46623
|
+
}
|
|
46624
|
+
const replies = Array.isArray(body) ? body : [body];
|
|
46625
|
+
if (replies.length === 0 || !replies.every(isReply)) return null;
|
|
46626
|
+
return replies.map(({ questionItemId, question, answer }) => ({ questionItemId, question, answer }));
|
|
46627
|
+
}
|
|
46628
|
+
function renderAsyncQuestionReply(text) {
|
|
46629
|
+
const replies = parseAsyncQuestionReply(text);
|
|
46630
|
+
return replies === null ? null : displayReplies(replies);
|
|
46631
|
+
}
|
|
46632
|
+
function displayReplies(replies) {
|
|
46633
|
+
return replies.map((reply) => `> ${reply.question}
|
|
46634
|
+
|
|
46635
|
+
${reply.answer}`).join("\n\n");
|
|
46636
|
+
}
|
|
46637
|
+
function isReply(value) {
|
|
46638
|
+
if (typeof value !== "object" || value === null) return false;
|
|
46639
|
+
const reply = value;
|
|
46640
|
+
return typeof reply["questionItemId"] === "string" && typeof reply["question"] === "string" && typeof reply["answer"] === "string";
|
|
46641
|
+
}
|
|
46642
|
+
var AsyncQuestionClient = class {
|
|
46643
|
+
/**
|
|
46644
|
+
* @param sessionId The session that the questions belong to.
|
|
46645
|
+
* @param connection The connection to the ACP client.
|
|
46646
|
+
* @param clientCapabilities The capabilities that the ACP client declared.
|
|
46647
|
+
* @param codexAcpClient Returns the current Codex client, which changes when the app-server restarts.
|
|
46648
|
+
*/
|
|
46649
|
+
constructor(sessionId, connection, clientCapabilities, codexAcpClient) {
|
|
46650
|
+
this.sessionId = sessionId;
|
|
46651
|
+
this.connection = connection;
|
|
46652
|
+
this.clientCapabilities = clientCapabilities;
|
|
46653
|
+
this.codexAcpClient = codexAcpClient;
|
|
46654
|
+
}
|
|
46655
|
+
sessionId;
|
|
46656
|
+
connection;
|
|
46657
|
+
clientCapabilities;
|
|
46658
|
+
codexAcpClient;
|
|
46659
|
+
/** Sends the elicitation without marking the session as waiting for the user. */
|
|
46660
|
+
createElicitation(request, signal) {
|
|
46661
|
+
const v2 = AcpV2Connection.of(this.connection);
|
|
46662
|
+
return v2 !== null ? v2.createElicitation(request, { cancellationSignal: signal }, { userAction: false }) : this.connection.request(methods.client.elicitation.create, request, { cancellationSignal: signal });
|
|
46663
|
+
}
|
|
46664
|
+
/** Steers the text into the turn; rejects if that turn is no longer running. */
|
|
46665
|
+
async steer(turnId, text) {
|
|
46666
|
+
await this.codexAcpClient().steerTurn({
|
|
46667
|
+
threadId: this.sessionId,
|
|
46668
|
+
turnId,
|
|
46669
|
+
prompt: [{ type: "text", text }],
|
|
46670
|
+
clientUserMessageId: randomUUID4()
|
|
46671
|
+
});
|
|
46672
|
+
}
|
|
46673
|
+
/** Tells the user that an answer was not sent, if the client supports notices. */
|
|
46674
|
+
async reportDroppedAnswer() {
|
|
46675
|
+
if (!clientSupportsNotices(this.clientCapabilities)) return;
|
|
46676
|
+
await this.connection.notify(methods.client.session.update, {
|
|
46677
|
+
sessionId: this.sessionId,
|
|
46678
|
+
update: createSessionNotice(
|
|
46679
|
+
"warning",
|
|
46680
|
+
"Answer not sent",
|
|
46681
|
+
"Codex finished before your answer arrived. Send it as a new message if it still matters."
|
|
46682
|
+
)
|
|
46683
|
+
});
|
|
46684
|
+
}
|
|
46685
|
+
};
|
|
46686
|
+
var AsyncQuestionElicitations = class {
|
|
46687
|
+
constructor(sessionId, clientCapabilities, client) {
|
|
46688
|
+
this.sessionId = sessionId;
|
|
46689
|
+
this.clientCapabilities = clientCapabilities;
|
|
46690
|
+
this.client = client;
|
|
46691
|
+
}
|
|
46692
|
+
sessionId;
|
|
46693
|
+
clientCapabilities;
|
|
46694
|
+
client;
|
|
46695
|
+
// The running turns that asked a question, each with the signal that cancels its questions.
|
|
46696
|
+
turns = /* @__PURE__ */ new Map();
|
|
46697
|
+
/** Asks the questions of a completed async message and cancels them when their turn ends; never waits. */
|
|
46698
|
+
handleNotification(notification) {
|
|
46699
|
+
switch (notification.method) {
|
|
46700
|
+
case "item/completed": {
|
|
46701
|
+
const { threadId, turnId, item } = notification.params;
|
|
46702
|
+
if (threadId === this.sessionId && item.type === "agentMessage" && item.delivery === "async" && (item.questions?.length ?? 0) > 0) {
|
|
46703
|
+
this.ask(turnId, item);
|
|
46704
|
+
}
|
|
46705
|
+
return;
|
|
46706
|
+
}
|
|
46707
|
+
case "turn/completed":
|
|
46708
|
+
if (notification.params.threadId === this.sessionId) {
|
|
46709
|
+
this.endTurn(notification.params.turn.id);
|
|
46710
|
+
}
|
|
46711
|
+
return;
|
|
46712
|
+
default:
|
|
46713
|
+
return;
|
|
46714
|
+
}
|
|
46715
|
+
}
|
|
46716
|
+
/** Cancels every open question, when the session closes or its app-server is gone. */
|
|
46717
|
+
dispose() {
|
|
46718
|
+
for (const turnId of [...this.turns.keys()]) {
|
|
46719
|
+
this.endTurn(turnId);
|
|
46720
|
+
}
|
|
46721
|
+
}
|
|
46722
|
+
endTurn(turnId) {
|
|
46723
|
+
this.turns.get(turnId)?.abort();
|
|
46724
|
+
this.turns.delete(turnId);
|
|
46725
|
+
}
|
|
46726
|
+
ask(turnId, message) {
|
|
46727
|
+
if (!clientSupportsFormElicitation(this.clientCapabilities)) return;
|
|
46728
|
+
let turn = this.turns.get(turnId);
|
|
46729
|
+
if (turn === void 0) {
|
|
46730
|
+
turn = new AbortController();
|
|
46731
|
+
this.turns.set(turnId, turn);
|
|
46732
|
+
}
|
|
46733
|
+
const questions = formQuestions(message);
|
|
46734
|
+
const request = {
|
|
46735
|
+
sessionId: this.sessionId,
|
|
46736
|
+
mode: "form",
|
|
46737
|
+
message: "Codex keeps working while you answer. An answer sent after it finishes is not delivered.",
|
|
46738
|
+
// Questions are answered independently, as in Codex.
|
|
46739
|
+
requestedSchema: buildUserInputSchema(questions, this.clientCapabilities, false),
|
|
46740
|
+
_meta: { codex: { delivery: "async" } }
|
|
46741
|
+
};
|
|
46742
|
+
void this.client.createElicitation(request, turn.signal).then(
|
|
46743
|
+
(response) => this.answer(turnId, message, questions, response).catch((error62) => logger.error("Failed to deliver an async question answer", error62)),
|
|
46744
|
+
(error62) => logger.log("Async question closed without an answer", { sessionId: this.sessionId, turnId, error: error62 })
|
|
46745
|
+
);
|
|
46746
|
+
}
|
|
46747
|
+
async answer(turnId, message, questions, response) {
|
|
46748
|
+
const answers = userInputAnswers(response, questions, this.clientCapabilities);
|
|
46749
|
+
const replies = questions.flatMap((question, index) => {
|
|
46750
|
+
const answer = userInputAnswerText(answers[question.id]?.answers ?? []);
|
|
46751
|
+
return answer.length === 0 ? [] : [{
|
|
46752
|
+
questionItemId: asyncQuestionItemId(message.id, index),
|
|
46753
|
+
question: question.question,
|
|
46754
|
+
answer
|
|
46755
|
+
}];
|
|
46756
|
+
});
|
|
46757
|
+
if (replies.length === 0) return;
|
|
46758
|
+
if (this.turns.has(turnId)) {
|
|
46759
|
+
try {
|
|
46760
|
+
await this.client.steer(turnId, formatAsyncQuestionReply(replies));
|
|
46761
|
+
return;
|
|
46762
|
+
} catch (error62) {
|
|
46763
|
+
logger.log("Async question answer was not steered into its turn", { sessionId: this.sessionId, turnId, error: error62 });
|
|
46764
|
+
}
|
|
46765
|
+
}
|
|
46766
|
+
logger.log("Dropped an async question answer: its turn has ended", { sessionId: this.sessionId, turnId });
|
|
46767
|
+
await this.client.reportDroppedAnswer();
|
|
46768
|
+
}
|
|
46769
|
+
};
|
|
46770
|
+
function formQuestions(message) {
|
|
46771
|
+
return (message.questions ?? []).map((question, index) => {
|
|
46772
|
+
const options = question.options ?? [];
|
|
46773
|
+
return {
|
|
46774
|
+
id: `question_${index}`,
|
|
46775
|
+
header: "",
|
|
46776
|
+
question: question.title,
|
|
46777
|
+
// Codex always accepts an own answer.
|
|
46778
|
+
isOther: options.length > 0,
|
|
46779
|
+
isSecret: false,
|
|
46780
|
+
options: options.length > 0 ? options.map((label) => ({ label, description: "" })) : null
|
|
46781
|
+
};
|
|
46782
|
+
});
|
|
46783
|
+
}
|
|
46784
|
+
|
|
46570
46785
|
// src/subagents/AcpSubagents.ts
|
|
46571
46786
|
function subagentProtocol(capabilities, version2 = 1) {
|
|
46572
46787
|
const subagents = capabilities?.subagents;
|
|
@@ -46622,7 +46837,7 @@ async function trustCodexHooks(client, cwd, requested) {
|
|
|
46622
46837
|
}
|
|
46623
46838
|
|
|
46624
46839
|
// src/CodexAcpServer.ts
|
|
46625
|
-
import { randomUUID as
|
|
46840
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
46626
46841
|
|
|
46627
46842
|
// src/TitleGenerator.ts
|
|
46628
46843
|
var TITLE_MODEL = "gpt-5.6-luna";
|
|
@@ -47685,7 +47900,7 @@ function remember(cache, key, value) {
|
|
|
47685
47900
|
}
|
|
47686
47901
|
|
|
47687
47902
|
// src/SessionListSubscriptions.ts
|
|
47688
|
-
import { randomUUID as
|
|
47903
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
47689
47904
|
import path14 from "node:path";
|
|
47690
47905
|
|
|
47691
47906
|
// src/CodexHomeWatcher.ts
|
|
@@ -48093,7 +48308,7 @@ var SessionListSubscriptions = class {
|
|
|
48093
48308
|
this.startWatching();
|
|
48094
48309
|
created.ready = this.makeReady(created);
|
|
48095
48310
|
}
|
|
48096
|
-
const subscription = { id:
|
|
48311
|
+
const subscription = { id: randomUUID5(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
|
|
48097
48312
|
this.subscriptions.set(subscription.id, subscription);
|
|
48098
48313
|
group.subscriptions.add(subscription);
|
|
48099
48314
|
this.startWatching();
|
|
@@ -49464,6 +49679,8 @@ var CodexAcpServer = class {
|
|
|
49464
49679
|
/** Tail of the per-session turn-start FIFO; see `acquireTurnStartReservation`. */
|
|
49465
49680
|
turnStartQueueTail;
|
|
49466
49681
|
steeringQueues;
|
|
49682
|
+
/** The async questions of each session, shown while the turn that asked them runs. */
|
|
49683
|
+
asyncQuestions = /* @__PURE__ */ new Map();
|
|
49467
49684
|
/**
|
|
49468
49685
|
* Steers awaiting their `userMessage` landing, keyed by the minted `clientUserMessageId`
|
|
49469
49686
|
* passed to `turn/steer`. Used only to show a v2 live `user_message` when a steer injected
|
|
@@ -49555,7 +49772,7 @@ var CodexAcpServer = class {
|
|
|
49555
49772
|
this.captureStderr();
|
|
49556
49773
|
this.getExitCode = getExitCode ?? (() => this.codexProcessState?.connection.process.exitCode ?? null);
|
|
49557
49774
|
this.getRecentStderr = getRecentStderr ?? (() => this.codexProcessState?.stderr ?? "");
|
|
49558
|
-
this.sessionFailureEpoch =
|
|
49775
|
+
this.sessionFailureEpoch = randomUUID6();
|
|
49559
49776
|
this.clientInfo = null;
|
|
49560
49777
|
this.clientCapabilities = null;
|
|
49561
49778
|
this.capabilities = ClientCapabilities.DEFAULT;
|
|
@@ -50799,6 +51016,8 @@ You have been logged out. Please try again.`);
|
|
|
50799
51016
|
} finally {
|
|
50800
51017
|
if (this.getSessionGeneration(params.sessionId) === closeGeneration) {
|
|
50801
51018
|
this.sessions.delete(params.sessionId);
|
|
51019
|
+
this.asyncQuestions.get(params.sessionId)?.dispose();
|
|
51020
|
+
this.asyncQuestions.delete(params.sessionId);
|
|
50802
51021
|
if (!this.v2PromptsInFlight.has(params.sessionId)) {
|
|
50803
51022
|
AcpV2Connection.of(this.connection)?.forgetSession(params.sessionId);
|
|
50804
51023
|
}
|
|
@@ -51559,7 +51778,7 @@ You have been logged out. Please try again.`);
|
|
|
51559
51778
|
});
|
|
51560
51779
|
const sessionState = this.getSessionState(params.sessionId);
|
|
51561
51780
|
this.assertSteerInputSupported(params, sessionState);
|
|
51562
|
-
const clientUserMessageId =
|
|
51781
|
+
const clientUserMessageId = randomUUID6();
|
|
51563
51782
|
const turnId = await this.getSteerableTurnId(sessionState);
|
|
51564
51783
|
if (turnId) {
|
|
51565
51784
|
const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState, clientUserMessageId);
|
|
@@ -52507,8 +52726,11 @@ You have been logged out. Please try again.`);
|
|
|
52507
52726
|
}
|
|
52508
52727
|
userInputToContentBlocks(input2) {
|
|
52509
52728
|
switch (input2.type) {
|
|
52510
|
-
case "text":
|
|
52729
|
+
case "text": {
|
|
52730
|
+
const asyncQuestionReply = renderAsyncQuestionReply(input2.text);
|
|
52731
|
+
if (asyncQuestionReply !== null) return [{ type: "text", text: asyncQuestionReply }];
|
|
52511
52732
|
return desktopAttachmentHistory(input2.text) ?? (input2.text.length > 0 ? [{ type: "text", text: input2.text }] : []);
|
|
52733
|
+
}
|
|
52512
52734
|
case "image":
|
|
52513
52735
|
return [{
|
|
52514
52736
|
type: "text",
|
|
@@ -52887,7 +53109,7 @@ You have been logged out. Please try again.`);
|
|
|
52887
53109
|
}
|
|
52888
53110
|
}
|
|
52889
53111
|
const promptKind = this.availableCommands.classifyPrompt(request.prompt);
|
|
52890
|
-
const messageId =
|
|
53112
|
+
const messageId = randomUUID6();
|
|
52891
53113
|
const session = new ACPSessionConnection(this.connection, sessionId);
|
|
52892
53114
|
let failure3 = null;
|
|
52893
53115
|
this.v2PromptsInFlight.add(sessionId);
|
|
@@ -53204,11 +53426,13 @@ You have been logged out. Please try again.`);
|
|
|
53204
53426
|
await observeInteraction(event);
|
|
53205
53427
|
if (!promptNotificationsActive) {
|
|
53206
53428
|
await promptEventHandler.handleSessionScopedNotification(event);
|
|
53429
|
+
this.asyncQuestionsFor(sessionState).handleNotification(event);
|
|
53207
53430
|
await this.trackCodexTurnCompletion(sessionState, event, promptEventHandler);
|
|
53208
53431
|
return;
|
|
53209
53432
|
}
|
|
53210
53433
|
const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && event.params.turn.id === sessionState.currentTurnId;
|
|
53211
53434
|
await promptEventHandler.handleNotification(event);
|
|
53435
|
+
this.asyncQuestionsFor(sessionState).handleNotification(event);
|
|
53212
53436
|
if (completesActiveTurn) {
|
|
53213
53437
|
promptNotificationsActive = false;
|
|
53214
53438
|
}
|
|
@@ -53440,7 +53664,7 @@ You have been logged out. Please try again.`);
|
|
|
53440
53664
|
activePrompt.currentTurn = null;
|
|
53441
53665
|
sessionState.currentTurnId = null;
|
|
53442
53666
|
sessionState.interruptTurnId = null;
|
|
53443
|
-
const implementationClientUserMessageId = insertion !== void 0 ?
|
|
53667
|
+
const implementationClientUserMessageId = insertion !== void 0 ? randomUUID6() : void 0;
|
|
53444
53668
|
if (implementationClientUserMessageId !== void 0) {
|
|
53445
53669
|
registerSyntheticInsertion(implementationClientUserMessageId, implementationRequest.prompt);
|
|
53446
53670
|
}
|
|
@@ -53651,12 +53875,28 @@ You have been logged out. Please try again.`);
|
|
|
53651
53875
|
if (this.recovery === null || !lost.aborted || !sessionState.subagents.hasOutstanding()) return;
|
|
53652
53876
|
throw this.recovery.errorForGeneration(generation);
|
|
53653
53877
|
}
|
|
53878
|
+
/** The async questions of a session, created on first use. */
|
|
53879
|
+
asyncQuestionsFor(sessionState) {
|
|
53880
|
+
const sessionId = sessionState.sessionId;
|
|
53881
|
+
let questions = this.asyncQuestions.get(sessionId);
|
|
53882
|
+
if (questions === void 0) {
|
|
53883
|
+
questions = new AsyncQuestionElicitations(sessionId, this.clientCapabilities, new AsyncQuestionClient(
|
|
53884
|
+
sessionId,
|
|
53885
|
+
this.connection,
|
|
53886
|
+
this.clientCapabilities,
|
|
53887
|
+
() => this.codexAcpClient
|
|
53888
|
+
));
|
|
53889
|
+
this.asyncQuestions.set(sessionId, questions);
|
|
53890
|
+
}
|
|
53891
|
+
return questions;
|
|
53892
|
+
}
|
|
53654
53893
|
/**
|
|
53655
53894
|
* Ends what a prompt showed as running when its app-server died: open tool calls of the session and of its native
|
|
53656
53895
|
* subagent sessions, the subagent sessions themselves and compactions. Nothing will ever complete them.
|
|
53657
53896
|
*/
|
|
53658
53897
|
async finishPromptAfterAppServerLoss(sessionState, eventHandler, promptClient, elicitations, state) {
|
|
53659
53898
|
try {
|
|
53899
|
+
this.asyncQuestions.get(sessionState.sessionId)?.dispose();
|
|
53660
53900
|
await elicitations?.completeAllUrlElicitations();
|
|
53661
53901
|
const sessionIds = /* @__PURE__ */ new Set([sessionState.sessionId, ...sessionState.subagents.childSessionIds()]);
|
|
53662
53902
|
await this.failUnfinishedToolCalls(sessionIds);
|