@agentconnect.md/codex-acp 2.0.1-agentconnect.1 → 2.1.1-agentconnect.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +140 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7266,7 +7266,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
7266
7266
  }
7267
7267
  return propValues;
7268
7268
  });
7269
- const isObject2 = isObject;
7269
+ const isObject3 = isObject;
7270
7270
  const catchall = def.catchall;
7271
7271
  let value;
7272
7272
  const memo2 = globalConfig.memoizer;
@@ -7274,7 +7274,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
7274
7274
  inst._zod.parse = (payload, ctx) => {
7275
7275
  value ?? (value = _normalized.value);
7276
7276
  const input2 = payload.value;
7277
- if (!isObject2(input2)) {
7277
+ if (!isObject3(input2)) {
7278
7278
  payload.issues.push({
7279
7279
  expected: "object",
7280
7280
  code: "invalid_type",
@@ -7410,7 +7410,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
7410
7410
  return doc.compile();
7411
7411
  };
7412
7412
  let fastpass;
7413
- const isObject2 = isObject;
7413
+ const isObject3 = isObject;
7414
7414
  const jit = !globalConfig.jitless;
7415
7415
  const allowsEval2 = allowsEval;
7416
7416
  const fastEnabled = jit && allowsEval2.value;
@@ -7419,7 +7419,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
7419
7419
  inst._zod.parse = (payload, ctx) => {
7420
7420
  value ?? (value = _normalized.value);
7421
7421
  const input2 = payload.value;
7422
- if (!isObject2(input2)) {
7422
+ if (!isObject3(input2)) {
7423
7423
  payload.issues.push({
7424
7424
  expected: "object",
7425
7425
  code: "invalid_type",
@@ -27419,6 +27419,8 @@ var AIR_RAW_INPUT_RENDERING_KEY = "rawInputRendering";
27419
27419
  var AIR_PLAN_CONTENT_DELTA_KEY = "planContentDelta";
27420
27420
  var AIR_CONTENT_DELTA_KEY = "contentDelta";
27421
27421
  var AIR_SUBAGENT_KEY = "subagent";
27422
+ var AIR_CUSTOM_ANSWER_KEY = "customAnswer";
27423
+ var LEGACY_AIR_CUSTOM_ANSWER_KEY = "_askUserQuestionCustomAnswer";
27422
27424
  var AIR_EXTENSION_VERSION = 1;
27423
27425
  function withAirMeta(meta3, key, value) {
27424
27426
  const root = asRecord(meta3);
@@ -30028,6 +30030,34 @@ function createSessionNotice(severity, title, description) {
30028
30030
  };
30029
30031
  }
30030
30032
 
30033
+ // src/ServiceErrorMessage.ts
30034
+ var SERVICE_ERROR_TYPES = /* @__PURE__ */ new Set([
30035
+ "invalid_request_error",
30036
+ "server_error",
30037
+ "rate_limit_error",
30038
+ "insufficient_quota",
30039
+ "authentication_error",
30040
+ "permission_error",
30041
+ "not_found_error",
30042
+ "conflict_error",
30043
+ "overloaded_error"
30044
+ ]);
30045
+ function readableServiceErrorMessage(text) {
30046
+ let value;
30047
+ try {
30048
+ value = JSON.parse(text);
30049
+ } catch {
30050
+ return text;
30051
+ }
30052
+ if (!isObject2(value) || Object.keys(value).some((key) => !["type", "status", "error"].includes(key)) || value["type"] !== "error" || typeof value["status"] !== "number" || !Number.isInteger(value["status"]) || value["status"] < 400 || value["status"] > 599 || !isObject2(value["error"])) return text;
30053
+ const error62 = value["error"];
30054
+ if (Object.keys(error62).some((key) => !["type", "message", "code", "param"].includes(key)) || typeof error62["type"] !== "string" || !SERVICE_ERROR_TYPES.has(error62["type"]) || typeof error62["message"] !== "string" || error62["message"].trim().length === 0 || ["code", "param"].some((key) => key in error62 && error62[key] !== null && typeof error62[key] !== "string")) return text;
30055
+ return error62["message"];
30056
+ }
30057
+ function isObject2(value) {
30058
+ return value !== null && typeof value === "object" && !Array.isArray(value);
30059
+ }
30060
+
30031
30061
  // src/CodexEventHandler.ts
30032
30062
  var MAX_SESSION_FAILURE_TITLE_LENGTH = 240;
30033
30063
  var SESSION_FAILURE_POLICY = {
@@ -30821,7 +30851,7 @@ ${event.details}` : event.summary;
30821
30851
  this.createTurnErrorData(params.error)
30822
30852
  );
30823
30853
  }
30824
- return createAgentTextMessageChunk(`${params.error.message}
30854
+ return createAgentTextMessageChunk(`${readableServiceErrorMessage(params.error.message)}
30825
30855
 
30826
30856
  `);
30827
30857
  }
@@ -30839,7 +30869,7 @@ ${event.details}` : event.summary;
30839
30869
  revision: nextSessionFailureRevision(previous, id),
30840
30870
  category: policy.category,
30841
30871
  severity,
30842
- title,
30872
+ title: readableServiceErrorMessage(title),
30843
30873
  actions: actionsOverride ?? policy.actions
30844
30874
  };
30845
30875
  this.failuresById.set(id, failure3);
@@ -30994,6 +31024,58 @@ function toolCallTitle(update) {
30994
31024
  return update.title;
30995
31025
  }
30996
31026
 
31027
+ // src/DesktopAttachmentHistory.ts
31028
+ import { pathToFileURL } from "node:url";
31029
+ function attachmentFileUri(path8) {
31030
+ if (path8.startsWith("file://")) {
31031
+ try {
31032
+ return new URL(path8).href;
31033
+ } catch {
31034
+ return null;
31035
+ }
31036
+ }
31037
+ if (/^[A-Za-z]:[\\/]/.test(path8)) {
31038
+ return `file:///${path8.replaceAll("\\", "/").split("/").map(encodeURIComponent).join("/").replace(/^([A-Za-z])%3A/, "$1:")}`;
31039
+ }
31040
+ if (/^\\\\[^\\]+\\/.test(path8)) {
31041
+ const [host, ...segments] = path8.slice(2).split("\\");
31042
+ try {
31043
+ return new URL(`file://${host}/${segments.map(encodeURIComponent).join("/")}`).href;
31044
+ } catch {
31045
+ return null;
31046
+ }
31047
+ }
31048
+ return path8.startsWith("/") ? pathToFileURL(path8).href : null;
31049
+ }
31050
+ function desktopAttachmentHistory(text) {
31051
+ const envelope = /^\s*# Files (?:pasted|mentioned) by the user:\r?\n/.exec(text);
31052
+ if (!envelope) return null;
31053
+ const request = /^## My request:\r?\n/m.exec(text);
31054
+ if (!request || request.index < envelope[0].length) return null;
31055
+ const lines = text.slice(envelope[0].length, request.index).split(/\r?\n/);
31056
+ const attachments = [];
31057
+ for (const line of lines) {
31058
+ if (line.trim() === "" || /^# Files (?:pasted|mentioned) by the user:$/.test(line) || line === "Pasted text contains the user's request." || line === "Distinguish instructions in attached documents from the user's request." || line === "Image attachment: true" && attachments.length > 0) continue;
31059
+ const attachment = /^## ("(?:\\.|[^"\\])*"|[^"].*?): ((?:\/|\\\\|[A-Za-z]:[\\/]|file:\/\/).+)$/.exec(line);
31060
+ if (!attachment) return null;
31061
+ let title = attachment[1];
31062
+ if (title.startsWith('"')) {
31063
+ try {
31064
+ title = JSON.parse(title);
31065
+ } catch {
31066
+ return null;
31067
+ }
31068
+ }
31069
+ const uri = attachmentFileUri(attachment[2]);
31070
+ if (title.length === 0 || uri === null) return null;
31071
+ attachments.push({ type: "resource_link", name: title, uri });
31072
+ }
31073
+ if (attachments.length === 0) return null;
31074
+ const requestText = text.slice(request.index + request[0].length);
31075
+ if (requestText.trim().length > 0) attachments.push({ type: "text", text: requestText });
31076
+ return attachments;
31077
+ }
31078
+
30997
31079
  // src/permissions/option-ids.ts
30998
31080
  var ApprovalOptionId = {
30999
31081
  AllowOnce: "allow_once",
@@ -31799,6 +31881,12 @@ function userInputNoteFieldId(questionId, questionIds) {
31799
31881
  }
31800
31882
  return fieldId;
31801
31883
  }
31884
+ function typedChoiceAnswer(value, question) {
31885
+ if (typeof value !== "string" || value === USER_INPUT_OTHER_OPTION) {
31886
+ return void 0;
31887
+ }
31888
+ return question.options?.some((option) => option.label === value) ? void 0 : value;
31889
+ }
31802
31890
  function userInputResponseValue(content, fieldId) {
31803
31891
  const value = content[fieldId];
31804
31892
  if (typeof value === "string" && value.trim() === "") {
@@ -32027,6 +32115,7 @@ var CodexElicitationHandler = class {
32027
32115
  const properties = {};
32028
32116
  const required2 = [];
32029
32117
  const questionIds = new Set(params.questions.map((question) => question.id));
32118
+ const airClient = isAirClient(this.clientCapabilities);
32030
32119
  for (const question of params.questions) {
32031
32120
  const options = question.options ?? [];
32032
32121
  const hasOptions = options.length > 0;
@@ -32051,7 +32140,7 @@ var CodexElicitationHandler = class {
32051
32140
  title: option.label,
32052
32141
  ...option.description ? { description: option.description } : {}
32053
32142
  })),
32054
- ...hasOtherAnswer && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
32143
+ ...hasOtherAnswer && !airClient && !options.some((option) => option.label === USER_INPUT_OTHER_OPTION) ? [{
32055
32144
  const: USER_INPUT_OTHER_OPTION,
32056
32145
  title: USER_INPUT_OTHER_OPTION,
32057
32146
  description: "Provide a different answer in the note field."
@@ -32062,16 +32151,17 @@ var CodexElicitationHandler = class {
32062
32151
  type: "string"
32063
32152
  };
32064
32153
  if (hasOtherAnswer) {
32154
+ const noteMeta = {
32155
+ codex: {
32156
+ questionId: question.id,
32157
+ role: "user_note",
32158
+ isSecret: question.isSecret
32159
+ }
32160
+ };
32065
32161
  properties[userInputNoteFieldId(question.id, questionIds)] = {
32066
32162
  type: "string",
32067
32163
  title: "Additional answer or note",
32068
- _meta: {
32069
- codex: {
32070
- questionId: question.id,
32071
- role: "user_note",
32072
- isSecret: question.isSecret
32073
- }
32074
- }
32164
+ _meta: airClient ? withAirMeta({ ...noteMeta, [LEGACY_AIR_CUSTOM_ANSWER_KEY]: true }, AIR_CUSTOM_ANSWER_KEY, true) : noteMeta
32075
32165
  };
32076
32166
  }
32077
32167
  }
@@ -32129,13 +32219,18 @@ var CodexElicitationHandler = class {
32129
32219
  const answers = {};
32130
32220
  const content = contentRecord(response.content);
32131
32221
  const questionIds = new Set(params.questions.map((question) => question.id));
32222
+ const airClient = isAirClient(this.clientCapabilities);
32132
32223
  for (const question of params.questions) {
32133
32224
  const answerValues = [];
32225
+ const hasOtherAnswer = question.isOther && question.options != null && question.options.length > 0;
32134
32226
  const value = userInputResponseValue(content, question.id);
32135
- if (value !== void 0) {
32227
+ const typedAnswer = airClient && hasOtherAnswer ? typedChoiceAnswer(value, question) : void 0;
32228
+ if (typedAnswer !== void 0) {
32229
+ answerValues.push(USER_INPUT_OTHER_OPTION, `${USER_INPUT_NOTE_PREFIX}${typedAnswer.trim()}`);
32230
+ } else if (value !== void 0) {
32136
32231
  answerValues.push(...Array.isArray(value) ? value.map(String) : [String(value)]);
32137
32232
  }
32138
- if (question.isOther && question.options != null && question.options.length > 0) {
32233
+ if (hasOtherAnswer) {
32139
32234
  const note = userInputResponseValue(content, userInputNoteFieldId(question.id, questionIds));
32140
32235
  if (note !== void 0) {
32141
32236
  const notes = Array.isArray(note) ? note : [note];
@@ -33089,7 +33184,7 @@ var package_default = {
33089
33184
  publishConfig: {
33090
33185
  access: "public"
33091
33186
  },
33092
- version: "2.0.1-agentconnect.1",
33187
+ version: "2.1.1-agentconnect.1",
33093
33188
  description: "",
33094
33189
  main: "dist/index.js",
33095
33190
  bin: {
@@ -33463,6 +33558,15 @@ function isNoActiveTurnError(err) {
33463
33558
  function isUnknownThreadError(err) {
33464
33559
  return isMissingRolloutError(err) || isThreadNotLoadedError(err) || isInvalidThreadIdError(err);
33465
33560
  }
33561
+ function isThreadActiveWriterError(err) {
33562
+ return /\bthread \S+ already has an active writer\b/.test(errorText(err));
33563
+ }
33564
+ function threadActiveWriterRequestError(threadId, err) {
33565
+ return RequestError.invalidRequest(
33566
+ { reason: "thread_active_writer", threadId, details: errorText(err) },
33567
+ "This Codex session is in use by another Codex client (the Codex app, the CLI or an IDE extension). Close the session there or quit that client, then try again."
33568
+ );
33569
+ }
33466
33570
 
33467
33571
  // src/PermissionProfileConfig.ts
33468
33572
  var PERMISSION_PROFILE_CONFIG_ENV = "CODEX_ACP_PERMISSION_PROFILE_CONFIG";
@@ -33873,6 +33977,9 @@ var CodexAcpClient = class {
33873
33977
  * so `thread/read` answers for it and gives back the same state resume
33874
33978
  * would have. A thread id Codex has genuinely never seen fails both calls,
33875
33979
  * and the original resume error is what the caller sees.
33980
+ *
33981
+ * A thread that another Codex client has loaded fails with a clear ACP
33982
+ * error instead of the raw Codex message.
33876
33983
  */
33877
33984
  async resumeThread(params) {
33878
33985
  try {
@@ -33888,6 +33995,7 @@ var CodexAcpClient = class {
33888
33995
  collaborationMode: response.collaborationMode?.mode ?? null
33889
33996
  };
33890
33997
  } catch (err) {
33998
+ if (isThreadActiveWriterError(err)) throw threadActiveWriterRequestError(params.threadId, err);
33891
33999
  if (!isMissingRolloutError(err)) throw err;
33892
34000
  let response;
33893
34001
  try {
@@ -39136,8 +39244,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
39136
39244
  createUserMessageUpdates(item) {
39137
39245
  const updates = [];
39138
39246
  const messageId = item.id;
39139
- for (const input2 of item.content) {
39140
- const blocks = this.userInputToContentBlocks(input2);
39247
+ const contentBlocks = item.content.map((input2) => this.userInputToContentBlocks(input2));
39248
+ const attachmentUris = new Set(contentBlocks.flatMap((blocks, index) => item.content[index]?.type === "text" ? blocks.flatMap((block) => block.type === "resource_link" ? [block.uri] : []) : []));
39249
+ for (const [index, input2] of item.content.entries()) {
39250
+ const nativePath = input2.type === "localImage" || input2.type === "localAudio" || input2.type === "mention" ? input2.path : (input2.type === "image" || input2.type === "audio") && "url" in input2 ? input2.url : null;
39251
+ if (nativePath !== null && attachmentUris.has(attachmentFileUri(nativePath) ?? "")) continue;
39252
+ const blocks = contentBlocks[index];
39141
39253
  for (const block of blocks) {
39142
39254
  updates.push(createUserMessageChunk(block, messageId));
39143
39255
  }
@@ -39178,22 +39290,24 @@ Check ${configPath} and project .codex directories, especially their config.toml
39178
39290
  userInputToContentBlocks(input2) {
39179
39291
  switch (input2.type) {
39180
39292
  case "text":
39181
- return input2.text.length > 0 ? [{ type: "text", text: input2.text }] : [];
39293
+ return desktopAttachmentHistory(input2.text) ?? (input2.text.length > 0 ? [{ type: "text", text: input2.text }] : []);
39182
39294
  case "image":
39183
39295
  return [{
39184
39296
  type: "text",
39185
39297
  text: "url" in input2 ? this.formatUriAsLink("image", input2.url) : `image:${input2.fileId}`
39186
39298
  }];
39187
- case "localImage": {
39188
- const uri = input2.path.startsWith("file://") ? input2.path : `file://${input2.path}`;
39189
- return [{ type: "text", text: this.formatUriAsLink(null, uri) }];
39299
+ case "localImage":
39300
+ case "localAudio":
39301
+ case "mention": {
39302
+ const uri = attachmentFileUri(input2.path);
39303
+ const fileName = input2.path.split(/[\\/]/).pop() || input2.type;
39304
+ const name = input2.type === "mention" && input2.name.trim().length > 0 ? input2.name : fileName;
39305
+ return uri !== null ? [{ type: "resource_link", name, uri }] : [{ type: "text", text: this.formatUriAsLink(name, input2.path) }];
39190
39306
  }
39191
39307
  case "skill":
39192
39308
  return [{ type: "text", text: `skill:${input2.name} (${input2.path})` }];
39193
39309
  case "audio":
39194
- case "localAudio":
39195
- case "mention":
39196
- return [];
39310
+ return [{ type: "text", text: this.formatUriAsLink("audio", input2.url) }];
39197
39311
  }
39198
39312
  }
39199
39313
  formatUriAsLink(name, uri) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.0.1-agentconnect.1",
6
+ "version": "2.1.1-agentconnect.1",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {