@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.
Files changed (170) hide show
  1. package/dist/public/assets/{AdaptiveButlerPage-khJQh6a_.js → AdaptiveButlerPage-D-gXre7Y.js} +2 -2
  2. package/dist/public/assets/{App-CcDXqFl1.css → App-7zrCMhE-.css} +1 -1
  3. package/dist/public/assets/App-Dl-mcdqy.js +30 -0
  4. package/dist/public/assets/{BootstrapPage-DcfYtoLC.js → BootstrapPage-B-yMdfpQ.js} +1 -1
  5. package/dist/public/assets/ConversationPage-DRQ5Sg_d.js +9 -0
  6. package/dist/public/assets/{DesktopDetachPreviewPage-CXUPMcBz.js → DesktopDetachPreviewPage-D1DMaGcy.js} +1 -1
  7. package/dist/public/assets/{DesktopModal-bMdI1jEe.js → DesktopModal-BnfGW2gk.js} +1 -1
  8. package/dist/public/assets/DesktopWindowPage-2SWAi0xz.js +2 -0
  9. package/dist/public/assets/FileContextPanel-fbPuE9dO.js +1 -0
  10. package/dist/public/assets/GitSidebar-BkmesJJR.js +6 -0
  11. package/dist/public/assets/{MobileCreateSessionSheet-DWPBsEx8.js → MobileCreateSessionSheet-CEJcDBZJ.js} +1 -1
  12. package/dist/public/assets/{MobileSheet-BXvQPkxt.js → MobileSheet-rkn_CUOY.js} +1 -1
  13. package/dist/public/assets/{MobileTopHeaderFrame-vdYOyaaB.js → MobileTopHeaderFrame-CU0wsYSS.js} +1 -1
  14. package/dist/public/assets/MobileWorkspaceSwitcherHeader-idl8o1OB.js +1 -0
  15. package/dist/public/assets/{PluginAccessOverview-C77TeZTK.js → PluginAccessOverview-BBgM6tb0.js} +1 -1
  16. package/dist/public/assets/{PluginContainerPage-DdSwOCw-.js → PluginContainerPage-D-ly3i3H.js} +1 -1
  17. package/dist/public/assets/{PluginDetailPage-BK1yTzvO.js → PluginDetailPage-CWAHYyyG.js} +1 -1
  18. package/dist/public/assets/{PluginsListPage-DAAwSc6W.js → PluginsListPage-Cte3vBgR.js} +1 -1
  19. package/dist/public/assets/{RelayConnectEntryPage-4Yyo2p8b.js → RelayConnectEntryPage-sRJlstx9.js} +1 -1
  20. package/dist/public/assets/{ServerSettingsModal-C_DEisHs.js → ServerSettingsModal-BBft9KEC.js} +1 -1
  21. package/dist/public/assets/SessionIndexPage-CN7cEdl9.js +1 -0
  22. package/dist/public/assets/SettingsPage-BGT-YqG2.js +2 -0
  23. package/dist/public/assets/TerminalManagerPanel-6-ZJ8vGn.js +1 -0
  24. package/dist/public/assets/TerminalPage-CUXXQYU2.js +55 -0
  25. package/dist/public/assets/{TerminalRuntimeFallbackModal-KvG6k4AQ.js → TerminalRuntimeFallbackModal-zc3qqMKJ.js} +1 -1
  26. package/dist/public/assets/ToolFilesPage-QzsZyr0F.js +1 -0
  27. package/dist/public/assets/ToolGitPage-CXg4ncuT.js +1 -0
  28. package/dist/public/assets/ToolProcessesPage-BPsOsg4w.js +1 -0
  29. package/dist/public/assets/ToolsHomePage-D1n4FU1s.js +1 -0
  30. package/dist/public/assets/WorkbenchLandingPage-BaU_dXls.js +1 -0
  31. package/dist/public/assets/WorkbenchLayout-DViAJhHz.js +1027 -0
  32. package/dist/public/assets/{WorkbenchModal-xbx1o6MO.js → WorkbenchModal-DWsNm2B2.js} +1 -1
  33. package/dist/public/assets/WorkbenchShellRoute-BGfRqBUa.js +1 -0
  34. package/dist/public/assets/WorkbenchShellRoute-f2jWjHWu.css +1 -0
  35. package/dist/public/assets/WorkspaceDebugDetailPage-BX0zVSsI.js +1 -0
  36. package/dist/public/assets/WorkspaceDetailPage-Dx6JX4jx.js +1 -0
  37. package/dist/public/assets/WorkspaceHomePage-DQVJ042Z.js +1 -0
  38. package/dist/public/assets/{client-runtime-manager-Bwau7p1v.js → client-runtime-manager-D9VbgJZ_.js} +1 -1
  39. package/dist/public/assets/host-alias-0TfFnYxR.js +1 -0
  40. package/dist/public/assets/index-DREvg1Yu.css +1 -0
  41. package/dist/public/assets/index-FOhyOpGY.js +50 -0
  42. package/dist/public/assets/{login-direct-candidate-resolver-CKUQ07IA.js → login-direct-candidate-resolver-17wEvjhh.js} +1 -1
  43. package/dist/public/assets/peer-host-config-sync-vYkmqzNz.js +1 -0
  44. package/dist/public/assets/{plugin-permission-copy-DIVk5jNp.js → plugin-permission-copy-apDn8EWG.js} +1 -1
  45. package/dist/public/assets/{plugins-api-DHJVvPZw.js → plugins-api-CnZYRKoS.js} +1 -1
  46. package/dist/public/assets/{preferences-service-CyxxeBmS.js → preferences-service-PZlLLAWH.js} +1 -1
  47. package/dist/public/assets/relay-entry-DhHwflXl.js +1 -0
  48. package/dist/public/assets/styles-BhKoKfQ_.css +1 -0
  49. package/dist/public/assets/terminal-runtime-meta-2zvacxvM.js +1 -0
  50. package/dist/public/assets/{useRegisteredDebugTemplates-wCGD2SLW.js → useRegisteredDebugTemplates-CJ-o4tFl.js} +1 -1
  51. package/dist/public/assets/workbench-navigation-aqJ1ay4M.js +1 -0
  52. package/dist/public/index.html +2 -2
  53. package/dist/server/middlewares/auth-guard.js +1 -0
  54. package/dist/server/middlewares/auth-guard.js.map +1 -1
  55. package/dist/server/modules/peer-host/host-api-proxy-service.d.ts +9 -0
  56. package/dist/server/modules/peer-host/host-api-proxy-service.js +174 -0
  57. package/dist/server/modules/peer-host/host-api-proxy-service.js.map +1 -0
  58. package/dist/server/modules/peer-host/host-handshake-controller.d.ts +7 -0
  59. package/dist/server/modules/peer-host/host-handshake-controller.js +10 -0
  60. package/dist/server/modules/peer-host/host-handshake-controller.js.map +1 -0
  61. package/dist/server/modules/peer-host/host-handshake.d.ts +15 -0
  62. package/dist/server/modules/peer-host/host-handshake.js +20 -0
  63. package/dist/server/modules/peer-host/host-handshake.js.map +1 -0
  64. package/dist/server/modules/peer-host/host-ws-proxy-service.d.ts +14 -0
  65. package/dist/server/modules/peer-host/host-ws-proxy-service.js +256 -0
  66. package/dist/server/modules/peer-host/host-ws-proxy-service.js.map +1 -0
  67. package/dist/server/modules/peer-host/peer-host-controller.d.ts +55 -0
  68. package/dist/server/modules/peer-host/peer-host-controller.js +62 -0
  69. package/dist/server/modules/peer-host/peer-host-controller.js.map +1 -0
  70. package/dist/server/modules/peer-host/peer-host-service.d.ts +77 -0
  71. package/dist/server/modules/peer-host/peer-host-service.js +529 -0
  72. package/dist/server/modules/peer-host/peer-host-service.js.map +1 -0
  73. package/dist/server/modules/provider/provider-discovery-runtime.js +16 -1
  74. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  75. package/dist/server/modules/sessions/codex-app-server-helper-client.js +14 -0
  76. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  77. package/dist/server/modules/sessions/codex-app-server-helper-process.js +59 -0
  78. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  79. package/dist/server/modules/sessions/codex-session-title-generator.d.ts +19 -0
  80. package/dist/server/modules/sessions/codex-session-title-generator.js +308 -0
  81. package/dist/server/modules/sessions/codex-session-title-generator.js.map +1 -0
  82. package/dist/server/modules/sessions/session-history-service.d.ts +16 -0
  83. package/dist/server/modules/sessions/session-history-service.js +208 -12
  84. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  85. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  86. package/dist/server/modules/sessions/session-live-runtime-service.js +181 -80
  87. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  88. package/dist/server/modules/sessions/session-title-utils.d.ts +3 -0
  89. package/dist/server/modules/sessions/session-title-utils.js +25 -0
  90. package/dist/server/modules/sessions/session-title-utils.js.map +1 -0
  91. package/dist/server/modules/sessions/workspace-office-mcp-config.d.ts +2 -1
  92. package/dist/server/modules/sessions/workspace-office-mcp-config.js +44 -3
  93. package/dist/server/modules/sessions/workspace-office-mcp-config.js.map +1 -1
  94. package/dist/server/modules/tasks/task-manager.d.ts +2 -1
  95. package/dist/server/modules/tasks/task-manager.js +3 -0
  96. package/dist/server/modules/tasks/task-manager.js.map +1 -1
  97. package/dist/server/modules/tasks/task-scheduler.d.ts +2 -1
  98. package/dist/server/modules/tasks/task-scheduler.js +21 -0
  99. package/dist/server/modules/tasks/task-scheduler.js.map +1 -1
  100. package/dist/server/modules/tasks/task-types.d.ts +6 -0
  101. package/dist/server/modules/tasks/task-types.js +1 -0
  102. package/dist/server/modules/tasks/task-types.js.map +1 -1
  103. package/dist/server/modules/workbench/workbench-controller.js +3 -2
  104. package/dist/server/modules/workbench/workbench-controller.js.map +1 -1
  105. package/dist/server/modules/workspace/affairs-library-service.d.ts +1 -0
  106. package/dist/server/modules/workspace/affairs-library-service.js +80 -0
  107. package/dist/server/modules/workspace/affairs-library-service.js.map +1 -1
  108. package/dist/server/modules/workspace/affairs-lightweight-session-service.js +13 -9
  109. package/dist/server/modules/workspace/affairs-lightweight-session-service.js.map +1 -1
  110. package/dist/server/routes/peer-hosts.d.ts +3 -0
  111. package/dist/server/routes/peer-hosts.js +18 -0
  112. package/dist/server/routes/peer-hosts.js.map +1 -0
  113. package/dist/server/routes/public.d.ts +2 -1
  114. package/dist/server/routes/public.js +2 -1
  115. package/dist/server/routes/public.js.map +1 -1
  116. package/dist/server/server/create-server.d.ts +4 -0
  117. package/dist/server/server/create-server.js +30 -2
  118. package/dist/server/server/create-server.js.map +1 -1
  119. package/dist/server/shared/http/error-handler.js +12 -0
  120. package/dist/server/shared/http/error-handler.js.map +1 -1
  121. package/dist/server/storage/repositories/peer-host-repository.d.ts +44 -0
  122. package/dist/server/storage/repositories/peer-host-repository.js +271 -0
  123. package/dist/server/storage/repositories/peer-host-repository.js.map +1 -0
  124. package/dist/server/storage/sqlite/client.js +81 -0
  125. package/dist/server/storage/sqlite/client.js.map +1 -1
  126. package/dist/server/storage/sqlite/schema.sql +64 -0
  127. package/dist/server/types/domain.d.ts +43 -0
  128. package/dist/server/ws/workbench-ws-hub.js +5 -14
  129. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  130. package/dist/server/ws/ws-server.d.ts +2 -1
  131. package/dist/server/ws/ws-server.js +5 -1
  132. package/dist/server/ws/ws-server.js.map +1 -1
  133. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +7 -0
  134. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +623 -9
  135. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  136. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +8 -1
  137. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  138. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +4 -0
  139. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +375 -15
  140. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  141. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +11 -0
  142. package/package.json +1 -1
  143. package/dist/public/assets/App-If9gThKM.js +0 -30
  144. package/dist/public/assets/ConversationPage-Bfb7GLTM.js +0 -9
  145. package/dist/public/assets/DesktopWindowPage-D1xwgS-7.js +0 -2
  146. package/dist/public/assets/FileContextPanel-C4syif3B.js +0 -1
  147. package/dist/public/assets/GitSidebar-DduL9aTV.js +0 -6
  148. package/dist/public/assets/MobileWorkspaceSwitcherHeader-DT330cAx.js +0 -1
  149. package/dist/public/assets/SessionIndexPage-DyMikN_x.js +0 -1
  150. package/dist/public/assets/SettingsPage-CDAVsPr3.js +0 -2
  151. package/dist/public/assets/TerminalManagerPanel-4OR47vcf.js +0 -1
  152. package/dist/public/assets/TerminalPage-Pvx396YX.js +0 -55
  153. package/dist/public/assets/ToolFilesPage-DrYHk0N-.js +0 -1
  154. package/dist/public/assets/ToolGitPage-Dz1q-Ns_.js +0 -1
  155. package/dist/public/assets/ToolProcessesPage-CRhphOmM.js +0 -1
  156. package/dist/public/assets/ToolsHomePage-BJSDLR6T.js +0 -1
  157. package/dist/public/assets/WorkbenchLandingPage-BlkxdOLC.js +0 -1
  158. package/dist/public/assets/WorkbenchLayout-D-U7ghT0.js +0 -1022
  159. package/dist/public/assets/WorkbenchShellRoute-DyWSCHz_.js +0 -1
  160. package/dist/public/assets/WorkbenchShellRoute-htbkGbtW.css +0 -1
  161. package/dist/public/assets/WorkspaceDebugDetailPage-B4ol2_a5.js +0 -1
  162. package/dist/public/assets/WorkspaceDetailPage-DMakfmHR.js +0 -1
  163. package/dist/public/assets/WorkspaceHomePage-tmCafatd.js +0 -1
  164. package/dist/public/assets/index-DmUJ8tIw.css +0 -1
  165. package/dist/public/assets/index-_OCkVmfl.js +0 -50
  166. package/dist/public/assets/relay-entry-B5GmiOrR.js +0 -1
  167. package/dist/public/assets/styles-DkbkRgWw.css +0 -1
  168. package/dist/public/assets/terminal-runtime-meta-DBsyT35T.js +0 -1
  169. package/dist/public/assets/workbench-navigation-RyUjchbD.js +0 -1
  170. /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, readdirSync, readFileSync, statSync } from "node:fs";
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.close();
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.close();
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.aborted);
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.aborted);
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 handleEvent(event, request, context, interrupted) {
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
- await context.sink.emit({
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)) {