@jingyi0605/codingns 0.9.6 → 0.9.7
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/dist/public/assets/{AdaptiveButlerPage-khJQh6a_.js → AdaptiveButlerPage-DclGPzEx.js} +2 -2
- package/dist/public/assets/{App-If9gThKM.js → App-BxX5mm9o.js} +3 -3
- package/dist/public/assets/{BootstrapPage-DcfYtoLC.js → BootstrapPage-Bl21SsuW.js} +1 -1
- package/dist/public/assets/{ConversationPage-Bfb7GLTM.js → ConversationPage-CmiVCV0q.js} +7 -7
- package/dist/public/assets/{DesktopDetachPreviewPage-CXUPMcBz.js → DesktopDetachPreviewPage-uaOHVsjV.js} +1 -1
- package/dist/public/assets/{DesktopModal-bMdI1jEe.js → DesktopModal-BxsogpLf.js} +1 -1
- package/dist/public/assets/{DesktopWindowPage-D1xwgS-7.js → DesktopWindowPage-Bubfw1nC.js} +1 -1
- package/dist/public/assets/{FileContextPanel-C4syif3B.js → FileContextPanel-DrYWcTkp.js} +1 -1
- package/dist/public/assets/{GitSidebar-DduL9aTV.js → GitSidebar-CQsfJqun.js} +1 -1
- package/dist/public/assets/{MobileCreateSessionSheet-DWPBsEx8.js → MobileCreateSessionSheet-CqtmfVNo.js} +1 -1
- package/dist/public/assets/{MobileSheet-BXvQPkxt.js → MobileSheet-C5IVmUsO.js} +1 -1
- package/dist/public/assets/{MobileTopHeaderFrame-vdYOyaaB.js → MobileTopHeaderFrame-EoBm3-X0.js} +1 -1
- package/dist/public/assets/{MobileWorkspaceSwitcherHeader-DT330cAx.js → MobileWorkspaceSwitcherHeader-BC8MMQs_.js} +1 -1
- package/dist/public/assets/{PluginAccessOverview-C77TeZTK.js → PluginAccessOverview-CrQiQxxZ.js} +1 -1
- package/dist/public/assets/{PluginContainerPage-DdSwOCw-.js → PluginContainerPage-_2u-9thM.js} +1 -1
- package/dist/public/assets/{PluginDetailPage-BK1yTzvO.js → PluginDetailPage-F9cKjSCp.js} +1 -1
- package/dist/public/assets/{PluginsListPage-DAAwSc6W.js → PluginsListPage-BOhcua_4.js} +1 -1
- package/dist/public/assets/{RelayConnectEntryPage-4Yyo2p8b.js → RelayConnectEntryPage-Ck_uZLzj.js} +1 -1
- package/dist/public/assets/{ServerSettingsModal-C_DEisHs.js → ServerSettingsModal-ICd82a_x.js} +1 -1
- package/dist/public/assets/SessionIndexPage-Cox6P6dw.js +1 -0
- package/dist/public/assets/{SettingsPage-CDAVsPr3.js → SettingsPage-BI5cM3j5.js} +1 -1
- package/dist/public/assets/{TerminalManagerPanel-4OR47vcf.js → TerminalManagerPanel-DiVhBQhf.js} +1 -1
- package/dist/public/assets/{TerminalPage-Pvx396YX.js → TerminalPage-DUMUO7Ng.js} +1 -1
- package/dist/public/assets/{TerminalRuntimeFallbackModal-KvG6k4AQ.js → TerminalRuntimeFallbackModal-DGPR_CMh.js} +1 -1
- package/dist/public/assets/{ToolFilesPage-DrYHk0N-.js → ToolFilesPage-m88CAp-r.js} +1 -1
- package/dist/public/assets/{ToolGitPage-Dz1q-Ns_.js → ToolGitPage-DCYpfTsb.js} +1 -1
- package/dist/public/assets/{ToolProcessesPage-CRhphOmM.js → ToolProcessesPage-Bk5ulsyq.js} +1 -1
- package/dist/public/assets/{ToolsHomePage-BJSDLR6T.js → ToolsHomePage-BrTwfjI7.js} +1 -1
- package/dist/public/assets/{WorkbenchLandingPage-BlkxdOLC.js → WorkbenchLandingPage-q4AAmdMV.js} +1 -1
- package/dist/public/assets/{WorkbenchLayout-D-U7ghT0.js → WorkbenchLayout-DD8b-m2G.js} +66 -61
- package/dist/public/assets/{WorkbenchModal-xbx1o6MO.js → WorkbenchModal-CsZeLArg.js} +1 -1
- package/dist/public/assets/WorkbenchShellRoute-BaiW_vfb.js +1 -0
- package/dist/public/assets/WorkbenchShellRoute-CxKYZ6uF.css +1 -0
- package/dist/public/assets/{WorkspaceDebugDetailPage-B4ol2_a5.js → WorkspaceDebugDetailPage-BcUUDEyw.js} +1 -1
- package/dist/public/assets/WorkspaceDetailPage-zEZ1VARA.js +1 -0
- package/dist/public/assets/{WorkspaceHomePage-tmCafatd.js → WorkspaceHomePage-CR0rqS4e.js} +1 -1
- package/dist/public/assets/{client-runtime-manager-Bwau7p1v.js → client-runtime-manager-BgGugw8T.js} +1 -1
- package/dist/public/assets/{index-_OCkVmfl.js → index-CFyk1rgJ.js} +5 -5
- package/dist/public/assets/index-CrU73EIV.css +1 -0
- package/dist/public/assets/{login-direct-candidate-resolver-CKUQ07IA.js → login-direct-candidate-resolver-ty2uOY5y.js} +1 -1
- package/dist/public/assets/{plugin-permission-copy-DIVk5jNp.js → plugin-permission-copy-iK3faI3n.js} +1 -1
- package/dist/public/assets/{plugins-api-DHJVvPZw.js → plugins-api-CmV7aDGA.js} +1 -1
- package/dist/public/assets/{preferences-service-CyxxeBmS.js → preferences-service-BoSmT_ny.js} +1 -1
- package/dist/public/assets/{relay-entry-B5GmiOrR.js → relay-entry-C8k5qsl5.js} +1 -1
- package/dist/public/assets/styles-BhKoKfQ_.css +1 -0
- package/dist/public/assets/{terminal-runtime-meta-DBsyT35T.js → terminal-runtime-meta-okQIDzfA.js} +1 -1
- package/dist/public/assets/{useRegisteredDebugTemplates-wCGD2SLW.js → useRegisteredDebugTemplates-B_vXUtSe.js} +1 -1
- package/dist/public/assets/{workbench-navigation-RyUjchbD.js → workbench-navigation-DoDaQR4z.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/server/modules/provider/provider-discovery-runtime.js +16 -1
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.js +14 -0
- package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-process.js +59 -0
- package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/codex-session-title-generator.d.ts +19 -0
- package/dist/server/modules/sessions/codex-session-title-generator.js +295 -0
- package/dist/server/modules/sessions/codex-session-title-generator.js.map +1 -0
- package/dist/server/modules/sessions/session-history-service.d.ts +5 -0
- package/dist/server/modules/sessions/session-history-service.js +183 -0
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +1 -0
- package/dist/server/modules/tasks/task-types.js +1 -0
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/workspace/affairs-lightweight-session-service.js +13 -9
- package/dist/server/modules/workspace/affairs-lightweight-session-service.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +7 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +596 -6
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +249 -6
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +11 -0
- package/package.json +1 -1
- package/dist/public/assets/SessionIndexPage-DyMikN_x.js +0 -1
- package/dist/public/assets/WorkbenchShellRoute-DyWSCHz_.js +0 -1
- package/dist/public/assets/WorkbenchShellRoute-htbkGbtW.css +0 -1
- package/dist/public/assets/WorkspaceDetailPage-DMakfmHR.js +0 -1
- package/dist/public/assets/index-DmUJ8tIw.css +0 -1
- package/dist/public/assets/styles-DkbkRgWw.css +0 -1
- /package/dist/public/assets/{styles-JKFlsYFv.js → styles-DwSuZo1w.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync,
|
|
3
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
@@ -13,6 +13,9 @@ import { loadDatabaseSync } from "../sqlite/node-sqlite.js";
|
|
|
13
13
|
import { createCodexThreadPermissionOptions } from "./codex-permissions.js";
|
|
14
14
|
const CODEX_RUNTIME_DEBUG_ENABLED = /^(1|true|yes)$/i.test(process.env.CODINGNS_PERF_DEBUG?.trim() ?? "");
|
|
15
15
|
const CODEX_APP_SERVER_REQUEST_TIMEOUT_MS = 20_000;
|
|
16
|
+
const CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS = 6 * 60 * 60 * 1000;
|
|
17
|
+
const CODEX_SPAWN_AGENT_RAW_SCAN_BYTES = 2 * 1024 * 1024;
|
|
18
|
+
const CODEX_SPAWN_AGENT_POLL_INTERVAL_MS = 2_000;
|
|
16
19
|
function logCodexRuntimeStep(scope, startedAtMs, detail = {}) {
|
|
17
20
|
if (!CODEX_RUNTIME_DEBUG_ENABLED) {
|
|
18
21
|
return;
|
|
@@ -21,6 +24,182 @@ function logCodexRuntimeStep(scope, startedAtMs, detail = {}) {
|
|
|
21
24
|
const suffix = formatCodexRuntimeDebugDetail(detail);
|
|
22
25
|
console.info(`[perf][codex-runtime] ${scope} ${durationMs}ms${suffix ? ` ${suffix}` : ""}`);
|
|
23
26
|
}
|
|
27
|
+
function closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef) {
|
|
28
|
+
if (!shouldKeepCodexTransportAliveAfterTurn(lifecycle, rawStoreRef)) {
|
|
29
|
+
transport.close();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// 子 Agent 依附在 Codex app-server 进程上。父 turn 完成后立即 close
|
|
33
|
+
// 会把刚 spawn 出来的子 Agent 一起 SIGTERM 掉。这里给一个足够长的
|
|
34
|
+
// 宽限期,让子 Agent 自己跑完;宽限期到了再兜底回收,避免进程永久泄漏。
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
transport.close();
|
|
37
|
+
}, CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS);
|
|
38
|
+
if (typeof timer.unref === "function") {
|
|
39
|
+
timer.unref();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function shouldKeepCodexTransportAliveAfterTurn(lifecycle, rawStoreRef) {
|
|
43
|
+
return lifecycle.keepTransportAliveAfterTurn || codexRawStoreContainsSpawnAgentCall(rawStoreRef);
|
|
44
|
+
}
|
|
45
|
+
function codexRawStoreContainsSpawnAgentCall(rawStoreRef) {
|
|
46
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
47
|
+
if (!text || !text.includes("spawn_agent")) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
for (const line of text.split("\n")) {
|
|
51
|
+
if (!line.includes("spawn_agent")) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const record = toRecord(JSON.parse(line));
|
|
56
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
57
|
+
if (isCodexSpawnAgentItem(record)
|
|
58
|
+
|| isCodexSpawnAgentItem(payload)
|
|
59
|
+
|| isCodexSpawnAgentItem(toRecord(readProp(record, "item")))
|
|
60
|
+
|| isCodexSpawnAgentItem(toRecord(readProp(payload, "item")))) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function extractCodexSpawnedAgentIdsFromRawStore(rawStoreRef) {
|
|
71
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
72
|
+
if (!text || !text.includes("spawn_agent")) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const spawnCallIds = new Set();
|
|
76
|
+
const agentIds = new Set();
|
|
77
|
+
for (const line of text.split("\n")) {
|
|
78
|
+
if (!line.trim()) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const record = toRecord(JSON.parse(line));
|
|
83
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
84
|
+
if (!payload) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (isCodexSpawnAgentItem(payload)) {
|
|
88
|
+
const callId = ensureText(readProp(payload, "call_id")).trim();
|
|
89
|
+
if (callId) {
|
|
90
|
+
spawnCallIds.add(callId);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (ensureText(readProp(payload, "type")).trim() !== "function_call_output") {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const callId = ensureText(readProp(payload, "call_id")).trim();
|
|
98
|
+
if (!callId || !spawnCallIds.has(callId)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const parsedOutput = parseStructuredJson(ensureText(readProp(payload, "output")));
|
|
102
|
+
const agentId = ensureText(readProp(parsedOutput, "agent_id")).trim();
|
|
103
|
+
if (looksLikeCodexThreadId(agentId)) {
|
|
104
|
+
agentIds.add(agentId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...agentIds];
|
|
112
|
+
}
|
|
113
|
+
function isCodexRawStoreTerminal(rawStoreRef) {
|
|
114
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
115
|
+
if (!text) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
for (const line of text.split("\n")) {
|
|
119
|
+
if (!line.includes("task_complete")
|
|
120
|
+
&& !line.includes("turn_aborted")
|
|
121
|
+
&& !line.includes("turn_failed")) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const record = toRecord(JSON.parse(line));
|
|
126
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
127
|
+
const recordType = ensureText(readProp(record, "type")).trim();
|
|
128
|
+
const payloadType = ensureText(readProp(payload, "type")).trim();
|
|
129
|
+
if ((recordType === "event_msg" && payloadType === "task_complete")
|
|
130
|
+
|| (recordType === "event_msg" && payloadType === "turn_aborted")
|
|
131
|
+
|| (recordType === "event_msg" && payloadType === "turn_failed")) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function readCodexRawStoreTail(rawStoreRef) {
|
|
142
|
+
try {
|
|
143
|
+
if (!rawStoreRef.trim() || !existsSync(rawStoreRef)) {
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
const stat = statSync(rawStoreRef);
|
|
147
|
+
if (!stat.isFile()) {
|
|
148
|
+
return "";
|
|
149
|
+
}
|
|
150
|
+
if (stat.size <= CODEX_SPAWN_AGENT_RAW_SCAN_BYTES) {
|
|
151
|
+
return readFileSync(rawStoreRef, "utf8");
|
|
152
|
+
}
|
|
153
|
+
const fd = openSync(rawStoreRef, "r");
|
|
154
|
+
try {
|
|
155
|
+
const length = CODEX_SPAWN_AGENT_RAW_SCAN_BYTES;
|
|
156
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
157
|
+
const bytesRead = readSync(fd, buffer, 0, length, stat.size - length);
|
|
158
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
closeSync(fd);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function isCodexSpawnAgentEvent(event) {
|
|
169
|
+
const eventRecord = toRecord(event);
|
|
170
|
+
if (!eventRecord) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// app-server 通常发的是 { type: "item.completed", item: {...} }。
|
|
174
|
+
// 但真实 3002 路径里,父 turn 的 transcript 也会出现已经展开的
|
|
175
|
+
// { type: "function_call", name: "spawn_agent" } 记录。两种都必须识别,
|
|
176
|
+
// 否则父 turn 完成后会 close app-server,把子 Agent 一起中断。
|
|
177
|
+
return isCodexSpawnAgentItem(toRecord(readProp(eventRecord, "item")) ?? eventRecord);
|
|
178
|
+
}
|
|
179
|
+
function markCodexSpawnAgentLifecycleFromEvents(lifecycle, events) {
|
|
180
|
+
if (lifecycle.keepTransportAliveAfterTurn) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (events.some((event) => isCodexSpawnAgentEvent(event))) {
|
|
184
|
+
lifecycle.keepTransportAliveAfterTurn = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function isCodexSpawnAgentItem(item) {
|
|
188
|
+
if (!item) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const itemType = ensureText(readProp(item, "type")).trim();
|
|
192
|
+
if (itemType === "function_call"
|
|
193
|
+
|| itemType === "functionCall"
|
|
194
|
+
|| itemType === "custom_tool_call") {
|
|
195
|
+
return (ensureText(readProp(item, "name")).trim() === "spawn_agent"
|
|
196
|
+
|| ensureText(readProp(item, "tool")).trim() === "spawn_agent");
|
|
197
|
+
}
|
|
198
|
+
if (itemType === "dynamicToolCall" || itemType === "mcpToolCall") {
|
|
199
|
+
return ensureText(readProp(item, "tool")).trim() === "spawn_agent";
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
24
203
|
function formatCodexRuntimeDebugDetail(detail) {
|
|
25
204
|
const entries = Object.entries(detail).filter(([, value]) => value !== undefined);
|
|
26
205
|
if (entries.length === 0) {
|
|
@@ -75,6 +254,9 @@ export class CodexRuntimeAdapter {
|
|
|
75
254
|
});
|
|
76
255
|
const abortController = new AbortController();
|
|
77
256
|
const eventQueue = createAsyncEventQueue();
|
|
257
|
+
const lifecycle = {
|
|
258
|
+
keepTransportAliveAfterTurn: false
|
|
259
|
+
};
|
|
78
260
|
const translateNotification = createCodexAppServerNotificationTranslator();
|
|
79
261
|
const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
|
|
80
262
|
const resumedSyntheticSession = await this.resumeSyntheticThreadFromHistory(transport, request);
|
|
@@ -115,6 +297,7 @@ export class CodexRuntimeAdapter {
|
|
|
115
297
|
});
|
|
116
298
|
}
|
|
117
299
|
const translated = translateNotification(notification);
|
|
300
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
118
301
|
forwardTranslatedNotification(translated);
|
|
119
302
|
});
|
|
120
303
|
transport.setServerRequestHandler(async (serverRequest) => {
|
|
@@ -142,6 +325,7 @@ export class CodexRuntimeAdapter {
|
|
|
142
325
|
const startTurnNotification = startTurnResult?.notification ?? null;
|
|
143
326
|
if (startTurnNotification) {
|
|
144
327
|
const translated = translateNotification(startTurnNotification);
|
|
328
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
145
329
|
forwardTranslatedNotification(translated);
|
|
146
330
|
}
|
|
147
331
|
logCodexRuntimeStep("start_session.turn_start", startTurnStartedAtMs, {
|
|
@@ -166,8 +350,8 @@ export class CodexRuntimeAdapter {
|
|
|
166
350
|
transport.close();
|
|
167
351
|
},
|
|
168
352
|
isAlive: () => transport.isClosed() === false,
|
|
169
|
-
completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs).finally(() => {
|
|
170
|
-
transport
|
|
353
|
+
completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs, lifecycle).finally(() => {
|
|
354
|
+
closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
|
|
171
355
|
})
|
|
172
356
|
};
|
|
173
357
|
}
|
|
@@ -261,6 +445,9 @@ export class CodexRuntimeAdapter {
|
|
|
261
445
|
: pickedRawStoreRef;
|
|
262
446
|
const abortController = new AbortController();
|
|
263
447
|
const eventQueue = createAsyncEventQueue();
|
|
448
|
+
const lifecycle = {
|
|
449
|
+
keepTransportAliveAfterTurn: false
|
|
450
|
+
};
|
|
264
451
|
const translateNotification = createCodexAppServerNotificationTranslator();
|
|
265
452
|
const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
|
|
266
453
|
logCodexRuntimeStep("continue_session.raw_store_ref_ready", runtimeStartedAtMs, {
|
|
@@ -286,6 +473,7 @@ export class CodexRuntimeAdapter {
|
|
|
286
473
|
});
|
|
287
474
|
}
|
|
288
475
|
const translated = translateNotification(notification);
|
|
476
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
289
477
|
forwardTranslatedNotification(translated);
|
|
290
478
|
});
|
|
291
479
|
transport.setServerRequestHandler(async (serverRequest) => {
|
|
@@ -313,6 +501,7 @@ export class CodexRuntimeAdapter {
|
|
|
313
501
|
const startTurnNotification = startTurnResult?.notification ?? null;
|
|
314
502
|
if (startTurnNotification) {
|
|
315
503
|
const translated = translateNotification(startTurnNotification);
|
|
504
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
316
505
|
forwardTranslatedNotification(translated);
|
|
317
506
|
}
|
|
318
507
|
logCodexRuntimeStep("continue_session.turn_start", startTurnStartedAtMs, {
|
|
@@ -337,8 +526,8 @@ export class CodexRuntimeAdapter {
|
|
|
337
526
|
transport.close();
|
|
338
527
|
},
|
|
339
528
|
isAlive: () => transport.isClosed() === false,
|
|
340
|
-
completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now()).finally(() => {
|
|
341
|
-
transport
|
|
529
|
+
completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now(), performance.now(), lifecycle).finally(() => {
|
|
530
|
+
closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
|
|
342
531
|
})
|
|
343
532
|
};
|
|
344
533
|
}
|
|
@@ -382,13 +571,16 @@ export class CodexRuntimeAdapter {
|
|
|
382
571
|
}
|
|
383
572
|
return fallbackMatch;
|
|
384
573
|
}
|
|
385
|
-
async runTurn(thread, request, sink, providerSessionId, rawStoreRef, abortController, preparedEvents, bufferedEvents = [], launchedAtMs = Date.now(), launchPerfStartedAtMs = performance.now()
|
|
574
|
+
async runTurn(thread, request, sink, providerSessionId, rawStoreRef, abortController, preparedEvents, bufferedEvents = [], launchedAtMs = Date.now(), launchPerfStartedAtMs = performance.now(), lifecycle = {
|
|
575
|
+
keepTransportAliveAfterTurn: false
|
|
576
|
+
}) {
|
|
386
577
|
const context = {
|
|
387
578
|
providerSessionId,
|
|
388
579
|
rawStoreRef,
|
|
389
580
|
// 运行时消息必须接在历史消息后面,不能每轮都从 1 重新编号,
|
|
390
581
|
// 否则前端会把新 assistant/tool 消息排到旧消息前面,表现成用户消息一直挂在底部。
|
|
391
582
|
sequence: Math.max(0, request.sequenceBase ?? 0),
|
|
583
|
+
lifecycle,
|
|
392
584
|
toolNameByCallId: new Map(),
|
|
393
585
|
stableMessageRefByIdentity: new Map(),
|
|
394
586
|
lastSignatureByIdentity: new Map(),
|
|
@@ -418,6 +610,7 @@ export class CodexRuntimeAdapter {
|
|
|
418
610
|
while (true) {
|
|
419
611
|
const next = await events.next();
|
|
420
612
|
if (next.done) {
|
|
613
|
+
await this.waitForSpawnedCodexAgentsIfNeeded(context, abortController.signal);
|
|
421
614
|
return;
|
|
422
615
|
}
|
|
423
616
|
await this.refreshSessionBindingIfNeeded(context);
|
|
@@ -450,11 +643,37 @@ export class CodexRuntimeAdapter {
|
|
|
450
643
|
});
|
|
451
644
|
}
|
|
452
645
|
}
|
|
646
|
+
async waitForSpawnedCodexAgentsIfNeeded(context, signal) {
|
|
647
|
+
if (!shouldKeepCodexTransportAliveAfterTurn(context.lifecycle, context.rawStoreRef)) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const agentIds = extractCodexSpawnedAgentIdsFromRawStore(context.rawStoreRef);
|
|
651
|
+
if (agentIds.length === 0) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const deadline = Date.now() + CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS;
|
|
655
|
+
const remainingAgentIds = new Set(agentIds);
|
|
656
|
+
while (remainingAgentIds.size > 0 && Date.now() < deadline && !signal.aborted) {
|
|
657
|
+
for (const agentId of [...remainingAgentIds]) {
|
|
658
|
+
const rawStoreRef = this.findRawStoreRefOnce(agentId, context.workspacePath, context.homeDir);
|
|
659
|
+
if (rawStoreRef && isCodexRawStoreTerminal(rawStoreRef)) {
|
|
660
|
+
remainingAgentIds.delete(agentId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (remainingAgentIds.size === 0 || Date.now() >= deadline || signal.aborted) {
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
await sleep(CODEX_SPAWN_AGENT_POLL_INTERVAL_MS);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
453
669
|
async handleEvent(event, request, context, interrupted) {
|
|
454
670
|
const eventType = ensureText(readProp(event, "type")).trim();
|
|
455
671
|
if (eventType.length === 0) {
|
|
456
672
|
return;
|
|
457
673
|
}
|
|
674
|
+
if (isCodexSpawnAgentEvent(event)) {
|
|
675
|
+
context.lifecycle.keepTransportAliveAfterTurn = true;
|
|
676
|
+
}
|
|
458
677
|
if (context.lastSignatureByIdentity.size === 0 && eventType.startsWith("item.")) {
|
|
459
678
|
logCodexRuntimeStep("turn.first_item_event", context.launchPerfStartedAtMs, {
|
|
460
679
|
sessionId: request.sessionId,
|
|
@@ -1686,6 +1905,17 @@ function translateCodexAppServerItem(item) {
|
|
|
1686
1905
|
status: normalizeCodexItemStatus(item.status)
|
|
1687
1906
|
};
|
|
1688
1907
|
}
|
|
1908
|
+
if (itemType === "functionCall" || itemType === "function_call") {
|
|
1909
|
+
return {
|
|
1910
|
+
type: "function_call",
|
|
1911
|
+
id: item.id,
|
|
1912
|
+
name: item.name,
|
|
1913
|
+
arguments: readProp(item, "arguments") ?? readProp(item, "input"),
|
|
1914
|
+
output: item.output,
|
|
1915
|
+
error: item.error,
|
|
1916
|
+
status: normalizeCodexItemStatus(item.status)
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1689
1919
|
if (itemType === "dynamicToolCall") {
|
|
1690
1920
|
const toolName = ensureText(item.tool).trim();
|
|
1691
1921
|
const patchText = isCodexExecCommandToolName(toolName)
|
|
@@ -2118,6 +2348,19 @@ function normalizeText(value) {
|
|
|
2118
2348
|
const normalized = ensureText(value).trim();
|
|
2119
2349
|
return normalized.length > 0 ? normalized : null;
|
|
2120
2350
|
}
|
|
2351
|
+
function parseStructuredJson(value) {
|
|
2352
|
+
const normalized = value.trim();
|
|
2353
|
+
if (!normalized) {
|
|
2354
|
+
return null;
|
|
2355
|
+
}
|
|
2356
|
+
try {
|
|
2357
|
+
const parsed = JSON.parse(normalized);
|
|
2358
|
+
return toRecord(parsed);
|
|
2359
|
+
}
|
|
2360
|
+
catch {
|
|
2361
|
+
return null;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2121
2364
|
function readThreadIdFromRawStore(rawStoreRef) {
|
|
2122
2365
|
const filePath = ensureText(rawStoreRef).trim();
|
|
2123
2366
|
if (!filePath || !existsSync(filePath)) {
|