@jingyi0605/codingns 0.9.7 → 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 (155) hide show
  1. package/dist/public/assets/{AdaptiveButlerPage-DclGPzEx.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-Bl21SsuW.js → BootstrapPage-B-yMdfpQ.js} +1 -1
  5. package/dist/public/assets/ConversationPage-DRQ5Sg_d.js +9 -0
  6. package/dist/public/assets/{DesktopDetachPreviewPage-uaOHVsjV.js → DesktopDetachPreviewPage-D1DMaGcy.js} +1 -1
  7. package/dist/public/assets/{DesktopModal-BxsogpLf.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-CqtmfVNo.js → MobileCreateSessionSheet-CEJcDBZJ.js} +1 -1
  12. package/dist/public/assets/{MobileSheet-C5IVmUsO.js → MobileSheet-rkn_CUOY.js} +1 -1
  13. package/dist/public/assets/{MobileTopHeaderFrame-EoBm3-X0.js → MobileTopHeaderFrame-CU0wsYSS.js} +1 -1
  14. package/dist/public/assets/MobileWorkspaceSwitcherHeader-idl8o1OB.js +1 -0
  15. package/dist/public/assets/{PluginAccessOverview-CrQiQxxZ.js → PluginAccessOverview-BBgM6tb0.js} +1 -1
  16. package/dist/public/assets/{PluginContainerPage-_2u-9thM.js → PluginContainerPage-D-ly3i3H.js} +1 -1
  17. package/dist/public/assets/{PluginDetailPage-F9cKjSCp.js → PluginDetailPage-CWAHYyyG.js} +1 -1
  18. package/dist/public/assets/{PluginsListPage-BOhcua_4.js → PluginsListPage-Cte3vBgR.js} +1 -1
  19. package/dist/public/assets/{RelayConnectEntryPage-Ck_uZLzj.js → RelayConnectEntryPage-sRJlstx9.js} +1 -1
  20. package/dist/public/assets/{ServerSettingsModal-ICd82a_x.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-DGPR_CMh.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-CsZeLArg.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-BgGugw8T.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-ty2uOY5y.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-iK3faI3n.js → plugin-permission-copy-apDn8EWG.js} +1 -1
  45. package/dist/public/assets/{plugins-api-CmV7aDGA.js → plugins-api-CnZYRKoS.js} +1 -1
  46. package/dist/public/assets/{preferences-service-BoSmT_ny.js → preferences-service-PZlLLAWH.js} +1 -1
  47. package/dist/public/assets/relay-entry-DhHwflXl.js +1 -0
  48. package/dist/public/assets/terminal-runtime-meta-2zvacxvM.js +1 -0
  49. package/dist/public/assets/{useRegisteredDebugTemplates-B_vXUtSe.js → useRegisteredDebugTemplates-CJ-o4tFl.js} +1 -1
  50. package/dist/public/assets/workbench-navigation-aqJ1ay4M.js +1 -0
  51. package/dist/public/index.html +2 -2
  52. package/dist/server/middlewares/auth-guard.js +1 -0
  53. package/dist/server/middlewares/auth-guard.js.map +1 -1
  54. package/dist/server/modules/peer-host/host-api-proxy-service.d.ts +9 -0
  55. package/dist/server/modules/peer-host/host-api-proxy-service.js +174 -0
  56. package/dist/server/modules/peer-host/host-api-proxy-service.js.map +1 -0
  57. package/dist/server/modules/peer-host/host-handshake-controller.d.ts +7 -0
  58. package/dist/server/modules/peer-host/host-handshake-controller.js +10 -0
  59. package/dist/server/modules/peer-host/host-handshake-controller.js.map +1 -0
  60. package/dist/server/modules/peer-host/host-handshake.d.ts +15 -0
  61. package/dist/server/modules/peer-host/host-handshake.js +20 -0
  62. package/dist/server/modules/peer-host/host-handshake.js.map +1 -0
  63. package/dist/server/modules/peer-host/host-ws-proxy-service.d.ts +14 -0
  64. package/dist/server/modules/peer-host/host-ws-proxy-service.js +256 -0
  65. package/dist/server/modules/peer-host/host-ws-proxy-service.js.map +1 -0
  66. package/dist/server/modules/peer-host/peer-host-controller.d.ts +55 -0
  67. package/dist/server/modules/peer-host/peer-host-controller.js +62 -0
  68. package/dist/server/modules/peer-host/peer-host-controller.js.map +1 -0
  69. package/dist/server/modules/peer-host/peer-host-service.d.ts +77 -0
  70. package/dist/server/modules/peer-host/peer-host-service.js +529 -0
  71. package/dist/server/modules/peer-host/peer-host-service.js.map +1 -0
  72. package/dist/server/modules/sessions/codex-session-title-generator.js +18 -5
  73. package/dist/server/modules/sessions/codex-session-title-generator.js.map +1 -1
  74. package/dist/server/modules/sessions/session-history-service.d.ts +12 -1
  75. package/dist/server/modules/sessions/session-history-service.js +51 -38
  76. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  77. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  78. package/dist/server/modules/sessions/session-live-runtime-service.js +181 -80
  79. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  80. package/dist/server/modules/sessions/session-title-utils.d.ts +3 -0
  81. package/dist/server/modules/sessions/session-title-utils.js +25 -0
  82. package/dist/server/modules/sessions/session-title-utils.js.map +1 -0
  83. package/dist/server/modules/sessions/workspace-office-mcp-config.d.ts +2 -1
  84. package/dist/server/modules/sessions/workspace-office-mcp-config.js +44 -3
  85. package/dist/server/modules/sessions/workspace-office-mcp-config.js.map +1 -1
  86. package/dist/server/modules/tasks/task-manager.d.ts +2 -1
  87. package/dist/server/modules/tasks/task-manager.js +3 -0
  88. package/dist/server/modules/tasks/task-manager.js.map +1 -1
  89. package/dist/server/modules/tasks/task-scheduler.d.ts +2 -1
  90. package/dist/server/modules/tasks/task-scheduler.js +21 -0
  91. package/dist/server/modules/tasks/task-scheduler.js.map +1 -1
  92. package/dist/server/modules/tasks/task-types.d.ts +5 -0
  93. package/dist/server/modules/tasks/task-types.js.map +1 -1
  94. package/dist/server/modules/workbench/workbench-controller.js +3 -2
  95. package/dist/server/modules/workbench/workbench-controller.js.map +1 -1
  96. package/dist/server/modules/workspace/affairs-library-service.d.ts +1 -0
  97. package/dist/server/modules/workspace/affairs-library-service.js +80 -0
  98. package/dist/server/modules/workspace/affairs-library-service.js.map +1 -1
  99. package/dist/server/routes/peer-hosts.d.ts +3 -0
  100. package/dist/server/routes/peer-hosts.js +18 -0
  101. package/dist/server/routes/peer-hosts.js.map +1 -0
  102. package/dist/server/routes/public.d.ts +2 -1
  103. package/dist/server/routes/public.js +2 -1
  104. package/dist/server/routes/public.js.map +1 -1
  105. package/dist/server/server/create-server.d.ts +4 -0
  106. package/dist/server/server/create-server.js +30 -2
  107. package/dist/server/server/create-server.js.map +1 -1
  108. package/dist/server/shared/http/error-handler.js +12 -0
  109. package/dist/server/shared/http/error-handler.js.map +1 -1
  110. package/dist/server/storage/repositories/peer-host-repository.d.ts +44 -0
  111. package/dist/server/storage/repositories/peer-host-repository.js +271 -0
  112. package/dist/server/storage/repositories/peer-host-repository.js.map +1 -0
  113. package/dist/server/storage/sqlite/client.js +81 -0
  114. package/dist/server/storage/sqlite/client.js.map +1 -1
  115. package/dist/server/storage/sqlite/schema.sql +64 -0
  116. package/dist/server/types/domain.d.ts +43 -0
  117. package/dist/server/ws/workbench-ws-hub.js +5 -14
  118. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  119. package/dist/server/ws/ws-server.d.ts +2 -1
  120. package/dist/server/ws/ws-server.js +5 -1
  121. package/dist/server/ws/ws-server.js.map +1 -1
  122. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +29 -5
  123. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  124. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +8 -1
  125. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  126. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +3 -0
  127. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +144 -27
  128. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  129. package/package.json +1 -1
  130. package/dist/public/assets/App-BxX5mm9o.js +0 -30
  131. package/dist/public/assets/ConversationPage-CmiVCV0q.js +0 -9
  132. package/dist/public/assets/DesktopWindowPage-Bubfw1nC.js +0 -2
  133. package/dist/public/assets/FileContextPanel-DrYWcTkp.js +0 -1
  134. package/dist/public/assets/GitSidebar-CQsfJqun.js +0 -6
  135. package/dist/public/assets/MobileWorkspaceSwitcherHeader-BC8MMQs_.js +0 -1
  136. package/dist/public/assets/SessionIndexPage-Cox6P6dw.js +0 -1
  137. package/dist/public/assets/SettingsPage-BI5cM3j5.js +0 -2
  138. package/dist/public/assets/TerminalManagerPanel-DiVhBQhf.js +0 -1
  139. package/dist/public/assets/TerminalPage-DUMUO7Ng.js +0 -55
  140. package/dist/public/assets/ToolFilesPage-m88CAp-r.js +0 -1
  141. package/dist/public/assets/ToolGitPage-DCYpfTsb.js +0 -1
  142. package/dist/public/assets/ToolProcessesPage-Bk5ulsyq.js +0 -1
  143. package/dist/public/assets/ToolsHomePage-BrTwfjI7.js +0 -1
  144. package/dist/public/assets/WorkbenchLandingPage-q4AAmdMV.js +0 -1
  145. package/dist/public/assets/WorkbenchLayout-DD8b-m2G.js +0 -1027
  146. package/dist/public/assets/WorkbenchShellRoute-BaiW_vfb.js +0 -1
  147. package/dist/public/assets/WorkbenchShellRoute-CxKYZ6uF.css +0 -1
  148. package/dist/public/assets/WorkspaceDebugDetailPage-BcUUDEyw.js +0 -1
  149. package/dist/public/assets/WorkspaceDetailPage-zEZ1VARA.js +0 -1
  150. package/dist/public/assets/WorkspaceHomePage-CR0rqS4e.js +0 -1
  151. package/dist/public/assets/index-CFyk1rgJ.js +0 -50
  152. package/dist/public/assets/index-CrU73EIV.css +0 -1
  153. package/dist/public/assets/relay-entry-C8k5qsl5.js +0 -1
  154. package/dist/public/assets/terminal-runtime-meta-okQIDzfA.js +0 -1
  155. package/dist/public/assets/workbench-navigation-DoDaQR4z.js +0 -1
