@alas-ide/codex-acp 2.5.0-alas.1 → 2.5.0-alas.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 +366 -126
- 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
|
-
/**
|
|
32393
|
-
|
|
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
|
-
/**
|
|
37653
|
-
|
|
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
|
|
37656
|
-
streams.agentMessageCitations.delete(
|
|
37657
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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.
|
|
40614
|
+
version: "2.5.0-alas.2",
|
|
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: "
|
|
40692
|
+
sourceCommit: "0f50c0b0345fd0e67b91ffc3c49d6b998d27da0c"
|
|
40670
40693
|
}
|
|
40671
40694
|
};
|
|
40672
40695
|
|
|
@@ -48951,6 +48974,198 @@ function isJetBrains2026_1Client(clientInfo) {
|
|
|
48951
48974
|
return (isIntelliJPlatform || isJetBrainsClient) && clientInfo.version.startsWith("2026.1");
|
|
48952
48975
|
}
|
|
48953
48976
|
|
|
48977
|
+
// src/CodexAsyncQuestions.ts
|
|
48978
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
48979
|
+
var TOOL_NAME = "request_user_input_async";
|
|
48980
|
+
var REPLY_START = "<send_user_message_question_reply>";
|
|
48981
|
+
var REPLY_END = "</send_user_message_question_reply>";
|
|
48982
|
+
var MAX_REPLY_FIELD_LENGTH = 512;
|
|
48983
|
+
function asyncQuestionItemId(messageId, index) {
|
|
48984
|
+
return JSON.stringify([TOOL_NAME, messageId, index]);
|
|
48985
|
+
}
|
|
48986
|
+
function formatAsyncQuestionReply(replies) {
|
|
48987
|
+
const bounded = replies.map((reply) => ({
|
|
48988
|
+
...reply,
|
|
48989
|
+
question: [...reply.question].slice(0, MAX_REPLY_FIELD_LENGTH).join("").replace(/[\r\n]/g, " ")
|
|
48990
|
+
}));
|
|
48991
|
+
if (bounded.some((reply) => reply.questionItemId.length > MAX_REPLY_FIELD_LENGTH)) {
|
|
48992
|
+
return displayReplies(bounded);
|
|
48993
|
+
}
|
|
48994
|
+
const body = bounded.map(({ answer, question, questionItemId }) => ({ answer, question, questionItemId }));
|
|
48995
|
+
return `${REPLY_START}
|
|
48996
|
+
${JSON.stringify(body)}
|
|
48997
|
+
${REPLY_END}`;
|
|
48998
|
+
}
|
|
48999
|
+
function parseAsyncQuestionReply(text) {
|
|
49000
|
+
const trimmed = text.trim();
|
|
49001
|
+
if (!trimmed.startsWith(REPLY_START) || !trimmed.endsWith(REPLY_END)) return null;
|
|
49002
|
+
let body;
|
|
49003
|
+
try {
|
|
49004
|
+
body = JSON.parse(trimmed.slice(REPLY_START.length, trimmed.length - REPLY_END.length));
|
|
49005
|
+
} catch {
|
|
49006
|
+
return null;
|
|
49007
|
+
}
|
|
49008
|
+
const replies = Array.isArray(body) ? body : [body];
|
|
49009
|
+
if (replies.length === 0 || !replies.every(isReply)) return null;
|
|
49010
|
+
return replies.map(({ questionItemId, question, answer }) => ({ questionItemId, question, answer }));
|
|
49011
|
+
}
|
|
49012
|
+
function renderAsyncQuestionReply(text) {
|
|
49013
|
+
const replies = parseAsyncQuestionReply(text);
|
|
49014
|
+
return replies === null ? null : displayReplies(replies);
|
|
49015
|
+
}
|
|
49016
|
+
function displayReplies(replies) {
|
|
49017
|
+
return replies.map((reply) => `> ${reply.question}
|
|
49018
|
+
|
|
49019
|
+
${reply.answer}`).join("\n\n");
|
|
49020
|
+
}
|
|
49021
|
+
function isReply(value) {
|
|
49022
|
+
if (typeof value !== "object" || value === null) return false;
|
|
49023
|
+
const reply = value;
|
|
49024
|
+
return typeof reply["questionItemId"] === "string" && typeof reply["question"] === "string" && typeof reply["answer"] === "string";
|
|
49025
|
+
}
|
|
49026
|
+
var AsyncQuestionClient = class {
|
|
49027
|
+
/**
|
|
49028
|
+
* @param sessionId The session that the questions belong to.
|
|
49029
|
+
* @param connection The connection to the ACP client.
|
|
49030
|
+
* @param clientCapabilities The capabilities that the ACP client declared.
|
|
49031
|
+
* @param codexAcpClient Returns the current Codex client, which changes when the app-server restarts.
|
|
49032
|
+
*/
|
|
49033
|
+
constructor(sessionId, connection, clientCapabilities, codexAcpClient) {
|
|
49034
|
+
this.sessionId = sessionId;
|
|
49035
|
+
this.connection = connection;
|
|
49036
|
+
this.clientCapabilities = clientCapabilities;
|
|
49037
|
+
this.codexAcpClient = codexAcpClient;
|
|
49038
|
+
}
|
|
49039
|
+
sessionId;
|
|
49040
|
+
connection;
|
|
49041
|
+
clientCapabilities;
|
|
49042
|
+
codexAcpClient;
|
|
49043
|
+
/** Sends the elicitation without marking the session as waiting for the user. */
|
|
49044
|
+
createElicitation(request, signal) {
|
|
49045
|
+
const v2 = AcpV2Connection.of(this.connection);
|
|
49046
|
+
return v2 !== null ? v2.createElicitation(request, { cancellationSignal: signal }, { userAction: false }) : this.connection.request(methods.client.elicitation.create, request, { cancellationSignal: signal });
|
|
49047
|
+
}
|
|
49048
|
+
/** Steers the text into the turn; rejects if that turn is no longer running. */
|
|
49049
|
+
async steer(turnId, text) {
|
|
49050
|
+
await this.codexAcpClient().steerTurn({
|
|
49051
|
+
threadId: this.sessionId,
|
|
49052
|
+
turnId,
|
|
49053
|
+
prompt: [{ type: "text", text }],
|
|
49054
|
+
clientUserMessageId: randomUUID5()
|
|
49055
|
+
});
|
|
49056
|
+
}
|
|
49057
|
+
/** Tells the user that an answer was not sent, if the client supports notices. */
|
|
49058
|
+
async reportDroppedAnswer() {
|
|
49059
|
+
if (!clientSupportsNotices(this.clientCapabilities)) return;
|
|
49060
|
+
await this.connection.notify(methods.client.session.update, {
|
|
49061
|
+
sessionId: this.sessionId,
|
|
49062
|
+
update: createSessionNotice(
|
|
49063
|
+
"warning",
|
|
49064
|
+
"Answer not sent",
|
|
49065
|
+
"Codex finished before your answer arrived. Send it as a new message if it still matters."
|
|
49066
|
+
)
|
|
49067
|
+
});
|
|
49068
|
+
}
|
|
49069
|
+
};
|
|
49070
|
+
var AsyncQuestionElicitations = class {
|
|
49071
|
+
constructor(sessionId, clientCapabilities, client) {
|
|
49072
|
+
this.sessionId = sessionId;
|
|
49073
|
+
this.clientCapabilities = clientCapabilities;
|
|
49074
|
+
this.client = client;
|
|
49075
|
+
}
|
|
49076
|
+
sessionId;
|
|
49077
|
+
clientCapabilities;
|
|
49078
|
+
client;
|
|
49079
|
+
// The running turns that asked a question, each with the signal that cancels its questions.
|
|
49080
|
+
turns = /* @__PURE__ */ new Map();
|
|
49081
|
+
/** Asks the questions of a completed async message and cancels them when their turn ends; never waits. */
|
|
49082
|
+
handleNotification(notification) {
|
|
49083
|
+
switch (notification.method) {
|
|
49084
|
+
case "item/completed": {
|
|
49085
|
+
const { threadId, turnId, item } = notification.params;
|
|
49086
|
+
if (threadId === this.sessionId && item.type === "agentMessage" && item.delivery === "async" && (item.questions?.length ?? 0) > 0) {
|
|
49087
|
+
this.ask(turnId, item);
|
|
49088
|
+
}
|
|
49089
|
+
return;
|
|
49090
|
+
}
|
|
49091
|
+
case "turn/completed":
|
|
49092
|
+
if (notification.params.threadId === this.sessionId) {
|
|
49093
|
+
this.endTurn(notification.params.turn.id);
|
|
49094
|
+
}
|
|
49095
|
+
return;
|
|
49096
|
+
default:
|
|
49097
|
+
return;
|
|
49098
|
+
}
|
|
49099
|
+
}
|
|
49100
|
+
/** Cancels every open question, when the session closes or its app-server is gone. */
|
|
49101
|
+
dispose() {
|
|
49102
|
+
for (const turnId of [...this.turns.keys()]) {
|
|
49103
|
+
this.endTurn(turnId);
|
|
49104
|
+
}
|
|
49105
|
+
}
|
|
49106
|
+
endTurn(turnId) {
|
|
49107
|
+
this.turns.get(turnId)?.abort();
|
|
49108
|
+
this.turns.delete(turnId);
|
|
49109
|
+
}
|
|
49110
|
+
ask(turnId, message) {
|
|
49111
|
+
if (!clientSupportsFormElicitation(this.clientCapabilities)) return;
|
|
49112
|
+
let turn = this.turns.get(turnId);
|
|
49113
|
+
if (turn === void 0) {
|
|
49114
|
+
turn = new AbortController();
|
|
49115
|
+
this.turns.set(turnId, turn);
|
|
49116
|
+
}
|
|
49117
|
+
const questions = formQuestions(message);
|
|
49118
|
+
const request = {
|
|
49119
|
+
sessionId: this.sessionId,
|
|
49120
|
+
mode: "form",
|
|
49121
|
+
message: "Codex keeps working while you answer. An answer sent after it finishes is not delivered.",
|
|
49122
|
+
// Questions are answered independently, as in Codex.
|
|
49123
|
+
requestedSchema: buildUserInputSchema(questions, this.clientCapabilities, false),
|
|
49124
|
+
_meta: { codex: { delivery: "async" } }
|
|
49125
|
+
};
|
|
49126
|
+
void this.client.createElicitation(request, turn.signal).then(
|
|
49127
|
+
(response) => this.answer(turnId, message, questions, response).catch((error62) => logger.error("Failed to deliver an async question answer", error62)),
|
|
49128
|
+
(error62) => logger.log("Async question closed without an answer", { sessionId: this.sessionId, turnId, error: error62 })
|
|
49129
|
+
);
|
|
49130
|
+
}
|
|
49131
|
+
async answer(turnId, message, questions, response) {
|
|
49132
|
+
const answers = userInputAnswers(response, questions, this.clientCapabilities);
|
|
49133
|
+
const replies = questions.flatMap((question, index) => {
|
|
49134
|
+
const answer = userInputAnswerText(answers[question.id]?.answers ?? []);
|
|
49135
|
+
return answer.length === 0 ? [] : [{
|
|
49136
|
+
questionItemId: asyncQuestionItemId(message.id, index),
|
|
49137
|
+
question: question.question,
|
|
49138
|
+
answer
|
|
49139
|
+
}];
|
|
49140
|
+
});
|
|
49141
|
+
if (replies.length === 0) return;
|
|
49142
|
+
if (this.turns.has(turnId)) {
|
|
49143
|
+
try {
|
|
49144
|
+
await this.client.steer(turnId, formatAsyncQuestionReply(replies));
|
|
49145
|
+
return;
|
|
49146
|
+
} catch (error62) {
|
|
49147
|
+
logger.log("Async question answer was not steered into its turn", { sessionId: this.sessionId, turnId, error: error62 });
|
|
49148
|
+
}
|
|
49149
|
+
}
|
|
49150
|
+
logger.log("Dropped an async question answer: its turn has ended", { sessionId: this.sessionId, turnId });
|
|
49151
|
+
await this.client.reportDroppedAnswer();
|
|
49152
|
+
}
|
|
49153
|
+
};
|
|
49154
|
+
function formQuestions(message) {
|
|
49155
|
+
return (message.questions ?? []).map((question, index) => {
|
|
49156
|
+
const options = question.options ?? [];
|
|
49157
|
+
return {
|
|
49158
|
+
id: `question_${index}`,
|
|
49159
|
+
header: "",
|
|
49160
|
+
question: question.title,
|
|
49161
|
+
// Codex always accepts an own answer.
|
|
49162
|
+
isOther: options.length > 0,
|
|
49163
|
+
isSecret: false,
|
|
49164
|
+
options: options.length > 0 ? options.map((label) => ({ label, description: "" })) : null
|
|
49165
|
+
};
|
|
49166
|
+
});
|
|
49167
|
+
}
|
|
49168
|
+
|
|
48954
49169
|
// src/subagents/AcpSubagents.ts
|
|
48955
49170
|
function subagentProtocol(capabilities, version2 = 1) {
|
|
48956
49171
|
const subagents = capabilities?.subagents;
|
|
@@ -48979,7 +49194,7 @@ var SubagentInteractionHandlers = class {
|
|
|
48979
49194
|
};
|
|
48980
49195
|
|
|
48981
49196
|
// src/CodexAcpServer.ts
|
|
48982
|
-
import { randomUUID as
|
|
49197
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
48983
49198
|
|
|
48984
49199
|
// src/TitleGenerator.ts
|
|
48985
49200
|
var TITLE_MODEL = "gpt-5.6-luna";
|
|
@@ -50067,7 +50282,7 @@ function remember(cache, key, value) {
|
|
|
50067
50282
|
}
|
|
50068
50283
|
|
|
50069
50284
|
// src/SessionListSubscriptions.ts
|
|
50070
|
-
import { randomUUID as
|
|
50285
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
50071
50286
|
import path14 from "node:path";
|
|
50072
50287
|
|
|
50073
50288
|
// src/CodexHomeWatcher.ts
|
|
@@ -50475,7 +50690,7 @@ var SessionListSubscriptions = class {
|
|
|
50475
50690
|
this.startWatching();
|
|
50476
50691
|
created.ready = this.makeReady(created);
|
|
50477
50692
|
}
|
|
50478
|
-
const subscription = { id:
|
|
50693
|
+
const subscription = { id: randomUUID6(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
|
|
50479
50694
|
this.subscriptions.set(subscription.id, subscription);
|
|
50480
50695
|
group.subscriptions.add(subscription);
|
|
50481
50696
|
this.startWatching();
|
|
@@ -51867,6 +52082,8 @@ var CodexAcpServer = class {
|
|
|
51867
52082
|
/** Tail of the per-session turn-start FIFO; see `acquireTurnStartReservation`. */
|
|
51868
52083
|
turnStartQueueTail;
|
|
51869
52084
|
steeringQueues;
|
|
52085
|
+
/** The async questions of each session, shown while the turn that asked them runs. */
|
|
52086
|
+
asyncQuestions = /* @__PURE__ */ new Map();
|
|
51870
52087
|
/**
|
|
51871
52088
|
* Steers awaiting their `userMessage` landing, keyed by the minted `clientUserMessageId`
|
|
51872
52089
|
* passed to `turn/steer`. Used only to show a v2 live `user_message` when a steer injected
|
|
@@ -51959,7 +52176,7 @@ var CodexAcpServer = class {
|
|
|
51959
52176
|
this.captureStderr();
|
|
51960
52177
|
this.getExitCode = getExitCode ?? (() => this.codexProcessState?.connection.process.exitCode ?? null);
|
|
51961
52178
|
this.getRecentStderr = getRecentStderr ?? (() => this.codexProcessState?.stderr ?? "");
|
|
51962
|
-
this.sessionFailureEpoch =
|
|
52179
|
+
this.sessionFailureEpoch = randomUUID7();
|
|
51963
52180
|
this.clientInfo = null;
|
|
51964
52181
|
this.clientCapabilities = null;
|
|
51965
52182
|
this.capabilities = ClientCapabilities.DEFAULT;
|
|
@@ -53252,6 +53469,8 @@ You have been logged out. Please try again.`);
|
|
|
53252
53469
|
} finally {
|
|
53253
53470
|
if (this.getSessionGeneration(params.sessionId) === closeGeneration) {
|
|
53254
53471
|
this.sessions.delete(params.sessionId);
|
|
53472
|
+
this.asyncQuestions.get(params.sessionId)?.dispose();
|
|
53473
|
+
this.asyncQuestions.delete(params.sessionId);
|
|
53255
53474
|
if (!this.v2PromptsInFlight.has(params.sessionId)) {
|
|
53256
53475
|
AcpV2Connection.of(this.connection)?.forgetSession(params.sessionId);
|
|
53257
53476
|
}
|
|
@@ -54028,7 +54247,7 @@ You have been logged out. Please try again.`);
|
|
|
54028
54247
|
});
|
|
54029
54248
|
const sessionState = this.getSessionState(params.sessionId);
|
|
54030
54249
|
this.assertSteerInputSupported(params, sessionState);
|
|
54031
|
-
const clientUserMessageId =
|
|
54250
|
+
const clientUserMessageId = randomUUID7();
|
|
54032
54251
|
const turnId = await this.getSteerableTurnId(sessionState);
|
|
54033
54252
|
if (turnId) {
|
|
54034
54253
|
const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState, clientUserMessageId);
|
|
@@ -55059,8 +55278,11 @@ You have been logged out. Please try again.`);
|
|
|
55059
55278
|
}
|
|
55060
55279
|
userInputToContentBlocks(input2) {
|
|
55061
55280
|
switch (input2.type) {
|
|
55062
|
-
case "text":
|
|
55281
|
+
case "text": {
|
|
55282
|
+
const asyncQuestionReply = renderAsyncQuestionReply(input2.text);
|
|
55283
|
+
if (asyncQuestionReply !== null) return [{ type: "text", text: asyncQuestionReply }];
|
|
55063
55284
|
return desktopAttachmentHistory(input2.text) ?? (input2.text.length > 0 ? [{ type: "text", text: input2.text }] : []);
|
|
55285
|
+
}
|
|
55064
55286
|
case "image":
|
|
55065
55287
|
return [{
|
|
55066
55288
|
type: "text",
|
|
@@ -55439,7 +55661,7 @@ You have been logged out. Please try again.`);
|
|
|
55439
55661
|
}
|
|
55440
55662
|
}
|
|
55441
55663
|
const promptKind = this.availableCommands.classifyPrompt(request.prompt);
|
|
55442
|
-
const messageId =
|
|
55664
|
+
const messageId = randomUUID7();
|
|
55443
55665
|
const session = new ACPSessionConnection(this.connection, sessionId);
|
|
55444
55666
|
let failure3 = null;
|
|
55445
55667
|
this.v2PromptsInFlight.add(sessionId);
|
|
@@ -55763,11 +55985,13 @@ You have been logged out. Please try again.`);
|
|
|
55763
55985
|
await observeInteraction(event);
|
|
55764
55986
|
if (!promptNotificationsActive) {
|
|
55765
55987
|
await promptEventHandler.handleSessionScopedNotification(event);
|
|
55988
|
+
this.asyncQuestionsFor(sessionState).handleNotification(event);
|
|
55766
55989
|
await this.trackCodexTurnCompletion(sessionState, event, promptEventHandler);
|
|
55767
55990
|
return;
|
|
55768
55991
|
}
|
|
55769
55992
|
const completesActiveTurn = event.method === "turn/completed" && event.params.threadId === sessionState.sessionId && event.params.turn.id === sessionState.currentTurnId;
|
|
55770
55993
|
await promptEventHandler.handleNotification(event);
|
|
55994
|
+
this.asyncQuestionsFor(sessionState).handleNotification(event);
|
|
55771
55995
|
if (completesActiveTurn) {
|
|
55772
55996
|
promptNotificationsActive = false;
|
|
55773
55997
|
}
|
|
@@ -56000,7 +56224,7 @@ You have been logged out. Please try again.`);
|
|
|
56000
56224
|
activePrompt.currentTurn = null;
|
|
56001
56225
|
sessionState.currentTurnId = null;
|
|
56002
56226
|
sessionState.interruptTurnId = null;
|
|
56003
|
-
const implementationClientUserMessageId = insertion !== void 0 ?
|
|
56227
|
+
const implementationClientUserMessageId = insertion !== void 0 ? randomUUID7() : void 0;
|
|
56004
56228
|
if (implementationClientUserMessageId !== void 0) {
|
|
56005
56229
|
registerSyntheticInsertion(implementationClientUserMessageId, implementationRequest.prompt);
|
|
56006
56230
|
}
|
|
@@ -56211,12 +56435,28 @@ You have been logged out. Please try again.`);
|
|
|
56211
56435
|
if (this.recovery === null || !lost.aborted || !sessionState.subagents.hasOutstanding()) return;
|
|
56212
56436
|
throw this.recovery.errorForGeneration(generation);
|
|
56213
56437
|
}
|
|
56438
|
+
/** The async questions of a session, created on first use. */
|
|
56439
|
+
asyncQuestionsFor(sessionState) {
|
|
56440
|
+
const sessionId = sessionState.sessionId;
|
|
56441
|
+
let questions = this.asyncQuestions.get(sessionId);
|
|
56442
|
+
if (questions === void 0) {
|
|
56443
|
+
questions = new AsyncQuestionElicitations(sessionId, this.clientCapabilities, new AsyncQuestionClient(
|
|
56444
|
+
sessionId,
|
|
56445
|
+
this.connection,
|
|
56446
|
+
this.clientCapabilities,
|
|
56447
|
+
() => this.codexAcpClient
|
|
56448
|
+
));
|
|
56449
|
+
this.asyncQuestions.set(sessionId, questions);
|
|
56450
|
+
}
|
|
56451
|
+
return questions;
|
|
56452
|
+
}
|
|
56214
56453
|
/**
|
|
56215
56454
|
* Ends what a prompt showed as running when its app-server died: open tool calls of the session and of its native
|
|
56216
56455
|
* subagent sessions, the subagent sessions themselves and compactions. Nothing will ever complete them.
|
|
56217
56456
|
*/
|
|
56218
56457
|
async finishPromptAfterAppServerLoss(sessionState, eventHandler, promptClient, elicitations, state) {
|
|
56219
56458
|
try {
|
|
56459
|
+
this.asyncQuestions.get(sessionState.sessionId)?.dispose();
|
|
56220
56460
|
await elicitations?.completeAllUrlElicitations();
|
|
56221
56461
|
const sessionIds = /* @__PURE__ */ new Set([sessionState.sessionId, ...sessionState.subagents.childSessionIds()]);
|
|
56222
56462
|
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.
|
|
6
|
+
"version": "2.5.0-alas.2",
|
|
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": "
|
|
84
|
+
"sourceCommit": "0f50c0b0345fd0e67b91ffc3c49d6b998d27da0c"
|
|
85
85
|
}
|
|
86
86
|
}
|