@jingyi0605/codingns 0.9.6 → 0.9.8
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-D-gXre7Y.js} +2 -2
- package/dist/public/assets/{App-CcDXqFl1.css → App-7zrCMhE-.css} +1 -1
- package/dist/public/assets/App-Dl-mcdqy.js +30 -0
- package/dist/public/assets/{BootstrapPage-DcfYtoLC.js → BootstrapPage-B-yMdfpQ.js} +1 -1
- package/dist/public/assets/ConversationPage-DRQ5Sg_d.js +9 -0
- package/dist/public/assets/{DesktopDetachPreviewPage-CXUPMcBz.js → DesktopDetachPreviewPage-D1DMaGcy.js} +1 -1
- package/dist/public/assets/{DesktopModal-bMdI1jEe.js → DesktopModal-BnfGW2gk.js} +1 -1
- package/dist/public/assets/DesktopWindowPage-2SWAi0xz.js +2 -0
- package/dist/public/assets/FileContextPanel-fbPuE9dO.js +1 -0
- package/dist/public/assets/GitSidebar-BkmesJJR.js +6 -0
- package/dist/public/assets/{MobileCreateSessionSheet-DWPBsEx8.js → MobileCreateSessionSheet-CEJcDBZJ.js} +1 -1
- package/dist/public/assets/{MobileSheet-BXvQPkxt.js → MobileSheet-rkn_CUOY.js} +1 -1
- package/dist/public/assets/{MobileTopHeaderFrame-vdYOyaaB.js → MobileTopHeaderFrame-CU0wsYSS.js} +1 -1
- package/dist/public/assets/MobileWorkspaceSwitcherHeader-idl8o1OB.js +1 -0
- package/dist/public/assets/{PluginAccessOverview-C77TeZTK.js → PluginAccessOverview-BBgM6tb0.js} +1 -1
- package/dist/public/assets/{PluginContainerPage-DdSwOCw-.js → PluginContainerPage-D-ly3i3H.js} +1 -1
- package/dist/public/assets/{PluginDetailPage-BK1yTzvO.js → PluginDetailPage-CWAHYyyG.js} +1 -1
- package/dist/public/assets/{PluginsListPage-DAAwSc6W.js → PluginsListPage-Cte3vBgR.js} +1 -1
- package/dist/public/assets/{RelayConnectEntryPage-4Yyo2p8b.js → RelayConnectEntryPage-sRJlstx9.js} +1 -1
- package/dist/public/assets/{ServerSettingsModal-C_DEisHs.js → ServerSettingsModal-BBft9KEC.js} +1 -1
- package/dist/public/assets/SessionIndexPage-CN7cEdl9.js +1 -0
- package/dist/public/assets/SettingsPage-BGT-YqG2.js +2 -0
- package/dist/public/assets/TerminalManagerPanel-6-ZJ8vGn.js +1 -0
- package/dist/public/assets/TerminalPage-CUXXQYU2.js +55 -0
- package/dist/public/assets/{TerminalRuntimeFallbackModal-KvG6k4AQ.js → TerminalRuntimeFallbackModal-zc3qqMKJ.js} +1 -1
- package/dist/public/assets/ToolFilesPage-QzsZyr0F.js +1 -0
- package/dist/public/assets/ToolGitPage-CXg4ncuT.js +1 -0
- package/dist/public/assets/ToolProcessesPage-BPsOsg4w.js +1 -0
- package/dist/public/assets/ToolsHomePage-D1n4FU1s.js +1 -0
- package/dist/public/assets/WorkbenchLandingPage-BaU_dXls.js +1 -0
- package/dist/public/assets/WorkbenchLayout-DViAJhHz.js +1027 -0
- package/dist/public/assets/{WorkbenchModal-xbx1o6MO.js → WorkbenchModal-DWsNm2B2.js} +1 -1
- package/dist/public/assets/WorkbenchShellRoute-BGfRqBUa.js +1 -0
- package/dist/public/assets/WorkbenchShellRoute-f2jWjHWu.css +1 -0
- package/dist/public/assets/WorkspaceDebugDetailPage-BX0zVSsI.js +1 -0
- package/dist/public/assets/WorkspaceDetailPage-Dx6JX4jx.js +1 -0
- package/dist/public/assets/WorkspaceHomePage-DQVJ042Z.js +1 -0
- package/dist/public/assets/{client-runtime-manager-Bwau7p1v.js → client-runtime-manager-D9VbgJZ_.js} +1 -1
- package/dist/public/assets/host-alias-0TfFnYxR.js +1 -0
- package/dist/public/assets/index-DREvg1Yu.css +1 -0
- package/dist/public/assets/index-FOhyOpGY.js +50 -0
- package/dist/public/assets/{login-direct-candidate-resolver-CKUQ07IA.js → login-direct-candidate-resolver-17wEvjhh.js} +1 -1
- package/dist/public/assets/peer-host-config-sync-vYkmqzNz.js +1 -0
- package/dist/public/assets/{plugin-permission-copy-DIVk5jNp.js → plugin-permission-copy-apDn8EWG.js} +1 -1
- package/dist/public/assets/{plugins-api-DHJVvPZw.js → plugins-api-CnZYRKoS.js} +1 -1
- package/dist/public/assets/{preferences-service-CyxxeBmS.js → preferences-service-PZlLLAWH.js} +1 -1
- package/dist/public/assets/relay-entry-DhHwflXl.js +1 -0
- package/dist/public/assets/styles-BhKoKfQ_.css +1 -0
- package/dist/public/assets/terminal-runtime-meta-2zvacxvM.js +1 -0
- package/dist/public/assets/{useRegisteredDebugTemplates-wCGD2SLW.js → useRegisteredDebugTemplates-CJ-o4tFl.js} +1 -1
- package/dist/public/assets/workbench-navigation-aqJ1ay4M.js +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/middlewares/auth-guard.js +1 -0
- package/dist/server/middlewares/auth-guard.js.map +1 -1
- package/dist/server/modules/peer-host/host-api-proxy-service.d.ts +9 -0
- package/dist/server/modules/peer-host/host-api-proxy-service.js +174 -0
- package/dist/server/modules/peer-host/host-api-proxy-service.js.map +1 -0
- package/dist/server/modules/peer-host/host-handshake-controller.d.ts +7 -0
- package/dist/server/modules/peer-host/host-handshake-controller.js +10 -0
- package/dist/server/modules/peer-host/host-handshake-controller.js.map +1 -0
- package/dist/server/modules/peer-host/host-handshake.d.ts +15 -0
- package/dist/server/modules/peer-host/host-handshake.js +20 -0
- package/dist/server/modules/peer-host/host-handshake.js.map +1 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.d.ts +14 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.js +256 -0
- package/dist/server/modules/peer-host/host-ws-proxy-service.js.map +1 -0
- package/dist/server/modules/peer-host/peer-host-controller.d.ts +55 -0
- package/dist/server/modules/peer-host/peer-host-controller.js +62 -0
- package/dist/server/modules/peer-host/peer-host-controller.js.map +1 -0
- package/dist/server/modules/peer-host/peer-host-service.d.ts +77 -0
- package/dist/server/modules/peer-host/peer-host-service.js +529 -0
- package/dist/server/modules/peer-host/peer-host-service.js.map +1 -0
- 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 +308 -0
- package/dist/server/modules/sessions/codex-session-title-generator.js.map +1 -0
- package/dist/server/modules/sessions/session-history-service.d.ts +16 -0
- package/dist/server/modules/sessions/session-history-service.js +208 -12
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +181 -80
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-title-utils.d.ts +3 -0
- package/dist/server/modules/sessions/session-title-utils.js +25 -0
- package/dist/server/modules/sessions/session-title-utils.js.map +1 -0
- package/dist/server/modules/sessions/workspace-office-mcp-config.d.ts +2 -1
- package/dist/server/modules/sessions/workspace-office-mcp-config.js +44 -3
- package/dist/server/modules/sessions/workspace-office-mcp-config.js.map +1 -1
- package/dist/server/modules/tasks/task-manager.d.ts +2 -1
- package/dist/server/modules/tasks/task-manager.js +3 -0
- package/dist/server/modules/tasks/task-manager.js.map +1 -1
- package/dist/server/modules/tasks/task-scheduler.d.ts +2 -1
- package/dist/server/modules/tasks/task-scheduler.js +21 -0
- package/dist/server/modules/tasks/task-scheduler.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +6 -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/workbench/workbench-controller.js +3 -2
- package/dist/server/modules/workbench/workbench-controller.js.map +1 -1
- package/dist/server/modules/workspace/affairs-library-service.d.ts +1 -0
- package/dist/server/modules/workspace/affairs-library-service.js +80 -0
- package/dist/server/modules/workspace/affairs-library-service.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/dist/server/routes/peer-hosts.d.ts +3 -0
- package/dist/server/routes/peer-hosts.js +18 -0
- package/dist/server/routes/peer-hosts.js.map +1 -0
- package/dist/server/routes/public.d.ts +2 -1
- package/dist/server/routes/public.js +2 -1
- package/dist/server/routes/public.js.map +1 -1
- package/dist/server/server/create-server.d.ts +4 -0
- package/dist/server/server/create-server.js +30 -2
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/http/error-handler.js +12 -0
- package/dist/server/shared/http/error-handler.js.map +1 -1
- package/dist/server/storage/repositories/peer-host-repository.d.ts +44 -0
- package/dist/server/storage/repositories/peer-host-repository.js +271 -0
- package/dist/server/storage/repositories/peer-host-repository.js.map +1 -0
- package/dist/server/storage/sqlite/client.js +81 -0
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +64 -0
- package/dist/server/types/domain.d.ts +43 -0
- package/dist/server/ws/workbench-ws-hub.js +5 -14
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/dist/server/ws/ws-server.d.ts +2 -1
- package/dist/server/ws/ws-server.js +5 -1
- package/dist/server/ws/ws-server.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 +623 -9
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +8 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +4 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +375 -15
- 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/App-If9gThKM.js +0 -30
- package/dist/public/assets/ConversationPage-Bfb7GLTM.js +0 -9
- package/dist/public/assets/DesktopWindowPage-D1xwgS-7.js +0 -2
- package/dist/public/assets/FileContextPanel-C4syif3B.js +0 -1
- package/dist/public/assets/GitSidebar-DduL9aTV.js +0 -6
- package/dist/public/assets/MobileWorkspaceSwitcherHeader-DT330cAx.js +0 -1
- package/dist/public/assets/SessionIndexPage-DyMikN_x.js +0 -1
- package/dist/public/assets/SettingsPage-CDAVsPr3.js +0 -2
- package/dist/public/assets/TerminalManagerPanel-4OR47vcf.js +0 -1
- package/dist/public/assets/TerminalPage-Pvx396YX.js +0 -55
- package/dist/public/assets/ToolFilesPage-DrYHk0N-.js +0 -1
- package/dist/public/assets/ToolGitPage-Dz1q-Ns_.js +0 -1
- package/dist/public/assets/ToolProcessesPage-CRhphOmM.js +0 -1
- package/dist/public/assets/ToolsHomePage-BJSDLR6T.js +0 -1
- package/dist/public/assets/WorkbenchLandingPage-BlkxdOLC.js +0 -1
- package/dist/public/assets/WorkbenchLayout-D-U7ghT0.js +0 -1022
- package/dist/public/assets/WorkbenchShellRoute-DyWSCHz_.js +0 -1
- package/dist/public/assets/WorkbenchShellRoute-htbkGbtW.css +0 -1
- package/dist/public/assets/WorkspaceDebugDetailPage-B4ol2_a5.js +0 -1
- package/dist/public/assets/WorkspaceDetailPage-DMakfmHR.js +0 -1
- package/dist/public/assets/WorkspaceHomePage-tmCafatd.js +0 -1
- package/dist/public/assets/index-DmUJ8tIw.css +0 -1
- package/dist/public/assets/index-_OCkVmfl.js +0 -50
- package/dist/public/assets/relay-entry-B5GmiOrR.js +0 -1
- package/dist/public/assets/styles-DkbkRgWw.css +0 -1
- package/dist/public/assets/terminal-runtime-meta-DBsyT35T.js +0 -1
- package/dist/public/assets/workbench-navigation-RyUjchbD.js +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 = normalizePositiveInteger(process.env.CODINGNS_CODEX_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 = normalizePositiveInteger(process.env.CODINGNS_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,222 @@ 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 normalizePositiveInteger(value, fallback) {
|
|
28
|
+
const parsed = Number(value);
|
|
29
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
30
|
+
}
|
|
31
|
+
function closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef) {
|
|
32
|
+
if (!shouldKeepCodexTransportAliveAfterTurn(lifecycle, rawStoreRef)) {
|
|
33
|
+
transport.close();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// 子 Agent 依附在 Codex app-server 进程上。父 turn 完成后立即 close
|
|
37
|
+
// 会把刚 spawn 出来的子 Agent 一起 SIGTERM 掉。这里给一个足够长的
|
|
38
|
+
// 宽限期,让子 Agent 自己跑完;宽限期到了再兜底回收,避免进程永久泄漏。
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
transport.close();
|
|
41
|
+
}, CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS);
|
|
42
|
+
if (typeof timer.unref === "function") {
|
|
43
|
+
timer.unref();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function shouldKeepCodexTransportAliveAfterTurn(lifecycle, rawStoreRef) {
|
|
47
|
+
if (lifecycle.parentTurnStopped) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (lifecycle.spawnedAgentsSettledAfterTurn) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (lifecycle.spawnedAgentIds.size > 0) {
|
|
54
|
+
return lifecycle.closedSpawnedAgentIds.size < lifecycle.spawnedAgentIds.size;
|
|
55
|
+
}
|
|
56
|
+
return lifecycle.keepTransportAliveAfterTurn || codexRawStoreContainsSpawnAgentCall(rawStoreRef);
|
|
57
|
+
}
|
|
58
|
+
function codexRawStoreContainsSpawnAgentCall(rawStoreRef) {
|
|
59
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
60
|
+
if (!text || !text.includes("spawn_agent")) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
for (const line of text.split("\n")) {
|
|
64
|
+
if (!line.includes("spawn_agent")) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const record = toRecord(JSON.parse(line));
|
|
69
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
70
|
+
if (isCodexSpawnAgentItem(record)
|
|
71
|
+
|| isCodexSpawnAgentItem(payload)
|
|
72
|
+
|| isCodexSpawnAgentItem(toRecord(readProp(record, "item")))
|
|
73
|
+
|| isCodexSpawnAgentItem(toRecord(readProp(payload, "item")))) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function extractCodexSpawnedAgentIdsFromRawStore(rawStoreRef) {
|
|
84
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
85
|
+
if (!text || !text.includes("spawn_agent")) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const spawnCallIds = new Set();
|
|
89
|
+
const agentIds = new Set();
|
|
90
|
+
for (const line of text.split("\n")) {
|
|
91
|
+
if (!line.trim()) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const record = toRecord(JSON.parse(line));
|
|
96
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
97
|
+
if (!payload) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (isCodexSpawnAgentItem(payload)) {
|
|
101
|
+
const callId = ensureText(readProp(payload, "call_id")).trim();
|
|
102
|
+
const agentId = extractCodexAgentIdFromToolOutput(readProp(payload, "output"));
|
|
103
|
+
if (callId) {
|
|
104
|
+
spawnCallIds.add(callId);
|
|
105
|
+
}
|
|
106
|
+
if (agentId) {
|
|
107
|
+
agentIds.add(agentId);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (ensureText(readProp(payload, "type")).trim() !== "function_call_output") {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const callId = ensureText(readProp(payload, "call_id")).trim();
|
|
115
|
+
const outputBelongsToSpawnAgent = ensureText(readProp(payload, "name")).trim() === "spawn_agent"
|
|
116
|
+
|| ensureText(readProp(payload, "tool")).trim() === "spawn_agent";
|
|
117
|
+
if ((!callId || !spawnCallIds.has(callId)) && !outputBelongsToSpawnAgent) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const agentId = extractCodexAgentIdFromToolOutput(readProp(payload, "output"));
|
|
121
|
+
if (agentId) {
|
|
122
|
+
agentIds.add(agentId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return [...agentIds];
|
|
130
|
+
}
|
|
131
|
+
function extractCodexSpawnedAgentIdsFromEvents(events) {
|
|
132
|
+
const agentIds = new Set();
|
|
133
|
+
for (const event of events) {
|
|
134
|
+
const item = toRecord(readProp(event, "item")) ?? event;
|
|
135
|
+
if (!isCodexSpawnAgentItem(item)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const agentId = extractCodexAgentIdFromToolOutput(readProp(item, "output"));
|
|
139
|
+
if (agentId) {
|
|
140
|
+
agentIds.add(agentId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return [...agentIds];
|
|
144
|
+
}
|
|
145
|
+
function extractCodexAgentIdFromToolOutput(output) {
|
|
146
|
+
const parsedOutput = typeof output === "string"
|
|
147
|
+
? parseStructuredJson(output)
|
|
148
|
+
: toRecord(output);
|
|
149
|
+
const agentId = ensureText(readProp(parsedOutput, "agent_id")).trim()
|
|
150
|
+
|| ensureText(readProp(parsedOutput, "agentId")).trim();
|
|
151
|
+
return looksLikeCodexThreadId(agentId) ? agentId : null;
|
|
152
|
+
}
|
|
153
|
+
function isCodexRawStoreTerminal(rawStoreRef) {
|
|
154
|
+
const text = readCodexRawStoreTail(rawStoreRef);
|
|
155
|
+
if (!text) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
for (const line of text.split("\n")) {
|
|
159
|
+
if (!line.includes("task_complete")
|
|
160
|
+
&& !line.includes("turn_aborted")
|
|
161
|
+
&& !line.includes("turn_failed")) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const record = toRecord(JSON.parse(line));
|
|
166
|
+
const payload = toRecord(readProp(record, "payload"));
|
|
167
|
+
const recordType = ensureText(readProp(record, "type")).trim();
|
|
168
|
+
const payloadType = ensureText(readProp(payload, "type")).trim();
|
|
169
|
+
if ((recordType === "event_msg" && payloadType === "task_complete")
|
|
170
|
+
|| (recordType === "event_msg" && payloadType === "turn_aborted")
|
|
171
|
+
|| (recordType === "event_msg" && payloadType === "turn_failed")) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// 单行坏掉不影响判断,继续看下一行。
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
function readCodexRawStoreTail(rawStoreRef) {
|
|
182
|
+
try {
|
|
183
|
+
if (!rawStoreRef.trim() || !existsSync(rawStoreRef)) {
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
const stat = statSync(rawStoreRef);
|
|
187
|
+
if (!stat.isFile()) {
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
if (stat.size <= CODEX_SPAWN_AGENT_RAW_SCAN_BYTES) {
|
|
191
|
+
return readFileSync(rawStoreRef, "utf8");
|
|
192
|
+
}
|
|
193
|
+
const fd = openSync(rawStoreRef, "r");
|
|
194
|
+
try {
|
|
195
|
+
const length = CODEX_SPAWN_AGENT_RAW_SCAN_BYTES;
|
|
196
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
197
|
+
const bytesRead = readSync(fd, buffer, 0, length, stat.size - length);
|
|
198
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
closeSync(fd);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function isCodexSpawnAgentEvent(event) {
|
|
209
|
+
const eventRecord = toRecord(event);
|
|
210
|
+
if (!eventRecord) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
// app-server 通常发的是 { type: "item.completed", item: {...} }。
|
|
214
|
+
// 但真实 3002 路径里,父 turn 的 transcript 也会出现已经展开的
|
|
215
|
+
// { type: "function_call", name: "spawn_agent" } 记录。两种都必须识别,
|
|
216
|
+
// 否则父 turn 完成后会 close app-server,把子 Agent 一起中断。
|
|
217
|
+
return isCodexSpawnAgentItem(toRecord(readProp(eventRecord, "item")) ?? eventRecord);
|
|
218
|
+
}
|
|
219
|
+
function markCodexSpawnAgentLifecycleFromEvents(lifecycle, events) {
|
|
220
|
+
for (const agentId of extractCodexSpawnedAgentIdsFromEvents(events)) {
|
|
221
|
+
lifecycle.spawnedAgentIds.add(agentId);
|
|
222
|
+
}
|
|
223
|
+
if (!lifecycle.keepTransportAliveAfterTurn && events.some((event) => isCodexSpawnAgentEvent(event))) {
|
|
224
|
+
lifecycle.keepTransportAliveAfterTurn = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function isCodexSpawnAgentItem(item) {
|
|
228
|
+
if (!item) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const itemType = ensureText(readProp(item, "type")).trim();
|
|
232
|
+
if (itemType === "function_call"
|
|
233
|
+
|| itemType === "functionCall"
|
|
234
|
+
|| itemType === "custom_tool_call") {
|
|
235
|
+
return (ensureText(readProp(item, "name")).trim() === "spawn_agent"
|
|
236
|
+
|| ensureText(readProp(item, "tool")).trim() === "spawn_agent");
|
|
237
|
+
}
|
|
238
|
+
if (itemType === "dynamicToolCall" || itemType === "mcpToolCall") {
|
|
239
|
+
return ensureText(readProp(item, "tool")).trim() === "spawn_agent";
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
24
243
|
function formatCodexRuntimeDebugDetail(detail) {
|
|
25
244
|
const entries = Object.entries(detail).filter(([, value]) => value !== undefined);
|
|
26
245
|
if (entries.length === 0) {
|
|
@@ -75,6 +294,14 @@ export class CodexRuntimeAdapter {
|
|
|
75
294
|
});
|
|
76
295
|
const abortController = new AbortController();
|
|
77
296
|
const eventQueue = createAsyncEventQueue();
|
|
297
|
+
const lifecycle = {
|
|
298
|
+
keepTransportAliveAfterTurn: false,
|
|
299
|
+
spawnedAgentsSettledAfterTurn: false,
|
|
300
|
+
parentTurnStopped: false,
|
|
301
|
+
spawnedAgentIds: new Set(),
|
|
302
|
+
pendingComplete: null,
|
|
303
|
+
closedSpawnedAgentIds: new Set()
|
|
304
|
+
};
|
|
78
305
|
const translateNotification = createCodexAppServerNotificationTranslator();
|
|
79
306
|
const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
|
|
80
307
|
const resumedSyntheticSession = await this.resumeSyntheticThreadFromHistory(transport, request);
|
|
@@ -115,6 +342,7 @@ export class CodexRuntimeAdapter {
|
|
|
115
342
|
});
|
|
116
343
|
}
|
|
117
344
|
const translated = translateNotification(notification);
|
|
345
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
118
346
|
forwardTranslatedNotification(translated);
|
|
119
347
|
});
|
|
120
348
|
transport.setServerRequestHandler(async (serverRequest) => {
|
|
@@ -129,6 +357,7 @@ export class CodexRuntimeAdapter {
|
|
|
129
357
|
});
|
|
130
358
|
transport.setOnClose((error) => {
|
|
131
359
|
if (error) {
|
|
360
|
+
lifecycle.parentTurnStopped = true;
|
|
132
361
|
eventQueue.push({
|
|
133
362
|
type: "turn.failed",
|
|
134
363
|
timestamp: nextTimestamp(),
|
|
@@ -142,6 +371,7 @@ export class CodexRuntimeAdapter {
|
|
|
142
371
|
const startTurnNotification = startTurnResult?.notification ?? null;
|
|
143
372
|
if (startTurnNotification) {
|
|
144
373
|
const translated = translateNotification(startTurnNotification);
|
|
374
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
145
375
|
forwardTranslatedNotification(translated);
|
|
146
376
|
}
|
|
147
377
|
logCodexRuntimeStep("start_session.turn_start", startTurnStartedAtMs, {
|
|
@@ -166,8 +396,8 @@ export class CodexRuntimeAdapter {
|
|
|
166
396
|
transport.close();
|
|
167
397
|
},
|
|
168
398
|
isAlive: () => transport.isClosed() === false,
|
|
169
|
-
completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs).finally(() => {
|
|
170
|
-
transport
|
|
399
|
+
completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs, lifecycle, transport).finally(() => {
|
|
400
|
+
closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
|
|
171
401
|
})
|
|
172
402
|
};
|
|
173
403
|
}
|
|
@@ -261,6 +491,14 @@ export class CodexRuntimeAdapter {
|
|
|
261
491
|
: pickedRawStoreRef;
|
|
262
492
|
const abortController = new AbortController();
|
|
263
493
|
const eventQueue = createAsyncEventQueue();
|
|
494
|
+
const lifecycle = {
|
|
495
|
+
keepTransportAliveAfterTurn: false,
|
|
496
|
+
spawnedAgentsSettledAfterTurn: false,
|
|
497
|
+
parentTurnStopped: false,
|
|
498
|
+
spawnedAgentIds: new Set(),
|
|
499
|
+
pendingComplete: null,
|
|
500
|
+
closedSpawnedAgentIds: new Set()
|
|
501
|
+
};
|
|
264
502
|
const translateNotification = createCodexAppServerNotificationTranslator();
|
|
265
503
|
const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
|
|
266
504
|
logCodexRuntimeStep("continue_session.raw_store_ref_ready", runtimeStartedAtMs, {
|
|
@@ -286,6 +524,7 @@ export class CodexRuntimeAdapter {
|
|
|
286
524
|
});
|
|
287
525
|
}
|
|
288
526
|
const translated = translateNotification(notification);
|
|
527
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
289
528
|
forwardTranslatedNotification(translated);
|
|
290
529
|
});
|
|
291
530
|
transport.setServerRequestHandler(async (serverRequest) => {
|
|
@@ -300,6 +539,7 @@ export class CodexRuntimeAdapter {
|
|
|
300
539
|
});
|
|
301
540
|
transport.setOnClose((error) => {
|
|
302
541
|
if (error) {
|
|
542
|
+
lifecycle.parentTurnStopped = true;
|
|
303
543
|
eventQueue.push({
|
|
304
544
|
type: "turn.failed",
|
|
305
545
|
timestamp: nextTimestamp(),
|
|
@@ -313,6 +553,7 @@ export class CodexRuntimeAdapter {
|
|
|
313
553
|
const startTurnNotification = startTurnResult?.notification ?? null;
|
|
314
554
|
if (startTurnNotification) {
|
|
315
555
|
const translated = translateNotification(startTurnNotification);
|
|
556
|
+
markCodexSpawnAgentLifecycleFromEvents(lifecycle, translated.events);
|
|
316
557
|
forwardTranslatedNotification(translated);
|
|
317
558
|
}
|
|
318
559
|
logCodexRuntimeStep("continue_session.turn_start", startTurnStartedAtMs, {
|
|
@@ -337,8 +578,8 @@ export class CodexRuntimeAdapter {
|
|
|
337
578
|
transport.close();
|
|
338
579
|
},
|
|
339
580
|
isAlive: () => transport.isClosed() === false,
|
|
340
|
-
completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now()).finally(() => {
|
|
341
|
-
transport
|
|
581
|
+
completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now(), performance.now(), lifecycle, transport).finally(() => {
|
|
582
|
+
closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
|
|
342
583
|
})
|
|
343
584
|
};
|
|
344
585
|
}
|
|
@@ -382,13 +623,22 @@ export class CodexRuntimeAdapter {
|
|
|
382
623
|
}
|
|
383
624
|
return fallbackMatch;
|
|
384
625
|
}
|
|
385
|
-
async runTurn(thread, request, sink, providerSessionId, rawStoreRef, abortController, preparedEvents, bufferedEvents = [], launchedAtMs = Date.now(), launchPerfStartedAtMs = performance.now()
|
|
626
|
+
async runTurn(thread, request, sink, providerSessionId, rawStoreRef, abortController, preparedEvents, bufferedEvents = [], launchedAtMs = Date.now(), launchPerfStartedAtMs = performance.now(), lifecycle = {
|
|
627
|
+
keepTransportAliveAfterTurn: false,
|
|
628
|
+
spawnedAgentsSettledAfterTurn: false,
|
|
629
|
+
parentTurnStopped: false,
|
|
630
|
+
spawnedAgentIds: new Set(),
|
|
631
|
+
pendingComplete: null,
|
|
632
|
+
closedSpawnedAgentIds: new Set()
|
|
633
|
+
}, transport = null) {
|
|
386
634
|
const context = {
|
|
387
635
|
providerSessionId,
|
|
388
636
|
rawStoreRef,
|
|
389
637
|
// 运行时消息必须接在历史消息后面,不能每轮都从 1 重新编号,
|
|
390
638
|
// 否则前端会把新 assistant/tool 消息排到旧消息前面,表现成用户消息一直挂在底部。
|
|
391
639
|
sequence: Math.max(0, request.sequenceBase ?? 0),
|
|
640
|
+
lifecycle,
|
|
641
|
+
transport,
|
|
392
642
|
toolNameByCallId: new Map(),
|
|
393
643
|
stableMessageRefByIdentity: new Map(),
|
|
394
644
|
lastSignatureByIdentity: new Map(),
|
|
@@ -409,7 +659,7 @@ export class CodexRuntimeAdapter {
|
|
|
409
659
|
for (const event of bufferedEvents) {
|
|
410
660
|
await this.refreshSessionBindingIfNeeded(context);
|
|
411
661
|
persistSyntheticEventIfNeeded(context.rawStoreRef, context.providerSessionId, event);
|
|
412
|
-
await this.handleEvent(event, request, context, abortController.signal
|
|
662
|
+
await this.handleEvent(event, request, context, abortController.signal);
|
|
413
663
|
}
|
|
414
664
|
const events = preparedEvents ??
|
|
415
665
|
(await thread.runStreamed(createCodexInput(request), {
|
|
@@ -418,11 +668,16 @@ export class CodexRuntimeAdapter {
|
|
|
418
668
|
while (true) {
|
|
419
669
|
const next = await events.next();
|
|
420
670
|
if (next.done) {
|
|
671
|
+
if (context.lifecycle.parentTurnStopped) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
await this.waitForSpawnedCodexAgentsIfNeeded(context, abortController.signal);
|
|
675
|
+
await this.emitPendingCompleteIfReady(context);
|
|
421
676
|
return;
|
|
422
677
|
}
|
|
423
678
|
await this.refreshSessionBindingIfNeeded(context);
|
|
424
679
|
persistSyntheticEventIfNeeded(context.rawStoreRef, context.providerSessionId, next.value);
|
|
425
|
-
await this.handleEvent(next.value, request, context, abortController.signal
|
|
680
|
+
await this.handleEvent(next.value, request, context, abortController.signal);
|
|
426
681
|
}
|
|
427
682
|
}
|
|
428
683
|
catch (error) {
|
|
@@ -450,11 +705,82 @@ export class CodexRuntimeAdapter {
|
|
|
450
705
|
});
|
|
451
706
|
}
|
|
452
707
|
}
|
|
453
|
-
async
|
|
708
|
+
async emitPendingCompleteIfReady(context) {
|
|
709
|
+
const pendingComplete = context.lifecycle.pendingComplete;
|
|
710
|
+
if (!pendingComplete || context.lifecycle.parentTurnStopped) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
context.lifecycle.pendingComplete = null;
|
|
714
|
+
await context.sink.emit({
|
|
715
|
+
type: "complete",
|
|
716
|
+
status: "completed",
|
|
717
|
+
providerSessionId: context.providerSessionId,
|
|
718
|
+
rawStoreRef: context.rawStoreRef,
|
|
719
|
+
detail: pendingComplete.detail,
|
|
720
|
+
timestamp: pendingComplete.timestamp
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
async waitForSpawnedCodexAgentsIfNeeded(context, signal) {
|
|
724
|
+
if (!shouldKeepCodexTransportAliveAfterTurn(context.lifecycle, context.rawStoreRef)) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const agentIds = Array.from(new Set([
|
|
728
|
+
...context.lifecycle.spawnedAgentIds,
|
|
729
|
+
...extractCodexSpawnedAgentIdsFromRawStore(context.rawStoreRef)
|
|
730
|
+
]));
|
|
731
|
+
if (agentIds.length === 0) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const deadline = Date.now() + CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS;
|
|
735
|
+
const remainingAgentIds = new Set(agentIds);
|
|
736
|
+
while (remainingAgentIds.size > 0
|
|
737
|
+
&& Date.now() < deadline
|
|
738
|
+
&& !signal.aborted
|
|
739
|
+
&& !context.lifecycle.parentTurnStopped) {
|
|
740
|
+
for (const agentId of [...remainingAgentIds]) {
|
|
741
|
+
const rawStoreRef = this.findRawStoreRefOnce(agentId, context.workspacePath, context.homeDir);
|
|
742
|
+
if (rawStoreRef && isCodexRawStoreTerminal(rawStoreRef)) {
|
|
743
|
+
await this.closeSpawnedCodexAgentIfNeeded(context, agentId);
|
|
744
|
+
remainingAgentIds.delete(agentId);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (remainingAgentIds.size === 0
|
|
748
|
+
|| Date.now() >= deadline
|
|
749
|
+
|| signal.aborted
|
|
750
|
+
|| context.lifecycle.parentTurnStopped) {
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
await sleep(CODEX_SPAWN_AGENT_POLL_INTERVAL_MS);
|
|
754
|
+
}
|
|
755
|
+
if (remainingAgentIds.size === 0) {
|
|
756
|
+
context.lifecycle.spawnedAgentsSettledAfterTurn = true;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async closeSpawnedCodexAgentIfNeeded(context, agentId) {
|
|
760
|
+
if (context.lifecycle.closedSpawnedAgentIds.has(agentId)) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
context.lifecycle.closedSpawnedAgentIds.add(agentId);
|
|
764
|
+
try {
|
|
765
|
+
await context.transport?.closeSpawnedAgent?.(agentId);
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
logCodexRuntimeStep("turn.close_spawned_agent_failed", context.launchPerfStartedAtMs, {
|
|
769
|
+
providerSessionId: context.providerSessionId,
|
|
770
|
+
agentId,
|
|
771
|
+
error: error instanceof Error ? error.message : String(error)
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async handleEvent(event, request, context, signal) {
|
|
454
776
|
const eventType = ensureText(readProp(event, "type")).trim();
|
|
777
|
+
const interrupted = signal.aborted;
|
|
455
778
|
if (eventType.length === 0) {
|
|
456
779
|
return;
|
|
457
780
|
}
|
|
781
|
+
if (isCodexSpawnAgentEvent(event)) {
|
|
782
|
+
context.lifecycle.keepTransportAliveAfterTurn = true;
|
|
783
|
+
}
|
|
458
784
|
if (context.lastSignatureByIdentity.size === 0 && eventType.startsWith("item.")) {
|
|
459
785
|
logCodexRuntimeStep("turn.first_item_event", context.launchPerfStartedAtMs, {
|
|
460
786
|
sessionId: request.sessionId,
|
|
@@ -463,17 +789,14 @@ export class CodexRuntimeAdapter {
|
|
|
463
789
|
});
|
|
464
790
|
}
|
|
465
791
|
if (eventType === "turn.completed") {
|
|
466
|
-
|
|
467
|
-
type: "complete",
|
|
468
|
-
status: "completed",
|
|
469
|
-
providerSessionId: context.providerSessionId,
|
|
470
|
-
rawStoreRef: context.rawStoreRef,
|
|
792
|
+
context.lifecycle.pendingComplete = {
|
|
471
793
|
detail: "codex turn completed",
|
|
472
794
|
timestamp: pickTimestamp(event)
|
|
473
|
-
}
|
|
795
|
+
};
|
|
474
796
|
return;
|
|
475
797
|
}
|
|
476
798
|
if (eventType === "turn.failed") {
|
|
799
|
+
context.lifecycle.parentTurnStopped = true;
|
|
477
800
|
const detail = extractTextBlocks(readProp(event, "error")).trim() || "codex turn failed";
|
|
478
801
|
await context.sink.emit({
|
|
479
802
|
type: "error",
|
|
@@ -487,6 +810,7 @@ export class CodexRuntimeAdapter {
|
|
|
487
810
|
return;
|
|
488
811
|
}
|
|
489
812
|
if (eventType === "turn.interrupted") {
|
|
813
|
+
context.lifecycle.parentTurnStopped = true;
|
|
490
814
|
await context.sink.emit({
|
|
491
815
|
type: "interrupted",
|
|
492
816
|
status: "interrupted",
|
|
@@ -1095,6 +1419,18 @@ function createCodexAppServerTransport(options) {
|
|
|
1095
1419
|
}
|
|
1096
1420
|
});
|
|
1097
1421
|
},
|
|
1422
|
+
async closeSpawnedAgent(agentId) {
|
|
1423
|
+
const normalizedAgentId = agentId.trim();
|
|
1424
|
+
if (!normalizedAgentId) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
await sendJsonRpcRequest(child, pendingResponses, () => nextJsonRpcId("thread-unsubscribe", () => ++requestSequence), {
|
|
1428
|
+
method: "thread/unsubscribe",
|
|
1429
|
+
params: {
|
|
1430
|
+
threadId: normalizedAgentId
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
},
|
|
1098
1434
|
setNotificationHandler(handler) {
|
|
1099
1435
|
notificationHandler = handler;
|
|
1100
1436
|
},
|
|
@@ -1686,6 +2022,17 @@ function translateCodexAppServerItem(item) {
|
|
|
1686
2022
|
status: normalizeCodexItemStatus(item.status)
|
|
1687
2023
|
};
|
|
1688
2024
|
}
|
|
2025
|
+
if (itemType === "functionCall" || itemType === "function_call") {
|
|
2026
|
+
return {
|
|
2027
|
+
type: "function_call",
|
|
2028
|
+
id: item.id,
|
|
2029
|
+
name: item.name,
|
|
2030
|
+
arguments: readProp(item, "arguments") ?? readProp(item, "input"),
|
|
2031
|
+
output: item.output,
|
|
2032
|
+
error: item.error,
|
|
2033
|
+
status: normalizeCodexItemStatus(item.status)
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
1689
2036
|
if (itemType === "dynamicToolCall") {
|
|
1690
2037
|
const toolName = ensureText(item.tool).trim();
|
|
1691
2038
|
const patchText = isCodexExecCommandToolName(toolName)
|
|
@@ -2118,6 +2465,19 @@ function normalizeText(value) {
|
|
|
2118
2465
|
const normalized = ensureText(value).trim();
|
|
2119
2466
|
return normalized.length > 0 ? normalized : null;
|
|
2120
2467
|
}
|
|
2468
|
+
function parseStructuredJson(value) {
|
|
2469
|
+
const normalized = value.trim();
|
|
2470
|
+
if (!normalized) {
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
try {
|
|
2474
|
+
const parsed = JSON.parse(normalized);
|
|
2475
|
+
return toRecord(parsed);
|
|
2476
|
+
}
|
|
2477
|
+
catch {
|
|
2478
|
+
return null;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2121
2481
|
function readThreadIdFromRawStore(rawStoreRef) {
|
|
2122
2482
|
const filePath = ensureText(rawStoreRef).trim();
|
|
2123
2483
|
if (!filePath || !existsSync(filePath)) {
|