@@ -13,9 +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;
16
+ const CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS = normalizePositiveInteger(process.env.CODINGNS_CODEX_SPAWN_AGENT_GRACE_MS, 6 * 60 * 60 * 1000);
17
17
  const CODEX_SPAWN_AGENT_RAW_SCAN_BYTES = 2 * 1024 * 1024;
18
- const CODEX_SPAWN_AGENT_POLL_INTERVAL_MS = 2_000;
18
+ const CODEX_SPAWN_AGENT_POLL_INTERVAL_MS = normalizePositiveInteger(process.env.CODINGNS_CODEX_SPAWN_AGENT_POLL_INTERVAL_MS, 2_000);
19
19
  function logCodexRuntimeStep(scope, startedAtMs, detail = {}) {
20
20
  if (!CODEX_RUNTIME_DEBUG_ENABLED) {
21
21
  return;
@@ -24,6 +24,10 @@ function logCodexRuntimeStep(scope, startedAtMs, detail = {}) {
24
24
  const suffix = formatCodexRuntimeDebugDetail(detail);
25
25
  console.info(`[perf][codex-runtime] ${scope} ${durationMs}ms${suffix ? ` ${suffix}` : ""}`);
26
26
  }
27
+ function normalizePositiveInteger(value, fallback) {
28
+ const parsed = Number(value);
29
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
30
+ }
27
31
  function closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef) {
28
32
  if (!shouldKeepCodexTransportAliveAfterTurn(lifecycle, rawStoreRef)) {
29
33
  transport.close();
@@ -40,6 +44,15 @@ function closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef) {
40
44
  }
41
45
  }
42
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
+ }
43
56
  return lifecycle.keepTransportAliveAfterTurn || codexRawStoreContainsSpawnAgentCall(rawStoreRef);
