@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.
- package/README.md +1 -0
- package/dist/index.js +479 -143
- 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.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: "
|
|
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
|
|
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
|
-
|
|
40937
|
-
|
|
40938
|
-
|
|
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
|
|
40941
|
-
const
|
|
40942
|
-
|
|
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
|
|
40945
|
-
const
|
|
40946
|
-
|
|
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" ||
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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.
|
|
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": "
|
|
84
|
+
"sourceCommit": "de8d4c961871fc0584ca1bd89f31a8de95066c65"
|
|
85
85
|
}
|
|
86
86
|
}
|