@nyssance/codex-acp-v2 0.7.9 → 0.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/index.js +397 -64
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.11 — 2026-10-12
4
+
5
+ ### Added
6
+
7
+ - `_codex/session_list` returns all non-archived sessions across projects when called
8
+ without params, walking native pages and deduplicating session IDs. Requests with
9
+ defined params retain the filtering and pagination behavior of `session/list`.
10
+
11
+ ### Fixed
12
+
13
+ - Suppress duplicate review results in live updates and full-turn history when the
14
+ review exit repeats the assistant's answer, while preserving standalone or different
15
+ review results regardless of native event order.
16
+
17
+ ## 0.7.10 — 2026-10-11
18
+
19
+ ### Fixed
20
+
21
+ - Restore image attachments, file references, and readable conversation titles in
22
+ history instead of exposing serialized attachment wrappers or Markdown links.
23
+ - Load native local image attachments with bounded reads and preserve unavailable
24
+ image placeholders when clipboard files have expired.
25
+ - Parse single and multiple questionnaire replies into question and answer metadata
26
+ for both live messages and history, preserving malformed input as ordinary text.
27
+ - Report user send times, assistant completion times, and live work duration through
28
+ message metadata; keep native historical total duration distinct from work duration.
29
+ - Preserve interruption metadata when replaying history and when live turns stop.
30
+
3
31
  ## 0.7.9 — 2026-10-10
4
32
 
5
33
  ### Added