44
57
  }
45
58
  function codexRawStoreContainsSpawnAgentCall(rawStoreRef) {
@@ -86,21 +99,26 @@ function extractCodexSpawnedAgentIdsFromRawStore(rawStoreRef) {
86
99
  }
87
100
  if (isCodexSpawnAgentItem(payload)) {
88
101
  const callId = ensureText(readProp(payload, "call_id")).trim();
102
+ const agentId = extractCodexAgentIdFromToolOutput(readProp(payload, "output"));
89
103
  if (callId) {
90
104
  spawnCallIds.add(callId);
91
105
  }
106
+ if (agentId) {
107
+ agentIds.add(agentId);
108
+ }
92
109
  continue;
93
110
  }
94
111
  if (ensureText(readProp(payload, "type")).trim() !== "function_call_output") {
95
112
  continue;
96
113
  }
97
114
  const callId = ensureText(readProp(payload, "call_id")).trim();
98
- if (!callId || !spawnCallIds.has(callId)) {
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) {
99
118
  continue;
100
119
  }
101
- const parsedOutput = parseStructuredJson(ensureText(readProp(payload, "output")));
102
- const agentId = ensureText(readProp(parsedOutput, "agent_id")).trim();
103
- if (looksLikeCodexThreadId(agentId)) {
120
+ const agentId = extractCodexAgentIdFromToolOutput(readProp(payload, "output"));
121
+ if (agentId) {
104
122
  agentIds.add(agentId);
105
123
  }
106
124
  }
@@ -110,6 +128,28 @@ function extractCodexSpawnedAgentIdsFromRawStore(rawStoreRef) {
110
128
  }
111
129
  return [...agentIds];
112
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
+ }
113
153
  function isCodexRawStoreTerminal(rawStoreRef) {
114
154
  const text = readCodexRawStoreTail(rawStoreRef);
115
155
  if (!text) {
@@ -177,10 +217,10 @@ function isCodexSpawnAgentEvent(event) {
177
217
  return isCodexSpawnAgentItem(toRecord(readProp(eventRecord, "item")) ?? eventRecord);
178
218
  }
179
219
  function markCodexSpawnAgentLifecycleFromEvents(lifecycle, events) {
180
- if (lifecycle.keepTransportAliveAfterTurn) {
181
- return;
220
+ for (const agentId of extractCodexSpawnedAgentIdsFromEvents(events)) {
221
+ lifecycle.spawnedAgentIds.add(agentId);
182
222
  }
183
- if (events.some((event) => isCodexSpawnAgentEvent(event))) {
223
+ if (!lifecycle.keepTransportAliveAfterTurn && events.some((event) => isCodexSpawnAgentEvent(event))) {
184
224
  lifecycle.keepTransportAliveAfterTurn = true;
185
225
  }
186
226
  }
@@ -255,7 +295,12 @@ export class CodexRuntimeAdapter {
255
295
  const abortController = new AbortController();
256
296
  const eventQueue = createAsyncEventQueue();
257
297
  const lifecycle = {
258
- keepTransportAliveAfterTurn: false
298
+ keepTransportAliveAfterTurn: false,
299
+ spawnedAgentsSettledAfterTurn: false,
300
+ parentTurnStopped: false,
301
+ spawnedAgentIds: new Set(),
302
+ pendingComplete: null,
303
+ closedSpawnedAgentIds: new Set()
259
304
  };
260
305
  const translateNotification = createCodexAppServerNotificationTranslator();
261
306
  const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
@@ -312,6 +357,7 @@ export class CodexRuntimeAdapter {
312
357
  });
313
358
  transport.setOnClose((error) => {
314
359
  if (error) {
360
+ lifecycle.parentTurnStopped = true;
315
361
  eventQueue.push({
316
362
  type: "turn.failed",
317
363
  timestamp: nextTimestamp(),
@@ -350,7 +396,7 @@ export class CodexRuntimeAdapter {
350
396
  transport.close();
351
397
  },
352
398
  isAlive: () => transport.isClosed() === false,
353
- completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs, lifecycle).finally(() => {
399
+ completed: this.runTurn(null, request, sink, providerSessionId, rawStoreRef, abortController, eventQueue.iterator, [], launchedAtMs, launchPerfStartedAtMs, lifecycle, transport).finally(() => {
354
400
  closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
355
401
  })
356
402
  };
@@ -446,7 +492,12 @@ export class CodexRuntimeAdapter {
446
492
  const abortController = new AbortController();
447
493
  const eventQueue = createAsyncEventQueue();
448
494
  const lifecycle = {
449
- keepTransportAliveAfterTurn: false
495
+ keepTransportAliveAfterTurn: false,
496
+ spawnedAgentsSettledAfterTurn: false,
497
+ parentTurnStopped: false,
498
+ spawnedAgentIds: new Set(),
499
+ pendingComplete: null,
500
+ closedSpawnedAgentIds: new Set()
450
501
  };
451
502
  const translateNotification = createCodexAppServerNotificationTranslator();
452
503
  const forwardTranslatedNotification = createCodexTranslatedNotificationForwarder(eventQueue);
@@ -488,6 +539,7 @@ export class CodexRuntimeAdapter {
488
539
  });
489
540
  transport.setOnClose((error) => {
490
541
  if (error) {
542
+ lifecycle.parentTurnStopped = true;
491
543
  eventQueue.push({
492
544
  type: "turn.failed",
493
545
  timestamp: nextTimestamp(),
@@ -526,7 +578,7 @@ export class CodexRuntimeAdapter {
526
578
  transport.close();
527
579
  },
528
580
  isAlive: () => transport.isClosed() === false,
529
- completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now(), performance.now(), lifecycle).finally(() => {
581
+ completed: this.runTurn(null, request, sink, resolvedSessionId, rawStoreRef, abortController, eventQueue.iterator, [], Date.now(), performance.now(), lifecycle, transport).finally(() => {
530
582
  closeCodexTransportAfterTurn(transport, lifecycle, rawStoreRef);
531
583
  })
532
584
  };
@@ -572,8 +624,13 @@ export class CodexRuntimeAdapter {
572
624
  return fallbackMatch;
573
625
  }
574
626
  async runTurn(thread, request, sink, providerSessionId, rawStoreRef, abortController, preparedEvents, bufferedEvents = [], launchedAtMs = Date.now(), launchPerfStartedAtMs = performance.now(), lifecycle = {
575
- keepTransportAliveAfterTurn: false
576
- }) {
627
+ keepTransportAliveAfterTurn: false,
628
+ spawnedAgentsSettledAfterTurn: false,
629
+ parentTurnStopped: false,
630
+ spawnedAgentIds: new Set(),
631
+ pendingComplete: null,
632
+ closedSpawnedAgentIds: new Set()
633
+ }, transport = null) {
577
634
  const context = {
578
635
  providerSessionId,
579
636
  rawStoreRef,
@@ -581,6 +638,7 @@ export class CodexRuntimeAdapter {
581
638
  // 否则前端会把新 assistant/tool 消息排到旧消息前面,表现成用户消息一直挂在底部。
582
639
  sequence: Math.max(0, request.sequenceBase ?? 0),
583
640
  lifecycle,
641
+ transport,
584
642
  toolNameByCallId: new Map(),
585
643
  stableMessageRefByIdentity: new Map(),
586
644
  lastSignatureByIdentity: new Map(),
@@ -601,7 +659,7 @@ export class CodexRuntimeAdapter {
601
659
  for (const event of bufferedEvents) {
602
660
  await this.refreshSessionBindingIfNeeded(context);
603
661
  persistSyntheticEventIfNeeded(context.rawStoreRef, context.providerSessionId, event);
604
- await this.handleEvent(event, request, context, abortController.signal.aborted);
662
+ await this.handleEvent(event, request, context, abortController.signal);
605
663
  }
606
664
  const events = preparedEvents ??
607
665
  (await thread.runStreamed(createCodexInput(request), {
@@ -610,12 +668,16 @@ export class CodexRuntimeAdapter {
610
668
  while (true) {
611
669
  const next = await events.next();
612
670
  if (next.done) {
671
+ if (context.lifecycle.parentTurnStopped) {
672
+ return;
673
+ }
613
674
  await this.waitForSpawnedCodexAgentsIfNeeded(context, abortController.signal);
675
+ await this.emitPendingCompleteIfReady(context);
614
676
  return;
615
677
  }
616
678
  await this.refreshSessionBindingIfNeeded(context);
617
679
  persistSyntheticEventIfNeeded(context.rawStoreRef, context.providerSessionId, next.value);
618
- await this.handleEvent(next.value, request, context, abortController.signal.aborted);
680
+ await this.handleEvent(next.value, request, context, abortController.signal);
619
681
  }
620
682
  }
621
683
  catch (error) {
@@ -643,31 +705,76 @@ export class CodexRuntimeAdapter {
643
705
  });
644
706
  }
645
707
  }
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
+ }
646
723
  async waitForSpawnedCodexAgentsIfNeeded(context, signal) {
647
724
  if (!shouldKeepCodexTransportAliveAfterTurn(context.lifecycle, context.rawStoreRef)) {
648
725
  return;
649
726
  }
650
- const agentIds = extractCodexSpawnedAgentIdsFromRawStore(context.rawStoreRef);
727
+ const agentIds = Array.from(new Set([
728
+ ...context.lifecycle.spawnedAgentIds,
729
+ ...extractCodexSpawnedAgentIdsFromRawStore(context.rawStoreRef)
730
+ ]));
651
731
  if (agentIds.length === 0) {
652
732
  return;
653
733
  }
654
734
  const deadline = Date.now() + CODEX_APP_SERVER_SPAWN_AGENT_GRACE_MS;
655
735
  const remainingAgentIds = new Set(agentIds);
656
- while (remainingAgentIds.size > 0 && Date.now() < deadline && !signal.aborted) {
736
+ while (remainingAgentIds.size > 0
737
+ && Date.now() < deadline
738
+ && !signal.aborted
739
+ && !context.lifecycle.parentTurnStopped) {
657
740
  for (const agentId of [...remainingAgentIds]) {
658
741
  const rawStoreRef = this.findRawStoreRefOnce(agentId, context.workspacePath, context.homeDir);
659
742
  if (rawStoreRef && isCodexRawStoreTerminal(rawStoreRef)) {
743
+ await this.closeSpawnedCodexAgentIfNeeded(context, agentId);
660
744
  remainingAgentIds.delete(agentId);
661
745
  }
662
746
  }
663
- if (remainingAgentIds.size === 0 || Date.now() >= deadline || signal.aborted) {
747
+ if (remainingAgentIds.size === 0
748
+ || Date.now() >= deadline
749
+ || signal.aborted
750
+ || context.lifecycle.parentTurnStopped) {
664
751
  break;
665
752
  }
666
753
  await sleep(CODEX_SPAWN_AGENT_POLL_INTERVAL_MS);
667
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
+ }
668
774
  }
669
- async handleEvent(event, request, context, interrupted) {
775
+ async handleEvent(event, request, context, signal) {
670
776
  const eventType = ensureText(readProp(event, "type")).trim();
777
+ const interrupted = signal.aborted;
671
778
  if (eventType.length === 0) {
672
779
  return;
673
780
  }
@@ -682,17 +789,14 @@ export class CodexRuntimeAdapter {
682
789
  });
683
790
  }
684
791
  if (eventType === "turn.completed") {
685
- await context.sink.emit({
686
- type: "complete",
687
- status: "completed",
688
- providerSessionId: context.providerSessionId,
689
- rawStoreRef: context.rawStoreRef,
792
+ context.lifecycle.pendingComplete = {
690
793
  detail: "codex turn completed",
691
794
  timestamp: pickTimestamp(event)
692
- });
795
+ };
693
796
  return;
694
797
  }
695
798
  if (eventType === "turn.failed") {
799
+ context.lifecycle.parentTurnStopped = true;
696
800
  const detail = extractTextBlocks(readProp(event, "error")).trim() || "codex turn failed";
697
801
  await context.sink.emit({
698
802
  type: "error",
@@ -706,6 +810,7 @@ export class CodexRuntimeAdapter {
706
810
  return;
707
811
  }
708
812
  if (eventType === "turn.interrupted") {
813
+ context.lifecycle.parentTurnStopped = true;
709
814
  await context.sink.emit({
710
815
  type: "interrupted",
711
816
  status: "interrupted",
@@ -1314,6 +1419,18 @@ function createCodexAppServerTransport(options) {
1314
1419
  }
1315
1420
  });
1316
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
+ },
1317
1434
  setNotificationHandler(handler) {
1318
1435
  notificationHandler = handler;
1319
1436
  },