@nyssance/codex-acp-v2 0.7.9 → 0.7.10
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/CHANGELOG.md +14 -0
- package/dist/index.js +313 -53
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.10 — 2026-10-11
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Restore image attachments, file references, and readable conversation titles in
|
|
8
|
+
history instead of exposing serialized attachment wrappers or Markdown links.
|
|
9
|
+
- Load native local image attachments with bounded reads and preserve unavailable
|
|
10
|
+
image placeholders when clipboard files have expired.
|
|
11
|
+
- Parse single and multiple questionnaire replies into question and answer metadata
|
|
12
|
+
for both live messages and history, preserving malformed input as ordinary text.
|
|
13
|
+
- Report user send times, assistant completion times, and live work duration through
|
|
14
|
+
message metadata; keep native historical total duration distinct from work duration.
|
|
15
|
+
- Preserve interruption metadata when replaying history and when live turns stop.
|
|
16
|
+
|
|
3
17
|
## 0.7.9 — 2026-10-10
|
|
4
18
|
|
|
5
19
|
### 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.
|
|
22171
|
+
version: "0.7.10",
|
|
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
|
-
|
|
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 [
|
|
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: "
|
|
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 :
|
|
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
|
|
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":
|
|
@@ -24163,6 +24308,10 @@ class EventBridge {
|
|
|
24163
24308
|
insertedMessages = new Map;
|
|
24164
24309
|
unannouncedItems = new Map;
|
|
24165
24310
|
turnTime = null;
|
|
24311
|
+
messageSentTimes = new Map;
|
|
24312
|
+
workTiming = null;
|
|
24313
|
+
timingCarrier = null;
|
|
24314
|
+
turnPrompt = null;
|
|
24166
24315
|
openToolCalls = new Set;
|
|
24167
24316
|
announcedTools = new Set;
|
|
24168
24317
|
messagePhases = new Map;
|
|
@@ -24188,9 +24337,22 @@ class EventBridge {
|
|
|
24188
24337
|
this.promptMeta.set(messageId, meta);
|
|
24189
24338
|
}
|
|
24190
24339
|
async insertPrompt(messageId, content) {
|
|
24191
|
-
|
|
24340
|
+
content = content.flatMap((block) => ContentBlock.isText(block) ? questionReplyContent(block.text) ?? [block] : [block]);
|
|
24341
|
+
let meta = this.promptMeta.get(messageId);
|
|
24342
|
+
if (content.some((block) => ContentBlock.isText(block) && block._meta?.["codex"] && typeof block._meta["codex"] === "object" && ("questionReply" in block._meta["codex"]))) {
|
|
24343
|
+
const parts = content.flatMap((block) => {
|
|
24344
|
+
if (!ContentBlock.isText(block))
|
|
24345
|
+
return [];
|
|
24346
|
+
const reply = block._meta?.["codex"]?.questionReply;
|
|
24347
|
+
return [reply ? { question: reply.question, text: reply.answer } : { text: block.text }];
|
|
24348
|
+
});
|
|
24349
|
+
meta = { ...meta, codex: { ...meta?.["codex"], questionReplyParts: parts } };
|
|
24350
|
+
}
|
|
24192
24351
|
this.promptMeta.delete(messageId);
|
|
24193
24352
|
this.insertedMessages.set(messageId, { content, meta });
|
|
24353
|
+
const sentAt = promptSentAt(meta);
|
|
24354
|
+
if (sentAt !== undefined)
|
|
24355
|
+
this.messageSentTimes.set(messageId, sentAt);
|
|
24194
24356
|
await this.client.update({ sessionUpdate: "user_message", messageId, content, ...meta ? { _meta: meta } : {} });
|
|
24195
24357
|
}
|
|
24196
24358
|
rememberCommand(messageId, native) {
|
|
@@ -24201,6 +24363,11 @@ class EventBridge {
|
|
|
24201
24363
|
}
|
|
24202
24364
|
beginTurn() {
|
|
24203
24365
|
this.commandEcho = null;
|
|
24366
|
+
this.messageSentTimes.clear();
|
|
24367
|
+
this.workTiming = null;
|
|
24368
|
+
this.timingCarrier = null;
|
|
24369
|
+
this.turnPrompt = null;
|
|
24370
|
+
this.turnTime = null;
|
|
24204
24371
|
this.restoredCommands.clear();
|
|
24205
24372
|
this.unresolvedUserItems.clear();
|
|
24206
24373
|
this.insertedMessages.clear();
|
|
@@ -24265,6 +24432,16 @@ class EventBridge {
|
|
|
24265
24432
|
if (notification.method === "turn/started" || notification.method === "turn/completed") {
|
|
24266
24433
|
const { id, startedAt } = notification.params.turn;
|
|
24267
24434
|
this.turnTime = { id, startedAt };
|
|
24435
|
+
if (notification.method === "turn/started" && startedAt !== null) {
|
|
24436
|
+
this.workTiming = { id, startedAt: startedAt * 1000 };
|
|
24437
|
+
}
|
|
24438
|
+
}
|
|
24439
|
+
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) {
|
|
24440
|
+
this.workTiming.finalStartedAt = notification.params.startedAtMs;
|
|
24441
|
+
if (this.timingCarrier?.turnId === notification.params.turnId) {
|
|
24442
|
+
const { turnId, update } = this.timingCarrier;
|
|
24443
|
+
await this.client.update(this.withTiming(update, turnId));
|
|
24444
|
+
}
|
|
24268
24445
|
}
|
|
24269
24446
|
let updates = await this.translate(notification);
|
|
24270
24447
|
if (notification.method === "item/completed") {
|
|
@@ -24277,9 +24454,61 @@ class EventBridge {
|
|
|
24277
24454
|
for (const update of updates) {
|
|
24278
24455
|
this.trackToolCall(update);
|
|
24279
24456
|
const turnId = "turnId" in notification.params ? notification.params.turnId : null;
|
|
24280
|
-
|
|
24457
|
+
let tagged = typeof turnId === "string" ? this.withTiming(withTurnId(update, turnId, this.turnTime?.id === turnId ? this.turnTime.startedAt : null), turnId) : update;
|
|
24458
|
+
if (update.sessionUpdate === "user_message" && typeof update.messageId === "string") {
|
|
24459
|
+
const originalSentAt = promptSentAt(update._meta);
|
|
24460
|
+
if (originalSentAt !== undefined)
|
|
24461
|
+
this.messageSentTimes.set(update.messageId, originalSentAt);
|
|
24462
|
+
if (notification.method === "item/started" && !this.messageSentTimes.has(update.messageId)) {
|
|
24463
|
+
this.messageSentTimes.set(update.messageId, notification.params.startedAtMs);
|
|
24464
|
+
}
|
|
24465
|
+
const sentAt = this.messageSentTimes.get(update.messageId);
|
|
24466
|
+
if (sentAt !== undefined) {
|
|
24467
|
+
if (this.workTiming?.id === turnId && this.turnPrompt?.turnId !== turnId && (originalSentAt !== undefined || this.insertedMessages.has(update.messageId) && promptSentAt(this.insertedMessages.get(update.messageId)?.meta) !== undefined))
|
|
24468
|
+
this.workTiming.startedAt = sentAt;
|
|
24469
|
+
tagged = timingMeta(typeof turnId === "string" ? this.withTiming(tagged, turnId) : tagged, { messageSentAt: sentAt });
|
|
24470
|
+
}
|
|
24471
|
+
}
|
|
24472
|
+
if (notification.method === "item/completed" && update.sessionUpdate === "agent_message") {
|
|
24473
|
+
tagged = timingMeta(tagged, { messageCompletedAt: notification.params.completedAtMs });
|
|
24474
|
+
}
|
|
24475
|
+
if (typeof turnId === "string" && (update.sessionUpdate === "user_message" || update.sessionUpdate === "agent_message")) {
|
|
24476
|
+
this.timingCarrier = { turnId, update: tagged };
|
|
24477
|
+
}
|
|
24478
|
+
if (typeof turnId === "string" && update.sessionUpdate === "user_message") {
|
|
24479
|
+
this.turnPrompt = { turnId, update: tagged };
|
|
24480
|
+
}
|
|
24481
|
+
await this.client.update(tagged);
|
|
24482
|
+
}
|
|
24483
|
+
if (notification.method === "turn/completed") {
|
|
24484
|
+
const turn = notification.params.turn;
|
|
24485
|
+
const carrier = this.timingCarrier;
|
|
24486
|
+
if (carrier?.turnId === turn.id) {
|
|
24487
|
+
await this.client.update(timingMeta(this.withTiming(carrier.update, turn.id), {
|
|
24488
|
+
...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
|
|
24489
|
+
...turn.durationMs !== null ? { turnDurationMs: turn.durationMs } : {}
|
|
24490
|
+
}));
|
|
24491
|
+
}
|
|
24492
|
+
if (this.turnPrompt?.turnId === turn.id && turn.status === "interrupted") {
|
|
24493
|
+
await this.client.update(timingMeta(this.withTiming(this.turnPrompt.update, turn.id), {
|
|
24494
|
+
turnInterrupted: true,
|
|
24495
|
+
...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
|
|
24496
|
+
...turn.durationMs !== null ? { turnDurationMs: turn.durationMs } : {}
|
|
24497
|
+
}));
|
|
24498
|
+
}
|
|
24499
|
+
this.turnPrompt = null;
|
|
24281
24500
|
}
|
|
24282
24501
|
}
|
|
24502
|
+
withTiming(update, turnId) {
|
|
24503
|
+
const timing = this.workTiming;
|
|
24504
|
+
if (timing?.id !== turnId)
|
|
24505
|
+
return update;
|
|
24506
|
+
const duration3 = timing.finalStartedAt === undefined ? undefined : timing.finalStartedAt - timing.startedAt;
|
|
24507
|
+
return timingMeta(update, {
|
|
24508
|
+
workStartedAt: timing.startedAt,
|
|
24509
|
+
...duration3 !== undefined && duration3 >= 0 ? { workDurationMs: duration3 } : {}
|
|
24510
|
+
});
|
|
24511
|
+
}
|
|
24283
24512
|
rememberNativeCommand(item) {
|
|
24284
24513
|
const command = this.commandEcho;
|
|
24285
24514
|
if (!command || command.nativeId)
|
|
@@ -24545,7 +24774,7 @@ ${details}` : summary)];
|
|
|
24545
24774
|
const inserted = item.clientId ? this.insertedMessages.get(item.clientId) : undefined;
|
|
24546
24775
|
if (inserted)
|
|
24547
24776
|
return [{ sessionUpdate: "user_message", messageId: item.clientId, content: inserted.content }];
|
|
24548
|
-
const content = item.content
|
|
24777
|
+
const content = fromUserMessage(item.content);
|
|
24549
24778
|
if (content.length === 0)
|
|
24550
24779
|
return [];
|
|
24551
24780
|
const messageId = item.clientId ?? item.id;
|
|
@@ -24720,6 +24949,18 @@ function turnPlan(params) {
|
|
|
24720
24949
|
}
|
|
24721
24950
|
};
|
|
24722
24951
|
}
|
|
24952
|
+
function timingMeta(update, fields) {
|
|
24953
|
+
const meta = update._meta;
|
|
24954
|
+
const codex = meta?.["codex"];
|
|
24955
|
+
return { ...update, _meta: { ...meta, codex: { ...codex, ...fields } } };
|
|
24956
|
+
}
|
|
24957
|
+
function promptSentAt(meta) {
|
|
24958
|
+
const codex = meta?.["codex"];
|
|
24959
|
+
if (typeof codex !== "object" || codex === null)
|
|
24960
|
+
return;
|
|
24961
|
+
const value = codex["messageSentAt"];
|
|
24962
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 8640000000000000 ? value : undefined;
|
|
24963
|
+
}
|
|
24723
24964
|
|
|
24724
24965
|
// src/codex/AppServerClient.ts
|
|
24725
24966
|
var import_node = __toESM(require_main(), 1);
|
|
@@ -26569,7 +26810,7 @@ class TurnContext {
|
|
|
26569
26810
|
// node_modules/open/index.js
|
|
26570
26811
|
import process10 from "process";
|
|
26571
26812
|
import path7 from "path";
|
|
26572
|
-
import { fileURLToPath } from "url";
|
|
26813
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26573
26814
|
import childProcess4 from "child_process";
|
|
26574
26815
|
import fs6, { constants as fsConstants2 } from "fs/promises";
|
|
26575
26816
|
|
|
@@ -26952,7 +27193,7 @@ var is_in_ssh_default = isInSsh;
|
|
|
26952
27193
|
|
|
26953
27194
|
// node_modules/open/index.js
|
|
26954
27195
|
var fallbackAttemptSymbol = Symbol("fallbackAttempt");
|
|
26955
|
-
var __dirname2 = import.meta.url ? path7.dirname(
|
|
27196
|
+
var __dirname2 = import.meta.url ? path7.dirname(fileURLToPath2(import.meta.url)) : "";
|
|
26956
27197
|
var localXdgOpenPath = path7.join(__dirname2, "xdg-open");
|
|
26957
27198
|
var { platform, arch } = process10;
|
|
26958
27199
|
var tryEachApp = async (apps, opener) => {
|
|
@@ -27164,7 +27405,7 @@ var baseOpen = async (options) => {
|
|
|
27164
27405
|
});
|
|
27165
27406
|
});
|
|
27166
27407
|
};
|
|
27167
|
-
var
|
|
27408
|
+
var open2 = (target, options) => {
|
|
27168
27409
|
if (typeof target !== "string") {
|
|
27169
27410
|
throw new TypeError("Expected a `target`");
|
|
27170
27411
|
}
|
|
@@ -27233,7 +27474,7 @@ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
|
|
|
27233
27474
|
defineLazyProperty(apps, "safari", () => detectPlatformBinary({
|
|
27234
27475
|
darwin: "Safari"
|
|
27235
27476
|
}));
|
|
27236
|
-
var open_default =
|
|
27477
|
+
var open_default = open2;
|
|
27237
27478
|
|
|
27238
27479
|
// src/agent/auth.ts
|
|
27239
27480
|
var AuthMethodId = {
|
|
@@ -27369,6 +27610,10 @@ class ClientSession {
|
|
|
27369
27610
|
async update(update) {
|
|
27370
27611
|
if (this.disposed)
|
|
27371
27612
|
return;
|
|
27613
|
+
if (SessionUpdate.isUserMessage(update) && update.content) {
|
|
27614
|
+
update = { ...update, content: await hydrateUserImages(update.content, this.output.signal) };
|
|
27615
|
+
}
|
|
27616
|
+
this.output.signal.throwIfAborted();
|
|
27372
27617
|
await abortable(this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update }), this.output.signal);
|
|
27373
27618
|
}
|
|
27374
27619
|
async updateAll(updates) {
|
|
@@ -27712,9 +27957,24 @@ function stringValue(request) {
|
|
|
27712
27957
|
function historyUpdates(turns, commandReceipts = new Map) {
|
|
27713
27958
|
const updates = [];
|
|
27714
27959
|
for (const turn of turns) {
|
|
27960
|
+
const openingUser = turn.items.find((item) => item.type === "userMessage");
|
|
27715
27961
|
for (const item of turn.items) {
|
|
27716
27962
|
const command = item.type === "userMessage" ? commandReceipts.get(item.id) : undefined;
|
|
27717
|
-
updates.push(...itemSnapshot(item, command).map((update) =>
|
|
27963
|
+
updates.push(...itemSnapshot(item, command).map((update) => {
|
|
27964
|
+
const replay = withTurnId(update, turn.id, turn.startedAt);
|
|
27965
|
+
const meta = replay._meta;
|
|
27966
|
+
const codex = meta?.["codex"];
|
|
27967
|
+
const duration3 = turn.durationMs ?? (turn.startedAt !== null && turn.completedAt !== null ? (turn.completedAt - turn.startedAt) * 1000 : null);
|
|
27968
|
+
replay._meta = { ...meta, codex: {
|
|
27969
|
+
...codex,
|
|
27970
|
+
historyReplay: true,
|
|
27971
|
+
...turn.completedAt !== null ? { turnCompletedAt: turn.completedAt * 1000 } : {},
|
|
27972
|
+
...item === openingUser && turn.startedAt !== null ? { messageSentAt: turn.startedAt * 1000 } : {},
|
|
27973
|
+
...duration3 !== null && Number.isFinite(duration3) && duration3 >= 0 ? { turnDurationMs: duration3 } : {},
|
|
27974
|
+
...turn.status === "interrupted" ? { historyInterrupted: true } : {}
|
|
27975
|
+
} };
|
|
27976
|
+
return replay;
|
|
27977
|
+
}));
|
|
27718
27978
|
}
|
|
27719
27979
|
}
|
|
27720
27980
|
return updates;
|
|
@@ -27727,9 +27987,9 @@ function historyTitle(thread, turns) {
|
|
|
27727
27987
|
for (const item of turn.items) {
|
|
27728
27988
|
if (item.type !== "userMessage")
|
|
27729
27989
|
continue;
|
|
27730
|
-
const
|
|
27731
|
-
if (
|
|
27732
|
-
return
|
|
27990
|
+
const title = promptTitle(fromUserMessage(item.content));
|
|
27991
|
+
if (title)
|
|
27992
|
+
return title;
|
|
27733
27993
|
}
|
|
27734
27994
|
}
|
|
27735
27995
|
const preview = thread.preview.trim();
|
|
@@ -27902,7 +28162,7 @@ class HistoryRead {
|
|
|
27902
28162
|
}
|
|
27903
28163
|
|
|
27904
28164
|
// src/agent/historyProjection.ts
|
|
27905
|
-
import { pathToFileURL as
|
|
28165
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
27906
28166
|
function inputBlock(input) {
|
|
27907
28167
|
switch (input.type) {
|
|
27908
28168
|
case "text":
|
|
@@ -27913,10 +28173,10 @@ function inputBlock(input) {
|
|
|
27913
28173
|
return { type: "resource_link", name: "Audio", uri: input.url, _meta: { codex: { inputType: input.type } } };
|
|
27914
28174
|
case "localImage":
|
|
27915
28175
|
case "localAudio":
|
|
27916
|
-
return { type: "resource_link", name: input.type === "localImage" ? "Image" : "Audio", uri: input.path.startsWith("file://") ? input.path :
|
|
28176
|
+
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
28177
|
case "skill":
|
|
27918
28178
|
case "mention":
|
|
27919
|
-
return { type: "resource_link", name: input.name, uri: input.path.startsWith("file://") ? input.path :
|
|
28179
|
+
return { type: "resource_link", name: input.name, uri: input.path.startsWith("file://") ? input.path : pathToFileURL3(input.path).href, _meta: { codex: { inputType: input.type } } };
|
|
27920
28180
|
}
|
|
27921
28181
|
}
|
|
27922
28182
|
function projectHistoryItem(item, turnId, startedAt, command) {
|
|
@@ -28328,8 +28588,8 @@ class CodexAgent {
|
|
|
28328
28588
|
const catalog = await this.withCodex(() => this.codex.allModels(true));
|
|
28329
28589
|
const nativeProvider = await this.resolveModelProvider(candidate);
|
|
28330
28590
|
const wholesale = this.providers.active !== null && this.providers.mode === "route" || candidate.active !== null && candidate.mode === "route";
|
|
28331
|
-
const
|
|
28332
|
-
const moving = [...this.sessions.values()].filter((runtime) => !
|
|
28591
|
+
const unavailable2 = new Set([...this.sessions.values()].filter((runtime) => this.providers.mode === "catalog" && this.providers.hasGateway(runtime.modelProvider) && !candidate.hasGateway(runtime.modelProvider)));
|
|
28592
|
+
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
28593
|
const targets = new Map(moving.map((runtime) => {
|
|
28334
28594
|
if (candidate.active === null)
|
|
28335
28595
|
return [runtime, null];
|
|
@@ -28367,7 +28627,7 @@ class CodexAgent {
|
|
|
28367
28627
|
}));
|
|
28368
28628
|
staged.set(runtime, { config: config2, catalog: nextCatalog, model: resolveModelSelection(nextCatalog, requestedModel ?? candidate.selectionId(thread.model, modelProvider), thread.reasoningEffort) });
|
|
28369
28629
|
}
|
|
28370
|
-
for (const runtime of
|
|
28630
|
+
for (const runtime of unavailable2)
|
|
28371
28631
|
runtime.config = candidate.rebind(runtime.config, this.providers, null);
|
|
28372
28632
|
this.providers = candidate;
|
|
28373
28633
|
for (const runtime of this.sessions.values()) {
|
|
@@ -28380,7 +28640,7 @@ class CodexAgent {
|
|
|
28380
28640
|
}
|
|
28381
28641
|
runtime.session.catalog = nextCatalog;
|
|
28382
28642
|
runtime.session.gatewayGroups = candidate.gatewayGroups();
|
|
28383
|
-
if (!next && !
|
|
28643
|
+
if (!next && !unavailable2.has(runtime))
|
|
28384
28644
|
runtime.session.model = resolveModelSelection(nextCatalog, findModel(nextCatalog, runtime.session.model.model) ? runtime.session.model.model : null, runtime.session.model.effort);
|
|
28385
28645
|
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
28646
|
}
|
|
@@ -28427,17 +28687,17 @@ class CodexAgent {
|
|
|
28427
28687
|
const runtime = await this.openSession({ kind: "fork", request: params }, signal);
|
|
28428
28688
|
return { sessionId: runtime.session.id, configOptions: sessionConfigOptions(runtime.session), _meta: { codex: runtime.lineage } };
|
|
28429
28689
|
}
|
|
28430
|
-
async openSession(
|
|
28690
|
+
async openSession(open3, signal) {
|
|
28431
28691
|
if (signal?.aborted)
|
|
28432
28692
|
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
28433
28693
|
const pending = this.admission(async () => {
|
|
28434
|
-
const runtime = await this.openSessionInternal(
|
|
28694
|
+
const runtime = await this.openSessionInternal(open3, signal);
|
|
28435
28695
|
if (signal?.aborted) {
|
|
28436
28696
|
await this.closeRuntime(runtime.session.id);
|
|
28437
28697
|
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
28438
28698
|
}
|
|
28439
28699
|
return runtime;
|
|
28440
|
-
},
|
|
28700
|
+
}, open3.kind === "new" ? undefined : open3.request.sessionId);
|
|
28441
28701
|
try {
|
|
28442
28702
|
return signal ? await abortable(pending, signal) : await pending;
|
|
28443
28703
|
} catch (error40) {
|
|
@@ -28451,24 +28711,24 @@ class CodexAgent {
|
|
|
28451
28711
|
throw RequestError.invalidRequest({ sessionId }, "A previous unsubscribe is still pending; retry after Codex responds or reconnect the agent");
|
|
28452
28712
|
}
|
|
28453
28713
|
}
|
|
28454
|
-
async openSessionInternal(
|
|
28455
|
-
const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[
|
|
28714
|
+
async openSessionInternal(open3, signal) {
|
|
28715
|
+
const method = { new: "session/new", resume: "session/resume", fork: "session/fork" }[open3.kind];
|
|
28456
28716
|
const capabilities = this.requireInitialized(method);
|
|
28457
|
-
if (
|
|
28458
|
-
this.assertUnsubscribeSettled(
|
|
28459
|
-
const { request } =
|
|
28460
|
-
const lastTurnId =
|
|
28717
|
+
if (open3.kind !== "new")
|
|
28718
|
+
this.assertUnsubscribeSettled(open3.request.sessionId);
|
|
28719
|
+
const { request } = open3;
|
|
28720
|
+
const lastTurnId = open3.kind === "fork" ? forkTurnId(request._meta) : undefined;
|
|
28461
28721
|
if (typeof request.cwd !== "string" || !path9.isAbsolute(request.cwd)) {
|
|
28462
28722
|
throw RequestError.invalidParams({ cwd: request.cwd }, "cwd must be an absolute path");
|
|
28463
28723
|
}
|
|
28464
|
-
if (
|
|
28465
|
-
throw RequestError.invalidParams({ replayFrom:
|
|
28724
|
+
if (open3.kind === "resume" && open3.request.replayFrom != null && open3.request.replayFrom.type !== "start") {
|
|
28725
|
+
throw RequestError.invalidParams({ replayFrom: open3.request.replayFrom }, 'Only replayFrom {type: "start"} is supported');
|
|
28466
28726
|
}
|
|
28467
|
-
const seed =
|
|
28727
|
+
const seed = open3.kind === "new" ? seedHistoryOf(open3.request._meta) : [];
|
|
28468
28728
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories);
|
|
28469
|
-
if (
|
|
28470
|
-
await this.closeRuntime(
|
|
28471
|
-
this.assertUnsubscribeSettled(
|
|
28729
|
+
if (open3.kind === "resume" && this.sessions.has(open3.request.sessionId)) {
|
|
28730
|
+
await this.closeRuntime(open3.request.sessionId);
|
|
28731
|
+
this.assertUnsubscribeSettled(open3.request.sessionId);
|
|
28472
28732
|
}
|
|
28473
28733
|
const mcpServers = request.mcpServers ?? [];
|
|
28474
28734
|
const clientTools = clientToolsOf(request._meta);
|
|
@@ -28483,8 +28743,8 @@ class CodexAgent {
|
|
|
28483
28743
|
if (this.providers.active !== null && this.providers.mode === "catalog") {
|
|
28484
28744
|
if (requestedModel !== null)
|
|
28485
28745
|
gatewayId = this.providers.gatewayIdFor(requestedModel);
|
|
28486
|
-
else if (
|
|
28487
|
-
const known = await this.withCodex(() => this.codex.threadRead({ threadId:
|
|
28746
|
+
else if (open3.kind === "resume") {
|
|
28747
|
+
const known = await this.withCodex(() => this.codex.threadRead({ threadId: open3.request.sessionId, includeTurns: false }));
|
|
28488
28748
|
gatewayId = this.providers.hasGateway(known.thread.modelProvider) ? known.thread.modelProvider : null;
|
|
28489
28749
|
}
|
|
28490
28750
|
}
|
|
@@ -28503,7 +28763,7 @@ class CodexAgent {
|
|
|
28503
28763
|
const { thread, skills } = await this.withSkillsContext(request.cwd, additionalDirectories, async (skills2) => ({ skills: skills2, thread: await this.withCodex(async () => {
|
|
28504
28764
|
if (signal?.aborted)
|
|
28505
28765
|
throw RequestError.requestCancelled(undefined, "Session opening was cancelled");
|
|
28506
|
-
switch (
|
|
28766
|
+
switch (open3.kind) {
|
|
28507
28767
|
case "new":
|
|
28508
28768
|
return await this.codex.threadStart({
|
|
28509
28769
|
...hostThreadOptions,
|
|
@@ -28515,15 +28775,15 @@ class CodexAgent {
|
|
|
28515
28775
|
});
|
|
28516
28776
|
case "resume":
|
|
28517
28777
|
try {
|
|
28518
|
-
return await this.codex.threadResume({ ...hostThreadOptions, threadId:
|
|
28778
|
+
return await this.codex.threadResume({ ...hostThreadOptions, threadId: open3.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
|
|
28519
28779
|
} catch (error40) {
|
|
28520
28780
|
if (!errorMessage(error40).includes("already has an active writer"))
|
|
28521
28781
|
throw error40;
|
|
28522
28782
|
readOnly = true;
|
|
28523
|
-
return readOnlyThread((await this.codex.threadRead({ threadId:
|
|
28783
|
+
return readOnlyThread((await this.codex.threadRead({ threadId: open3.request.sessionId, includeTurns: false })).thread, request.cwd);
|
|
28524
28784
|
}
|
|
28525
28785
|
case "fork":
|
|
28526
|
-
return await this.codex.threadFork({ ...hostThreadOptions, ...lastTurnId === undefined ? {} : { lastTurnId }, threadId:
|
|
28786
|
+
return await this.codex.threadFork({ ...hostThreadOptions, ...lastTurnId === undefined ? {} : { lastTurnId }, threadId: open3.request.sessionId, config: config2, cwd: request.cwd, modelProvider, model: startModel, excludeTurns: true });
|
|
28527
28787
|
}
|
|
28528
28788
|
}) }));
|
|
28529
28789
|
const sessionId = thread.thread.id;
|
|
@@ -28583,8 +28843,8 @@ class CodexAgent {
|
|
|
28583
28843
|
removeCancellation = () => signal?.removeEventListener("abort", cancelOpening);
|
|
28584
28844
|
if (signal?.aborted)
|
|
28585
28845
|
cancelOpening();
|
|
28586
|
-
const replay =
|
|
28587
|
-
if (
|
|
28846
|
+
const replay = open3.kind === "fork" || open3.kind === "resume" && open3.request.replayFrom?.type === "start";
|
|
28847
|
+
if (open3.kind !== "new") {
|
|
28588
28848
|
await this.replayHistory(runtime, replay, thread.thread);
|
|
28589
28849
|
} else {
|
|
28590
28850
|
if (seed.length > 0) {
|
|
@@ -28841,13 +29101,13 @@ class CodexAgent {
|
|
|
28841
29101
|
response.data.push(...missing2.values());
|
|
28842
29102
|
}
|
|
28843
29103
|
return {
|
|
28844
|
-
sessions: response.data.map((thread) => ({
|
|
29104
|
+
sessions: await Promise.all(response.data.map(async (thread) => ({
|
|
28845
29105
|
sessionId: thread.id,
|
|
28846
29106
|
cwd: thread.cwd,
|
|
28847
|
-
title: thread.name?.trim() || thread.preview.trim() || null,
|
|
29107
|
+
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
29108
|
updatedAt: new Date(thread.updatedAt * 1000).toISOString(),
|
|
28849
29109
|
_meta: { codex: { archived, ...threadLineage(thread) } }
|
|
28850
|
-
})),
|
|
29110
|
+
}))),
|
|
28851
29111
|
nextCursor: response.nextCursor
|
|
28852
29112
|
};
|
|
28853
29113
|
}
|