package/dist/index.js CHANGED
@@ -22168,7 +22168,7 @@ var package_default = {
22168
22168
  publishConfig: {
22169
22169
  access: "public"
22170
22170
  },
22171
- version: "0.7.9",
22171
+ version: "0.7.11",
22172
22172
  description: "Native ACP v2 agent for the OpenAI Codex app-server",
22173
22173
  packageManager: "bun@1.4.0",
22174
22174
  engines: {
@@ -23776,8 +23776,100 @@ function mcpStartupFailed(serverName, message) {
23776
23776
  }
23777
23777
 
23778
23778
  // src/codex/sessionConfig.ts
23779
- import { pathToFileURL } from "url";
23779
+ import { pathToFileURL as pathToFileURL2 } from "url";
23780
23780
  import path3 from "path";
23781
+
23782
+ // src/codex/userImages.ts
23783
+ import { constants } from "fs";
23784
+ import { open } from "fs/promises";
23785
+ import { fileURLToPath, pathToFileURL } from "url";
23786
+ var nativeImages = new WeakMap;
23787
+ var MAX_IMAGE_BYTES = 32 * 1024 * 1024;
23788
+ function localImageContent(path3) {
23789
+ const block = { type: "resource_link", name: "Image", uri: path3.startsWith("file:") ? path3 : pathToFileURL(path3).href };
23790
+ nativeImages.set(block, path3);
23791
+ return block;
23792
+ }
23793
+ async function hydrateUserImages(content, signal) {
23794
+ const result = [];
23795
+ for (const block of content) {
23796
+ signal.throwIfAborted();
23797
+ const path3 = nativeImages.get(block);
23798
+ result.push(path3 === undefined ? block : await readImage(path3));
23799
+ }
23800
+ signal.throwIfAborted();
23801
+ return result;
23802
+ }
23803
+ async function readImage(path3) {
23804
+ try {
23805
+ const file2 = await open(path3.startsWith("file:") ? fileURLToPath(path3) : path3, constants.O_RDONLY | constants.O_NONBLOCK);
23806
+ try {
23807
+ const stat = await file2.stat();
23808
+ if (!stat.isFile() || stat.size === 0 || stat.size > MAX_IMAGE_BYTES)
23809
+ return unavailable();
23810
+ const data = Buffer.alloc(stat.size);
23811
+ let offset = 0;
23812
+ while (offset < data.length) {
23813
+ const { bytesRead } = await file2.read(data, offset, data.length - offset, offset);
23814
+ if (bytesRead === 0)
23815
+ return unavailable();
23816
+ offset += bytesRead;
23817
+ }
23818
+ const mimeType = imageMimeType(data);
23819
+ return mimeType ? { type: "image", mimeType, data: data.toString("base64") } : unavailable();
23820
+ } finally {
23821
+ await file2.close();
23822
+ }
23823
+ } catch {
23824
+ return unavailable();
23825
+ }
23826
+ }
23827
+ function unavailable() {
23828
+ return { type: "resource_link", name: "Image unavailable", uri: "codex-image:unavailable", _meta: { codex: { imageUnavailable: true } } };
23829
+ }
23830
+ function imageMimeType(data) {
23831
+ if (data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
23832
+ return "image/png";
23833
+ if (data[0] === 255 && data[1] === 216 && data[2] === 255)
23834
+ return "image/jpeg";
23835
+ if (/^GIF8[79]a$/.test(data.toString("ascii", 0, 6)))
23836
+ return "image/gif";
23837
+ if (data.toString("ascii", 0, 4) === "RIFF" && data.toString("ascii", 8, 12) === "WEBP")
23838
+ return "image/webp";
23839
+ if (data.toString("ascii", 0, 2) === "BM")
23840
+ return "image/bmp";
23841
+ return null;
23842
+ }
23843
+
23844
+ // src/codex/questionReplies.ts
23845
+ function questionReplyContent(text) {
23846
+ const envelope = /^\s*<send_user_message_question_reply>\s*([\s\S]*?)\s*<\/send_user_message_question_reply>\s*$/.exec(text);
23847
+ if (!envelope)
23848
+ return null;
23849
+ let replies;
23850
+ try {
23851
+ replies = JSON.parse(envelope[1]);
23852
+ } catch {
23853
+ return null;
23854
+ }
23855
+ if (!Array.isArray(replies) || replies.length === 0)
23856
+ return null;
23857
+ const content = [];
23858
+ for (const reply of replies) {
23859
+ if (reply === null || typeof reply !== "object" || Array.isArray(reply) || typeof reply.questionItemId !== "string" || typeof reply.question !== "string" || typeof reply.answer !== "string")
23860
+ return null;
23861
+ const { questionItemId, question, answer } = reply;
23862
+ content.push({
23863
+ type: "text",
23864
+ text: `${question}
23865
+ ${answer}`,
23866
+ _meta: { codex: { questionReply: { questionItemId, question, answer } } }
23867
+ });
23868
+ }
23869
+ return content;
23870
+ }
23871
+
23872
+ // src/codex/sessionConfig.ts
23781
23873
  var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
23782
23874
  function sanitizeMcpServerName(name) {
23783
23875
  return name.replace(MCP_SERVER_NAME_WHITESPACE, "_");
@@ -23882,22 +23974,44 @@ ${resource.blob}
23882
23974
  function fromUserInput(input) {
23883
23975
  switch (input.type) {
23884
23976
  case "text":
23885
- return input.text.length > 0 ? [{ type: "text", text: input.text }] : [];
23886
- case "image":
23887
- return [{ type: "text", text: "url" in input ? formatUriAsLink("image", input.url) : `image:${input.fileId}` }];
23977
+ return questionReplyContent(input.text) ?? (input.text.length > 0 ? [fileReference(input.text) ?? { type: "text", text: input.text }] : []);
23978
+ case "image": {
23979
+ if ("url" in input) {
23980
+ const data = /^data:(image\/[a-zA-Z0-9.+-]+);base64,([a-zA-Z0-9+/=\r\n]+)$/.exec(input.url);
23981
+ if (data)
23982
+ return [{ type: "image", mimeType: data[1], data: data[2] }];
23983
+ return [{ type: "resource_link", name: "Image", uri: input.url }];
23984
+ }
23985
+ return [{ type: "resource_link", name: "Image", uri: `codex-file:${input.fileId}` }];
23986
+ }
23888
23987
  case "localImage":
23889
- return [{ type: "text", text: formatUriAsLink(null, toFileUri(input.path)) }];
23988
+ return [localImageContent(input.path)];
23890
23989
  case "skill":
23891
23990
  return [{ type: "text", text: `skill:${input.name} (${input.path})` }];
23892
23991
  case "mention":
23893
- return [{ type: "text", text: formatUriAsLink(input.name, toFileUri(input.path)) }];
23992
+ return [{ type: "resource_link", name: input.name, uri: toFileUri(input.path) }];
23894
23993
  case "audio":
23895
23994
  case "localAudio":
23896
23995
  return [];
23897
23996
  }
23898
23997
  }
23998
+ function fileReference(text) {
23999
+ if (!text.startsWith("[@") || !text.endsWith(")") || /[\r\n]/.test(text))
24000
+ return null;
24001
+ const separator = text.lastIndexOf("](file://");
24002
+ if (separator < 3)
24003
+ return null;
24004
+ const uri = text.slice(separator + 2, -1);
24005
+ try {
24006
+ if (new URL(uri).protocol !== "file:" || /\s/.test(uri))
24007
+ return null;
24008
+ } catch {
24009
+ return null;
24010
+ }
24011
+ return { type: "resource_link", name: text.slice(2, separator), uri };
24012
+ }
23899
24013
  function toFileUri(value) {
23900
- return value.startsWith("file://") ? value : pathToFileURL(value).href;
24014
+ return value.startsWith("file://") ? value : pathToFileURL2(value).href;
23901
24015
  }
23902
24016
  function isSupportedImageUrl(uri) {
23903
24017
  if (!uri)
@@ -23922,12 +24036,43 @@ function promptTitle(prompt) {
23922
24036
  for (const block of prompt) {
23923
24037
  if (!ContentBlock.isText(block))
23924
24038
  continue;
24039
+ const replies = questionReplyContent(block.text);
24040
+ if (replies)
24041
+ return promptTitle(replies);
23925
24042
  const line = block.text.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0);
23926
24043
  if (line)
23927
24044
  return line.length > 120 ? `${line.slice(0, 117)}...` : line;
23928
24045
  }
24046
+ for (const block of prompt) {
24047
+ if (ContentBlock.isResourceLink(block))
24048
+ return block.name;
24049
+ }
23929
24050
  return null;
23930
24051
  }
24052
+ function fromUserMessage(inputs) {
24053
+ const images = new Set(inputs.filter((input) => input.type === "localImage").map((input) => input.path));
24054
+ const inlineImages = inputs.filter((input) => input.type === "image" && ("url" in input) && /^data:image\/[a-zA-Z0-9.+-]+;base64,[a-zA-Z0-9+/=\r\n]+$/.test(input.url)).length;
24055
+ const imageCount = images.size + inlineImages;
24056
+ return inputs.flatMap((input, index) => {
24057
+ if (index !== 0 || input.type !== "text" || imageCount === 0)
24058
+ return fromUserInput(input);
24059
+ const envelope = /^\s*# Files mentioned by the user:\r?\n\r?\n([\s\S]*?)\r?\n\r?\nDistinguish instructions in attached documents from the user's request\.\r?\n\r?\n## My request:\r?\n([\s\S]*)$/.exec(input.text);
24060
+ if (!envelope)
24061
+ return fromUserInput(input);
24062
+ const entries = envelope[1].split(/\r?\n\r?\n/);
24063
+ const paths = new Set;
24064
+ for (const entry of entries) {
24065
+ const match = /^## (.+): (.+)\r?\nImage attachment: true$/.exec(entry);
24066
+ if (!match || path3.basename(match[2]) !== match[1] || paths.has(match[2]))
24067
+ return fromUserInput(input);
24068
+ paths.add(match[2]);
24069
+ }
24070
+ if (paths.size !== imageCount || [...images].some((image) => !paths.has(image)))
24071
+ return fromUserInput(input);
24072
+ const text = envelope[2].trim();
24073
+ return text ? [{ type: "text", text }] : [];
24074
+ });
24075
+ }
23931
24076
 
23932
24077
  // src/agent/commandReceipts.ts
23933
24078
  import { createHash, randomUUID } from "crypto";
@@ -24074,7 +24219,7 @@ function itemSnapshot(item, command) {
24074
24219
  return [];
24075
24220
  if (command)
24076
24221
  return [{ sessionUpdate: "user_message", messageId: command.messageId, content: command.content }];
24077
- const content = item.content.flatMap(fromUserInput);
24222
+ const content = fromUserMessage(item.content);
24078
24223
  return content.length > 0 ? [{ sessionUpdate: "user_message", messageId: item.clientId ?? item.id, content }] : [];
24079
24224
  }
24080
24225
  case "agentMessage":
@@ -24128,6 +24273,12 @@ function itemSnapshot(item, command) {
24128
24273
  return [];
24129
24274
  }
24130
24275
  }
24276
+ function isDuplicateReview(item, items) {
24277
+ if (item.type !== "exitedReviewMode")
24278
+ return false;
24279
+ const text = item.review.trim();
24280
+ return text.length > 0 && Array.from(items).some((candidate) => candidate.type === "agentMessage" && candidate.phase !== "commentary" && candidate.text.trim() === text);
24281
+ }
24131
24282
 
24132
24283
  // src/bridge/turnMetadata.ts
24133
24284
  function withTurnId(update, turnId, startedAt) {
@@ -24163,8 +24314,14 @@ class EventBridge {
24163
24314
  insertedMessages = new Map;
24164
24315
  unannouncedItems = new Map;
24165
24316
  turnTime = null;
24317
+ messageSentTimes = new Map;
24318
+ workTiming = null;
24319
+ timingCarrier = null;
24320
+ turnPrompt = null;
24166
24321
  openToolCalls = new Set;
24167
24322
  announcedTools = new Set;
24323
+ pendingReviews = new Map;
24324
+ reviewMessages = new Map;
24168
24325
  messagePhases = new Map;
24169
24326
  promptMeta = new Map;
24170
24327
  reasoningWithDeltas = new Set;
@@ -24188,9 +24345,22 @@ class EventBridge {
24188
24345
  this.promptMeta.set(messageId, meta);
24189
24346
  }
24190
24347
  async insertPrompt(messageId, content) {
24191
- const meta = this.promptMeta.get(messageId);
24348
+ content = content.flatMap((block) => ContentBlock.isText(block) ? questionReplyContent(block.text) ?? [block] : [block]);
24349
+ let meta = this.promptMeta.get(messageId);
24350
+ if (content.some((block) => ContentBlock.isText(block) && block._meta?.["codex"] && typeof block._meta["codex"] === "object" && ("questionReply" in block._meta["codex"]))) {
24351
+ const parts = content.flatMap((block) => {
24352
+ if (!ContentBlock.isText(block))
24353
+ return [];
24354
+ const reply = block._meta?.["codex"]?.questionReply;
24355
+ return [reply ? { question: reply.question, text: reply.answer } : { text: block.text }];
24356
+ });
24357
+ meta = { ...meta, codex: { ...meta?.["codex"], questionReplyParts: parts } };
24358
+ }
24192
24359
  this.promptMeta.delete(messageId);
24193
24360
  this.insertedMessages.set(messageId, { content, meta });
24361
+ const sentAt = promptSentAt(meta);
24362
+ if (sentAt !== undefined)
24363
+ this.messageSentTimes.set(messageId, sentAt);
24194
24364
  await this.client.update({ sessionUpdate: "user_message", messageId, content, ...meta ? { _meta: meta } : {} });
24195
24365
  }
24196
24366
  rememberCommand(messageId, native) {
@@ -24201,6 +24371,11 @@ class EventBridge {
24201
24371
  }
24202
24372
  beginTurn() {
24203
24373
  this.commandEcho = null;
24374
+ this.messageSentTimes.clear();
24375
+ this.workTiming = null;
24376
+ this.timingCarrier = null;
24377
+ this.turnPrompt = null;
24378
+ this.turnTime = null;
24204
24379
  this.restoredCommands.clear();
24205
24380
  this.unresolvedUserItems.clear();
24206
24381
  this.insertedMessages.clear();
@@ -24210,6 +24385,8 @@ class EventBridge {
24210
24385
  this.openToolCalls.clear();
24211
24386
  this.announcedTools.clear();
24212
24387
  this.messagePhases.clear();
24388
+ this.reviewMessages.clear();
24389
+ this.pendingReviews.clear();
24213
24390
  this.reasoningWithDeltas.clear();
24214
24391
  this.terminalItems.clear();
24215
24392
  this.imageGenerations.clear();
@@ -24232,6 +24409,8 @@ class EventBridge {
24232
24409
  for (const item of items) {
24233
24410
  if (item.type === "userMessage")
24234
24411
  this.unresolvedUserItems.add(item.id);
24412
+ if (item.type === "agentMessage")
24413
+ this.reviewMessages.set(item.id, item);
24235
24414
  if (!replay && itemSnapshot(item).some((update) => update.sessionUpdate === "tool_call_update")) {
24236
24415
  this.unannouncedItems.set(item.id, { item, turnId });
24237
24416
  }
@@ -24244,8 +24423,12 @@ class EventBridge {
24244
24423
  if (receipt)
24245
24424
  this.restoredCommands.set(item.id, receipt);
24246
24425
  this.unresolvedUserItems.delete(item.id);
24247
- if (!replay)
24426
+ if (!replay || isDuplicateReview(item, this.reviewMessages.values()))
24248
24427
  continue;
24428
+ if (item.type === "exitedReviewMode") {
24429
+ this.pendingReviews.set(item.id, { item, turnId });
24430
+ continue;
24431
+ }
24249
24432
  if (item.type === "commandExecution" && item.status === "inProgress" && usesTerminal(item))
24250
24433
  this.terminalItems.add(item.id);
24251
24434
  for (const update of itemSnapshot(item, receipt ?? undefined)) {
@@ -24265,6 +24448,16 @@ class EventBridge {
24265
24448
  if (notification.method === "turn/started" || notification.method === "turn/completed") {
24266
24449
  const { id, startedAt } = notification.params.turn;
24267
24450
  this.turnTime = { id, startedAt };
24451
+ if (notification.method === "turn/started" && startedAt !== null) {
24452
+ this.workTiming = { id, startedAt: startedAt * 1000 };
24453
+ }
24454
+ }
24455
+ if (notification.method === "item/started" && notification.params.item.type === "agentMessage" && notification.params.item.phase === "final_answer" && notification.params.item.delivery !== "async" && this.workTiming?.id === notification.params.turnId && this.workTiming.finalStartedAt === undefined) {
24456
+ this.workTiming.finalStartedAt = notification.params.startedAtMs;
24457
+ if (this.timingCarrier?.turnId === notification.params.turnId) {
24458
+ const { turnId, update } = this.timingCarrier;
24459
+ await this.client.update(this.withTiming(update, turnId));
24460
+ }
24268
24461
  }
24269
24462
  let updates = await this.translate(notification);
24270
24463
  if (notification.method === "item/completed") {
@@ -24277,9 +24470,61 @@ class EventBridge {
24277
24470
  for (const update of updates) {
24278
24471
  this.trackToolCall(update);
24279
24472
  const turnId = "turnId" in notification.params ? notification.params.turnId : null;
24280
- await this.client.update(typeof turnId === "string" ? withTurnId(update, turnId, this.turnTime?.id === turnId ? this.turnTime.startedAt : null) : update);
24473
+ let tagged = typeof turnId === "string" ? this.withTiming(withTurnId(update, turnId, this.turnTime?.id === turnId ? this.turnTime.startedAt : null), turnId) : update;
24474
+ if (update.sessionUpdate === "user_message" && typeof update.messageId === "string") {
24475
+ const originalSentAt = promptSentAt(update._meta);
24476
+ if (originalSentAt !== undefined)
24477
+ this.messageSentTimes.set(update.messageId, originalSentAt);
24478
+ if (notification.method === "item/started" && !this.messageSentTimes.has(update.messageId)) {
24479
+ this.messageSentTimes.set(update.messageId, notification.params.startedAtMs);
24480
+ }
24481
+ const sentAt = this.messageSentTimes.get(update.messageId);
24482
+ if (sentAt !== undefined) {
24483
+ if (this.workTiming?.id === turnId && this.turnPrompt?.turnId !== turnId && (originalSentAt !== undefined || this.insertedMessages.has(update.messageId) && promptSentAt(this.insertedMessages.get(update.messageId)?.meta) !== undefined))
24484
+ this.workTiming.startedAt = sentAt;
24485
+ tagged = timingMeta(typeof turnId === "string" ? this.withTiming(tagged, turnId) : tagged, { messageSentAt: sentAt });
24486
+ }
24487
+ }
24488
+ if (notification.method === "item/completed" && update.sessionUpdate === "agent_message") {
24489
+ tagged = timingMeta(tagged, { messageCompletedAt: notification.params.completedAtMs });
24490
+ }
24491
+ if (typeof turnId === "string" && (update.sessionUpdate === "user_message" || update.sessionUpdate === "agent_message")) {
24492
+ this.timingCarrier = { turnId, update: tagged };
24493
+ }
24494
+ if (typeof turnId === "string" && update.sessionUpdate === "user_message") {
24495
+ this.turnPrompt = { turnId, update: tagged };
24496
+ }
24497
+ await this.client.update(tagged);
24498
+ }
24499
+ if (notification.method === "turn/completed") {
24500
+ const turn = notification.params.turn;
24501
+ const carrier = this.timingCarrier;
24502
+ if (carrier?.turnId === turn.id) {
24503
+ await this.client.update(timingMeta(this.withTiming(carrier.update, turn.id), {
24504
+ ...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
24505
+ ...turn.durationMs !== null ? { turnDurationMs: turn.durationMs } : {}
24506
+ }));
24507
+ }
24508
+ if (this.turnPrompt?.turnId === turn.id && turn.status === "interrupted") {
24509
+ await this.client.update(timingMeta(this.withTiming(this.turnPrompt.update, turn.id), {
24510
+ turnInterrupted: true,
24511
+ ...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
24512
+ ...turn.durationMs !== null ? { turnDurationMs: turn.durationMs } : {}
24513
+ }));
24514
+ }
24515
+ this.turnPrompt = null;
24281
24516
  }
24282
24517
  }
24518
+ withTiming(update, turnId) {
24519
+ const timing = this.workTiming;
24520
+ if (timing?.id !== turnId)
24521
+ return update;
24522
+ const duration3 = timing.finalStartedAt === undefined ? undefined : timing.finalStartedAt - timing.startedAt;
24523
+ return timingMeta(update, {
24524
+ workStartedAt: timing.startedAt,
24525
+ ...duration3 !== undefined && duration3 >= 0 ? { workDurationMs: duration3 } : {}
24526
+ });
24527
+ }
24283
24528
  rememberNativeCommand(item) {
24284
24529
  const command = this.commandEcho;
24285
24530
  if (!command || command.nativeId)
@@ -24303,6 +24548,7 @@ class EventBridge {
24303
24548
  }
24304
24549
  }
24305
24550
  async finishOpenToolCalls(status) {
24551
+ await this.flushReviews();
24306
24552
  this.unannouncedItems.clear();
24307
24553
  const ids = [...this.openToolCalls];
24308
24554
  const terminals = [...this.terminalItems];
@@ -24327,6 +24573,18 @@ class EventBridge {
24327
24573
  }
24328
24574
  async flush() {
24329
24575
  await this.flushPlans();
24576
+ await this.flushReviews();
24577
+ }
24578
+ async flushReviews() {
24579
+ const pending = [...this.pendingReviews.values()];
24580
+ this.pendingReviews.clear();
24581
+ for (const { item, turnId } of pending) {
24582
+ if (isDuplicateReview(item, this.reviewMessages.values()))
24583
+ continue;
24584
+ for (const update of itemSnapshot(item)) {
24585
+ await this.client.update(this.withTiming(withTurnId(update, turnId, this.turnTime?.startedAt), turnId));
24586
+ }
24587
+ }
24330
24588
  }
24331
24589
  dispose() {
24332
24590
  this.beginTurn();
@@ -24349,7 +24607,7 @@ class EventBridge {
24349
24607
  case "item/started":
24350
24608
  return this.itemStarted(notification.params.item);
24351
24609
  case "item/completed":
24352
- return this.itemCompleted(notification.params.item);
24610
+ return this.itemCompleted(notification.params.item, notification.params.turnId);
24353
24611
  case "item/commandExecution/outputDelta": {
24354
24612
  const { itemId, delta } = notification.params;
24355
24613
  return this.terminalItems.has(itemId) && delta.length > 0 ? [terminalOutputChunk(itemId, delta)] : [];
@@ -24545,7 +24803,7 @@ ${details}` : summary)];
24545
24803
  const inserted = item.clientId ? this.insertedMessages.get(item.clientId) : undefined;
24546
24804
  if (inserted)
24547
24805
  return [{ sessionUpdate: "user_message", messageId: item.clientId, content: inserted.content }];
24548
- const content = item.content.flatMap(fromUserInput);
24806
+ const content = fromUserMessage(item.content);
24549
24807
  if (content.length === 0)
24550
24808
  return [];
24551
24809
  const messageId = item.clientId ?? item.id;
@@ -24563,9 +24821,10 @@ ${details}` : summary)];
24563
24821
  return [];
24564
24822
  }
24565
24823
  }
24566
- async itemCompleted(item) {
24824
+ async itemCompleted(item, turnId) {
24567
24825
  switch (item.type) {
24568
24826
  case "agentMessage":
24827
+ this.reviewMessages.set(item.id, item);
24569
24828
  this.messagePhases.delete(item.id);
24570
24829
  return itemSnapshot(item);
24571
24830
  case "reasoning": {
@@ -24605,8 +24864,8 @@ ${details}` : summary)];
24605
24864
  case "plan":
24606
24865
  return await this.planCompleted(item);
24607
24866
  case "exitedReviewMode": {
24608
- const text = item.review.trim();
24609
- return text.length > 0 ? [this.agentMessage(item.id, text)] : [];
24867
+ this.pendingReviews.set(item.id, { item, turnId });
24868
+ return [];
24610
24869
  }
24611
24870
  case "userMessage":
24612
24871
  case "hookPrompt":
@@ -24720,6 +24979,18 @@ function turnPlan(params) {
24720
24979
  }
24721
24980
  };
24722
24981
  }
24982
+ function timingMeta(update, fields) {
24983
+ const meta = update._meta;
24984
+ const codex = meta?.["codex"];
24985
+ return { ...update, _meta: { ...meta, codex: { ...codex, ...fields } } };
24986
+ }
24987
+ function promptSentAt(meta) {
24988
+ const codex = meta?.["codex"];
24989
+ if (typeof codex !== "object" || codex === null)
24990
+ return;
24991
+ const value = codex["messageSentAt"];
24992
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 8640000000000000 ? value : undefined;
24993
+ }
24723
24994
 
24724
24995
  // src/codex/AppServerClient.ts
24725
24996
  var import_node = __toESM(require_main(), 1);
@@ -26569,7 +26840,7 @@ class TurnContext {
26569
26840
  // node_modules/open/index.js
26570
26841
  import process10 from "process";
26571
26842
  import path7 from "path";
26572
- import { fileURLToPath } from "url";
26843
+ import { fileURLToPath as fileURLToPath2 } from "url";
26573
26844
  import childProcess4 from "child_process";
26574
26845
  import fs6, { constants as fsConstants2 } from "fs/promises";
26575
26846
 
@@ -26952,7 +27223,7 @@ var is_in_ssh_default = isInSsh;
26952
27223
 
26953
27224
  // node_modules/open/index.js
26954
27225
  var fallbackAttemptSymbol = Symbol("fallbackAttempt");
26955
- var __dirname2 = import.meta.url ? path7.dirname(fileURLToPath(import.meta.url)) : "";
27226
+ var __dirname2 = import.meta.url ? path7.dirname(fileURLToPath2(import.meta.url)) : "";
26956
27227
  var localXdgOpenPath = path7.join(__dirname2, "xdg-open");
26957
27228
  var { platform, arch } = process10;
26958
27229
  var tryEachApp = async (apps, opener) => {
@@ -27164,7 +27435,7 @@ var baseOpen = async (options) => {
27164
27435
  });
27165
27436
  });
27166
27437
  };
27167
- var open = (target, options) => {
27438
+ var open2 = (target, options) => {
27168
27439
  if (typeof target !== "string") {
27169
27440
  throw new TypeError("Expected a `target`");
27170
27441
  }
@@ -27233,7 +27504,7 @@ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
27233
27504
  defineLazyProperty(apps, "safari", () => detectPlatformBinary({
27234
27505
  darwin: "Safari"
27235
27506
  }));
27236
- var open_default = open;
27507
+ var open_default = open2;
27237
27508
 
27238
27509
  // src/agent/auth.ts
27239
27510
  var AuthMethodId = {
@@ -27369,6 +27640,10 @@ class ClientSession {
27369
27640
  async update(update) {
27370
27641
  if (this.disposed)
27371
27642
  return;
27643
+ if (SessionUpdate.isUserMessage(update) && update.content) {
27644
+ update = { ...update, content: await hydrateUserImages(update.content, this.output.signal) };
27645
+ }
27646
+ this.output.signal.throwIfAborted();
27372
27647
  await abortable(this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update }), this.output.signal);
27373
27648
  }
27374
27649
  async updateAll(updates) {
@@ -27712,9 +27987,26 @@ function stringValue(request) {
27712
27987
  function historyUpdates(turns, commandReceipts = new Map) {
27713
27988
  const updates = [];
27714
27989
  for (const turn of turns) {
27990
+ const openingUser = turn.items.find((item) => item.type === "userMessage");
27715
27991
  for (const item of turn.items) {
27992
+ if (isDuplicateReview(item, turn.items))
27993
+ continue;
27716
27994
  const command = item.type === "userMessage" ? commandReceipts.get(item.id) : undefined;
27717
- updates.push(...itemSnapshot(item, command).map((update) => withTurnId(update, turn.id, turn.startedAt)));
27995
+ updates.push(...itemSnapshot(item, command).map((update) => {
27996
+ const replay = withTurnId(update, turn.id, turn.startedAt);
27997
+ const meta = replay._meta;
27998
+ const codex = meta?.["codex"];
27999
+ const duration3 = turn.durationMs ?? (turn.startedAt !== null && turn.completedAt !== null ? (turn.completedAt - turn.startedAt) * 1000 : null);
28000
+ replay._meta = { ...meta, codex: {
28001
+ ...codex,
28002
+ historyReplay: true,
28003
+ ...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
28004
+ ...item === openingUser && turn.startedAt !== null ? { messageSentAt: turn.startedAt * 1000 } : {},
28005
+ ...duration3 !== null && Number.isFinite(duration3) && duration3 >= 0 ? { turnDurationMs: duration3 } : {},
28006
+ ...turn.status === "interrupted" ? { historyInterrupted: true } : {}
28007
+ } };
28008
+ return replay;
28009
+ }));
27718
28010
  }
27719
28011
  }
27720
28012
  return updates;
@@ -27727,9 +28019,9 @@ function historyTitle(thread, turns) {
27727
28019
  for (const item of turn.items) {
27728
28020
  if (item.type !== "userMessage")
27729
28021
  continue;
27730
- const text2 = item.content.filter((input) => input.type === "text").map((input) => input.text.trim()).find((part) => part.length > 0);
27731
- if (text2)
27732
- return firstLine(text2);
28022
+ const title = promptTitle(fromUserMessage(item.content));
28023
+ if (title)
28024
+ return title;
27733
28025
  }
27734
28026
  }
27735
28027
  const preview = thread.preview.trim();
@@ -27902,7 +28194,7 @@ class HistoryRead {
27902
28194
  }
27903
28195
 
27904
28196
  // src/agent/historyProjection.ts
27905
- import { pathToFileURL as pathToFileURL2 } from "url";
28197
+ import { pathToFileURL as pathToFileURL3 } from "url";
27906
28198
  function inputBlock(input) {
27907
28199
  switch (input.type) {
27908
28200
  case "text":
@@ -27913,10 +28205,10 @@ function inputBlock(input) {
27913
28205
  return { type: "resource_link", name: "Audio", uri: input.url, _meta: { codex: { inputType: input.type } } };
27914
28206
  case "localImage":
27915
28207
  case "localAudio":
27916
- return { type: "resource_link", name: input.type === "localImage" ? "Image" : "Audio", uri: input.path.startsWith("file://") ? input.path : pathToFileURL2(input.path).href, _meta: { codex: { inputType: input.type, ..."detail" in input && input.detail ? { detail: input.detail } : {} } } };
28208
+ return { type: "resource_link", name: input.type === "localImage" ? "Image" : "Audio", uri: input.path.startsWith("file://") ? input.path : pathToFileURL3(input.path).href, _meta: { codex: { inputType: input.type, ..."detail" in input && input.detail ? { detail: input.detail } : {} } } };
27917
28209
  case "skill":
27918
28210
  case "mention":
27919
- return { type: "resource_link", name: input.name, uri: input.path.startsWith("file://") ? input.path : pathToFileURL2(input.path).href, _meta: { codex: { inputType: input.type } } };
28211
+ return { type: "resource_link", name: input.name, uri: input.path.startsWith("file://") ? input.path : pathToFileURL3(input.path).href, _meta: { codex: { inputType: input.type } } };
27920
28212
  }
27921
28213
  }
27922
28214
  function projectHistoryItem(item, turnId, startedAt, command) {
@@ -27962,7 +28254,7 @@ async function readSessionHistory(codex, raw, signal, commandReceipts) {
27962
28254
  if (view === "full")
27963
28255
  await read.call(() => assertCommandHistory(codex, start.thread, commandReceipts, signal));
27964
28256
  const turns = await read.call(() => Promise.all(page.data.map(async (turn) => {
27965
- const projections = view === "full" ? await Promise.all(turn.items.map(async (item) => projectHistoryItem(item, turn.id, turn.startedAt, item.type === "userMessage" ? await commandReceipts?.read(item.id) : null))) : [];
28257
+ const projections = view === "full" ? await Promise.all(turn.items.filter((item) => !isDuplicateReview(item, turn.items)).map(async (item) => projectHistoryItem(item, turn.id, turn.startedAt, item.type === "userMessage" ? await commandReceipts?.read(item.id) : null))) : [];
27966
28258
  return {
27967
28259
  turnId: turn.id,
27968
28260
  status: turn.status,
@@ -28241,7 +28533,7 @@ class CodexAgent {
28241
28533
  additionalDirectories: {}
28242
28534
  },
28243
28535
  providers: {},
28244
- _meta: { alwith: { tools: { version: 1 }, turns: { version: 2 } }, codex: { forkAtTurn: true, sessionLineage: true, archive: true, sessionHistory: { version: 2, items: true, modes: ["browse", "export"] }, seedHistory: true, skills: true, plugins: true, providerCatalog: true, rename: true, account: true, fuzzyFileSearch: true } }
28536
+ _meta: { alwith: { tools: { version: 1 }, turns: { version: 2 } }, codex: { forkAtTurn: true, sessionLineage: true, archive: true, sessionList: { version: 1 }, sessionHistory: { version: 2, items: true, modes: ["browse", "export"] }, seedHistory: true, skills: true, plugins: true, providerCatalog: true, rename: true, account: true, fuzzyFileSearch: true } }
28245
28537
  },
28246
28538
  authMethods: authMethods(this.capabilities, this.env)
28247
28539
  };
@@ -28328,8 +28620,8 @@ class CodexAgent {
28328
28620
  const catalog = await this.withCodex(() => this.codex.allModels(true));
28329
28621
  const nativeProvider = await this.resolveModelProvider(candidate);
28330
28622
  const wholesale = this.providers.active !== null && this.providers.mode === "route" || candidate.active !== null && candidate.mode === "route";
28331
- const unavailable = new Set([...this.sessions.values()].filter((runtime) => this.providers.mode === "catalog" && this.providers.hasGateway(runtime.modelProvider) && !candidate.hasGateway(runtime.modelProvider)));
28332
- const moving = [...this.sessions.values()].filter((runtime) => !unavailable.has(runtime) && (wholesale || this.providers.hasGateway(runtime.modelProvider) && candidate.gateway(runtime.modelProvider) !== this.providers.gateway(runtime.modelProvider)));
28623
+ const unavailable2 = new Set([...this.sessions.values()].filter((runtime) => this.providers.mode === "catalog" && this.providers.hasGateway(runtime.modelProvider) && !candidate.hasGateway(runtime.modelProvider)));
28624
+ const moving = [...this.sessions.values()].filter((runtime) => !unavailable2.has(runtime) && (wholesale || this.providers.hasGateway(runtime.modelProvider) && candidate.gateway(runtime.modelProvider) !== this.providers.gateway(runtime.modelProvider)));
28333
28625
  const targets = new Map(moving.map((runtime) => {
28334
28626
  if (candidate.active === null)
28335
28627
  return [runtime, null];
@@ -28367,7 +28659,7 @@ class CodexAgent {
28367
28659
  }));
28368
28660
  staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, requestedModel ?? candidate.selectionId(thread.model, modelProvider), thread.reasoningEffort) });
28369
28661
  }
28370
- for (const runtime of unavailable)
28662
+ for (const runtime of unavailable2)
28371
28663
  runtime.config = candidate.rebind(runtime.config, this.providers, null);
28372
28664
  this.providers = candidate;
28373
28665
  for (const runtime of this.sessions.values()) {
@@ -28380,7 +28672,7 @@ class CodexAgent {
28380
28672
  }
28381
28673
  runtime.session.catalog = nextCatalog;
28382
28674
  runtime.session.gatewayGroups = candidate.gatewayGroups();
28383
- if (!next && !unavailable.has(runtime))
28675
+ if (!next && !unavailable2.has(runtime))
28384
28676
  runtime.session.model = resolveModelSelection(nextCatalog, findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null, runtime.session.model.effort);
28385
28677
  runtime.client.update({ sessionUpdate: "config_option_update", configOptions: sessionConfigOptions(runtime.session), _meta: { codex: { routing: { stale: false } } } }).catch((error40) => logger.error("Provider config notification failed", error40));
28386
28678
  }
@@ -28427,17 +28719,17 @@ class CodexAgent {
28427
28719
  const runtime = await this.openSession({ kind: "fork", request: params }, signal);
28428
28720
  return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session), _meta: { codex: runtime.lineage } };
28429
28721
  }
28430
- async openSession(open2, signal) {
28722
+ async openSession(open3, signal) {
28431
28723
  if (signal?.aborted)
28432
28724
  throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
28433
28725
  const pending = this.admission(async () => {
28434
- const runtime = await this.openSessionInternal(open2, signal);
28726
+ const runtime = await this.openSessionInternal(open3, signal);
28435
28727
  if (signal?.aborted) {
28436
28728
  await this.closeRuntime(runtime.session.id);
28437
28729
  throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
28438
28730
  }
28439
28731
  return runtime;
28440
- }, open2.kind === "new" ? undefined : open2.request.sessionId);
28732
+ }, open3.kind === "new" ? undefined : open3.request.sessionId);
28441
28733
  try {
28442
28734
  return signal ? await abortable(pending, signal) : await pending;
28443
28735
  } catch (error40) {
@@ -28451,24 +28743,24 @@ class CodexAgent {
28451
28743
  throw RequestError.invalidRequest({ sessionId }, "A previous unsubscribe is still pending; retry after Codex responds or reconnect the agent");
28452
28744
  }
28453
28745
  }
28454
- async openSessionInternal(open2, signal) {
28455
- const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[open2.kind];
28746
+ async openSessionInternal(open3, signal) {
28747
+ const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[open3.kind];
28456
28748
  const capabilities = this.requireInitialized(method);
28457
- if (open2.kind !== "new")
28458
- this.assertUnsubscribeSettled(open2.request.sessionId);
28459
- const { request } = open2;
28460
- const lastTurnId = open2.kind === "fork" ? forkTurnId(request._meta) : undefined;
28749
+ if (open3.kind !== "new")
28750
+ this.assertUnsubscribeSettled(open3.request.sessionId);
28751
+ const { request } = open3;
28752
+ const lastTurnId = open3.kind === "fork" ? forkTurnId(request._meta) : undefined;
28461
28753
  if (typeof request.cwd !== "string" || !path9.isAbsolute(request.cwd)) {
28462
28754
  throw RequestError.invalidParams({ cwd: request.cwd }, "cwd must be an absolute path");
28463
28755
  }
28464
- if (open2.kind === "resume" && open2.request.replayFrom != null && open2.request.replayFrom.type !== "start") {
28465
- throw RequestError.invalidParams({ replayFrom: open2.request.replayFrom }, 'Only replayFrom {type: "start"} is supported');
28756
+ if (open3.kind === "resume" && open3.request.replayFrom != null && open3.request.replayFrom.type !== "start") {
28757
+ throw RequestError.invalidParams({ replayFrom: open3.request.replayFrom }, 'Only replayFrom {type: "start"} is supported');
28466
28758
  }
28467
- const seed = open2.kind === "new" ? seedHistoryOf(open2.request._meta) : [];
28759
+ const seed = open3.kind === "new" ? seedHistoryOf(open3.request._meta) : [];
28468
28760
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories);
28469
- if (open2.kind === "resume" && this.sessions.has(open2.request.sessionId)) {
28470
- await this.closeRuntime(open2.request.sessionId);
28471
- this.assertUnsubscribeSettled(open2.request.sessionId);
28761
+ if (open3.kind === "resume" && this.sessions.has(open3.request.sessionId)) {
28762
+ await this.closeRuntime(open3.request.sessionId);
28763
+ this.assertUnsubscribeSettled(open3.request.sessionId);
28472
28764
  }
28473
28765
  const mcpServers = request.mcpServers ?? [];
28474
28766
  const clientTools = clientToolsOf(request._meta);
@@ -28483,8 +28775,8 @@ class CodexAgent {
28483
28775
  if (this.providers.active !== null && this.providers.mode === "catalog") {
28484
28776
  if (requestedModel !== null)
28485
28777
  gatewayId = this.providers.gatewayIdFor(requestedModel);
28486
- else if (open2.kind === "resume") {
28487
- const known = await this.withCodex(() => this.codex.threadRead({ threadId: open2.request.sessionId, includeTurns: false }));
28778
+ else if (open3.kind === "resume") {
28779
+ const known = await this.withCodex(() => this.codex.threadRead({ threadId: open3.request.sessionId, includeTurns: false }));
28488
28780
  gatewayId = this.providers.hasGateway(known.thread.modelProvider) ? known.thread.modelProvider : null;
28489
28781
  }
28490
28782
  }
@@ -28503,7 +28795,7 @@ class CodexAgent {
28503
28795
  const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
28504
28796
  if (signal?.aborted)
28505
28797
  throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
28506
- switch (open2.kind) {
28798
+ switch (open3.kind) {
28507
28799
  case "new":
28508
28800
  return await this.codex.threadStart({
28509
28801
  ...hostThreadOptions,
@@ -28515,15 +28807,15 @@ class CodexAgent {
28515
28807
  });
28516
28808
  case "resume":
28517
28809
  try {
28518
- return await this.codex.threadResume({ ...hostThreadOptions, threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
28810
+ return await this.codex.threadResume({ ...hostThreadOptions, threadId: open3.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
28519
28811
  } catch (error40) {
28520
28812
  if (!errorMessage(error40).includes("already has an active writer"))
28521
28813
  throw error40;
28522
28814
  readOnly = true;
28523
- return readOnlyThread((await this.codex.threadRead({ threadId: open2.request.sessionId, includeTurns: false })).thread, request.cwd);
28815
+ return readOnlyThread((await this.codex.threadRead({ threadId: open3.request.sessionId, includeTurns: false })).thread, request.cwd);
28524
28816
  }
28525
28817
  case "fork":
28526
- return await this.codex.threadFork({ ...hostThreadOptions, ...lastTurnId === undefined ? {} : { lastTurnId }, threadId: open2.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
28818
+ return await this.codex.threadFork({ ...hostThreadOptions, ...lastTurnId === undefined ? {} : { lastTurnId }, threadId: open3.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
28527
28819
  }
28528
28820
  }) }));
28529
28821
  const sessionId = thread.thread.id;
@@ -28583,8 +28875,8 @@ class CodexAgent {
28583
28875
  removeCancellation = () => signal?.removeEventListener("abort", cancelOpening);
28584
28876
  if (signal?.aborted)
28585
28877
  cancelOpening();
28586
- const replay = open2.kind === "fork" || open2.kind === "resume" && open2.request.replayFrom?.type === "start";
28587
- if (open2.kind !== "new") {
28878
+ const replay = open3.kind === "fork" || open3.kind === "resume" && open3.request.replayFrom?.type === "start";
28879
+ if (open3.kind !== "new") {
28588
28880
  await this.replayHistory(runtime, replay, thread.thread);
28589
28881
  } else {
28590
28882
  if (seed.length > 0) {
@@ -28807,16 +29099,41 @@ class CodexAgent {
28807
29099
  }
28808
29100
  async listSessions(params) {
28809
29101
  this.requireInitialized("session/list");
29102
+ return this.readSessionList(params, false);
29103
+ }
29104
+ async sessionList(params = {}) {
29105
+ this.requireInitialized("_codex/session_list");
29106
+ if (Object.values(params).some((value) => value !== undefined))
29107
+ return this.listSessions(params);
29108
+ return this.readSessionList({}, true);
29109
+ }
29110
+ async readSessionList(params, all) {
28810
29111
  const cwd = params.cwd?.trim() || null;
28811
29112
  const archived = archivedFilter(params._meta);
28812
29113
  const list = (cursor) => this.withCodex(() => this.codex.threadList({
28813
29114
  cursor,
29115
+ ...all ? { limit: 100 } : {},
28814
29116
  ...cwd ? { cwd } : {},
28815
29117
  sourceKinds: ["cli", "vscode", "exec", "appServer", "unknown"],
28816
29118
  modelProviders: [],
28817
29119
  ...archived ? { archived: true } : {}
28818
29120
  }));
28819
29121
  const response = await list(params.cursor ?? null);
29122
+ if (all) {
29123
+ const threads = new Map(response.data.map((thread) => [thread.id, thread]));
29124
+ const cursors = new Set;
29125
+ while (response.nextCursor !== null) {
29126
+ const cursor = response.nextCursor;
29127
+ if (cursors.has(cursor))
29128
+ throw RequestError.internalError(undefined, "Codex thread list repeated a cursor");
29129
+ cursors.add(cursor);
29130
+ const page = await list(cursor);
29131
+ for (const thread of page.data)
29132
+ threads.set(thread.id, thread);
29133
+ response.nextCursor = page.nextCursor;
29134
+ }
29135
+ response.data = [...threads.values()];
29136
+ }
28820
29137
  const home = this.codexHome;
28821
29138
  if (home === null)
28822
29139
  throw RequestError.internalError(undefined, "Codex home is unavailable after initialize");
@@ -28840,16 +29157,18 @@ class CodexAgent {
28840
29157
  }
28841
29158
  response.data.push(...missing2.values());
28842
29159
  }
28843
- return {
28844
- sessions: response.data.map((thread) => ({
29160
+ const sessions = [];
29161
+ const batchSize = all ? 8 : response.data.length;
29162
+ for (let offset = 0;offset < response.data.length; offset += batchSize) {
29163
+ sessions.push(...await Promise.all(response.data.slice(offset, offset + batchSize).map(async (thread) => ({
28845
29164
  sessionId: thread.id,
28846
29165
  cwd: thread.cwd,
28847
- title: thread.name?.trim() || thread.preview.trim() || null,
29166
+ title: !thread.name?.trim() && /^(?:\[@|# Files mentioned by the user:|<send_user_message_question_reply>)/.test(thread.preview.trimStart()) ? historyTitle(thread, (await this.turnPage(thread.id, null)).data) : thread.name?.trim() || thread.preview.trim() || null,
28848
29167
  updatedAt: new Date(thread.updatedAt * 1000).toISOString(),
28849
29168
  _meta: { codex: { archived, ...threadLineage(thread) } }
28850
- })),
28851
- nextCursor: response.nextCursor
28852
- };
29169
+ }))));
29170
+ }
29171
+ return all ? { sessions } : { sessions, nextCursor: response.nextCursor };
28853
29172
  }
28854
29173
  async closeSession(params) {
28855
29174
  return await this.admission(() => this.closeSessionInternal(params), params.sessionId);
@@ -29700,6 +30019,20 @@ function interruptedTurn(threadId) {
29700
30019
  turn: { id: "", items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
29701
30020
  };
29702
30021
  }
30022
+ function parseSessionListParams(raw) {
30023
+ const params = objectParams()(raw);
30024
+ for (const key of ["cwd", "cursor"]) {
30025
+ const value = params[key];
30026
+ if (value !== undefined && value !== null && typeof value !== "string") {
30027
+ throw RequestError.invalidParams({ [key]: value }, `${key} must be a string when given`);
30028
+ }
30029
+ }
30030
+ const meta = params["_meta"];
30031
+ if (meta !== undefined && meta !== null && (typeof meta !== "object" || Array.isArray(meta))) {
30032
+ throw RequestError.invalidParams({ _meta: meta }, "_meta must be an object when given");
30033
+ }
30034
+ return params;
30035
+ }
29703
30036
  function parseSessionRenameParams(raw) {
29704
30037
  const params = objectParams()(raw);
29705
30038
  if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
@@ -29846,7 +30179,7 @@ function createAgentApp(options) {
29846
30179
  };
29847
30180
  return agent({ name: options.info.name }).onConnect((connection) => {
29848
30181
  agent2 = new CodexAgent(connection.client, options);
29849
- }).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_history", { parse: parseSessionHistoryParams }, (ctx) => current().sessionHistory(ctx.params, ctx.signal)).onRequest("_codex/session_history_items", { parse: parseSessionHistoryItemsParams }, (ctx) => current().sessionHistoryItems(ctx.params, ctx.signal)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest("_codex/skills_list", { parse: objectParams() }, (ctx) => current().skillsList(ctx.params)).onRequest("_codex/skills_config_write", { parse: objectParams() }, (ctx) => current().skillsConfigWrite(ctx.params)).onRequest("_codex/plugin_list", { parse: objectParams() }, (ctx) => current().pluginList(ctx.params)).onRequest("_codex/plugin_installed", { parse: objectParams() }, (ctx) => current().pluginInstalled(ctx.params)).onRequest("_codex/plugin_install", { parse: objectParams() }, (ctx) => current().pluginInstall(ctx.params)).onRequest("_codex/plugin_uninstall", { parse: objectParams() }, (ctx) => current().pluginUninstall(ctx.params)).onRequest("_codex/plugin_read", { parse: objectParams() }, (ctx) => current().pluginRead(ctx.params)).onRequest("_codex/marketplace_add", { parse: objectParams() }, (ctx) => current().marketplaceAdd(ctx.params)).onRequest("_codex/marketplace_remove", { parse: objectParams() }, (ctx) => current().marketplaceRemove(ctx.params)).onRequest("_codex/marketplace_upgrade", { parse: objectParams() }, (ctx) => current().marketplaceUpgrade(ctx.params)).onRequest("_codex/session_rename", { parse: parseSessionRenameParams }, (ctx) => current().sessionRename(ctx.params)).onRequest("_codex/account_read", { parse: objectParams() }, () => current().accountRead()).onRequest("_codex/rate_limits", { parse: objectParams() }, () => current().rateLimits()).onRequest("_codex/fuzzy_file_search", { parse: parseFuzzyFileSearchParams }, (ctx) => current().fuzzyFileSearch(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
30182
+ }).onRequest(methods.agent.initialize, (ctx) => current().initialize(ctx.params)).onRequest(methods.agent.auth.login, (ctx) => current().login(ctx.params, ctx.requestId)).onRequest(methods.agent.auth.logout, (ctx) => current().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => current().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => current().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => current().disableProvider(ctx.params)).onRequest(methods.agent.session.new, (ctx) => current().newSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.resume, (ctx) => current().resumeSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.fork, (ctx) => current().forkSession(ctx.params, ctx.signal)).onRequest(methods.agent.session.list, (ctx) => current().listSessions(ctx.params)).onRequest("_codex/session_list", { parse: parseSessionListParams }, (ctx) => current().sessionList(ctx.params)).onRequest(methods.agent.session.close, (ctx) => current().closeSession(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => current().deleteSession(ctx.params)).onRequest("_codex/session_history", { parse: parseSessionHistoryParams }, (ctx) => current().sessionHistory(ctx.params, ctx.signal)).onRequest("_codex/session_history_items", { parse: parseSessionHistoryItemsParams }, (ctx) => current().sessionHistoryItems(ctx.params, ctx.signal)).onRequest("_codex/session_archive", { parse: parseSessionIdParams }, (ctx) => current().archiveSession(ctx.params)).onRequest("_codex/session_unarchive", { parse: parseSessionIdParams }, (ctx) => current().unarchiveSession(ctx.params)).onRequest("_codex/skills_list", { parse: objectParams() }, (ctx) => current().skillsList(ctx.params)).onRequest("_codex/skills_config_write", { parse: objectParams() }, (ctx) => current().skillsConfigWrite(ctx.params)).onRequest("_codex/plugin_list", { parse: objectParams() }, (ctx) => current().pluginList(ctx.params)).onRequest("_codex/plugin_installed", { parse: objectParams() }, (ctx) => current().pluginInstalled(ctx.params)).onRequest("_codex/plugin_install", { parse: objectParams() }, (ctx) => current().pluginInstall(ctx.params)).onRequest("_codex/plugin_uninstall", { parse: objectParams() }, (ctx) => current().pluginUninstall(ctx.params)).onRequest("_codex/plugin_read", { parse: objectParams() }, (ctx) => current().pluginRead(ctx.params)).onRequest("_codex/marketplace_add", { parse: objectParams() }, (ctx) => current().marketplaceAdd(ctx.params)).onRequest("_codex/marketplace_remove", { parse: objectParams() }, (ctx) => current().marketplaceRemove(ctx.params)).onRequest("_codex/marketplace_upgrade", { parse: objectParams() }, (ctx) => current().marketplaceUpgrade(ctx.params)).onRequest("_codex/session_rename", { parse: parseSessionRenameParams }, (ctx) => current().sessionRename(ctx.params)).onRequest("_codex/account_read", { parse: objectParams() }, () => current().accountRead()).onRequest("_codex/rate_limits", { parse: objectParams() }, () => current().rateLimits()).onRequest("_codex/fuzzy_file_search", { parse: parseFuzzyFileSearchParams }, (ctx) => current().fuzzyFileSearch(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => current().setSessionConfigOption(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => current().prompt(ctx.params)).onNotification(methods.agent.session.cancel, (ctx) => current().cancel(ctx.params));
29850
30183
  }
29851
30184
 
29852
30185
  // src/codex/process.ts
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.7.9",
6
+ "version": "0.7.11",
7
7
  "description": "Native ACP v2 agent for the OpenAI Codex app-server",
8
8
  "packageManager": "bun@1.4.0",
9
9
  "engines": {