evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -25,13 +25,15 @@ import { sanitizeSessionTitle } from '../core/session/session-title.js';
25
25
  import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
26
26
  import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
27
27
  import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
28
- import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, recordSandboxInitializationFailure, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
28
+ import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, isSandboxInitializationToolResult, recordSandboxInitializationFailure, getSandboxInitializationFailureCounters, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldEnableClaudeWeakerNestedSandbox, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
29
29
  import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
30
30
  import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
31
31
  import { getManagedTaskTempDir, isManagedSessionRuntimeDir } from '../cli/task-context.js';
32
- import { hasTrustedFullAccessContext } from './runner-types.js';
33
- import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
32
+ import { unsettledInterruptError, withInterruptDeadline, hasTrustedFullAccessContext } from './runner-types.js';
33
+ import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel, sdkAutoCompactWindowForModel } from './runner-types.js';
34
+ import { hasContextWindowSuffix, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
34
35
  const resolveModule = createRequire(import.meta.url);
36
+ const CLAUDE_INTERRUPT_TIMEOUT_MS = 5_000;
35
37
  /**
36
38
  * Resolve the platform package explicitly instead of relying on the SDK's
37
39
  * internal optional-dependency lookup. The latter is sensitive to the module
@@ -281,16 +283,17 @@ function buildClaudeManagedLockdownSettings(capabilityOptions) {
281
283
  }
282
284
  const CLAUDE_SANDBOX_GLOB_MAGIC = /[*?[\]]/;
283
285
  function trustedRuntimeWritePaths(runtimeEnv) {
284
- const tmp = process.env.TMPDIR?.trim();
285
- if (!tmp || !path.isAbsolute(tmp))
286
+ const sessionDir = runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR?.trim();
287
+ if (!sessionDir || !isManagedSessionRuntimeDir(sessionDir))
286
288
  return [];
287
- const root = resolveProtectedCandidate(tmp);
289
+ const root = resolveProtectedCandidate(sessionDir);
288
290
  const candidates = [runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR, runtimeEnv?.EVOLCORE_RUNTIME_LOCK_DIR]
289
291
  .filter((value) => typeof value === 'string' && path.isAbsolute(value));
290
292
  const paths = [];
291
293
  for (const candidate of candidates) {
292
294
  const resolved = resolveProtectedCandidate(candidate);
293
- if (!isSameOrDescendant(resolved, root))
295
+ if (!isSameOrDescendant(resolved, root)
296
+ || (resolved !== root && !(path.dirname(resolved) === root && path.basename(resolved) === 'locks')))
294
297
  continue;
295
298
  try {
296
299
  const stat = fs.lstatSync(resolved);
@@ -409,11 +412,18 @@ async function refreshGatewayPricing(baseUrl, apiKey, requestHeaders) {
409
412
  }
410
413
  /**
411
414
  * 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
412
- * 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
415
+ *
416
+ * 若网关把窗口变体作为独立条目下发(目录里同时有 `claude-opus-5` 与
417
+ * `claude-opus-5[1m]`),裸 ID 就是用户明确选的非 1M 变体,不能擅自升级,
418
+ * 否则两条列表项的实际效果无法区分。只有目录未提供任何窗口变体时,才沿用
419
+ * 按本地名单补 `[1m]` 的旧行为。
413
420
  */
414
- function applyContextWindow(modelId) {
415
- if (/\[1m\]$/.test(modelId))
416
- return modelId; // 已带后缀
421
+ function applyContextWindow(modelId, catalog) {
422
+ if (hasContextWindowSuffix(modelId))
423
+ return modelId; // 窗口已显式指定
424
+ const variantListed = catalog?.models.some(m => hasContextWindowSuffix(m.id) && stripContextWindowSuffix(m.id) === modelId);
425
+ if (variantListed)
426
+ return modelId;
417
427
  if (isOneMillionContextModel(modelId))
418
428
  return `${modelId}[1m]`;
419
429
  return modelId;
@@ -472,7 +482,7 @@ class MessageStream {
472
482
  }
473
483
  export class AgentRunner {
474
484
  name = 'claude';
475
- capabilities = { fullaccess: true, clear: true, compact: true, fork: true, forkAtTurn: true, askUserQuestion: true, planApproval: true, fileRewind: 'checkpoint' };
485
+ capabilities = { taskRecovery: true, fullaccess: true, clear: true, compact: true, fork: true, forkAtTurn: true, askUserQuestion: true, planApproval: true, fileRewind: 'checkpoint' };
476
486
  apiKey;
477
487
  model;
478
488
  effort;
@@ -491,6 +501,7 @@ export class AgentRunner {
491
501
  sandboxSettingsFiles = new Map();
492
502
  streamDone = new Map();
493
503
  streamDoneResolvers = new Map();
504
+ unconfirmedLifecycleFences = new Map();
494
505
  onSessionIdUpdate;
495
506
  onCompactStart;
496
507
  permissionGateway;
@@ -500,9 +511,9 @@ export class AgentRunner {
500
511
  /** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
501
512
  recentStderr = new Map();
502
513
  static STDERR_BUFFER_MAX = 80;
503
- async recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, error, stderr = []) {
514
+ async recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, error, stderr = [], scope = 'process') {
504
515
  const detail = createSandboxInitializationError(error, stderr);
505
- const circuit = recordSandboxInitializationFailure(detail, stderr);
516
+ const circuit = recordSandboxInitializationFailure(detail, stderr, Date.now(), scope === 'session' ? sessionId : undefined);
506
517
  const context = this.permissionContexts.get(sessionId);
507
518
  auditToolInfrastructureFailure({
508
519
  toolName: 'Bash',
@@ -516,6 +527,11 @@ export class AgentRunner {
516
527
  callId: inputId,
517
528
  taskId: context?.taskId,
518
529
  occurrences: circuit.occurrences,
530
+ sandboxFailure: {
531
+ kind: circuit.kind,
532
+ observation: 'initialization_failed',
533
+ counters: getSandboxInitializationFailureCounters(),
534
+ },
519
535
  });
520
536
  await context?.recordExecutionAnomaly?.({
521
537
  code: 'capability_unavailable',
@@ -1329,7 +1345,7 @@ export class AgentRunner {
1329
1345
  * SDK 原始事件 → 标准 AgentEvent 转换
1330
1346
  * 所有 SDK 特有的事件类型引用封装在此方法内
1331
1347
  */
1332
- async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false) {
1348
+ async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false, recoverySessionId) {
1333
1349
  let lastSessionId;
1334
1350
  let pendingSessionId;
1335
1351
  let hasTurnActivity = false;
@@ -1343,6 +1359,7 @@ export class AgentRunner {
1343
1359
  const taskMetadata = new Map();
1344
1360
  // tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
1345
1361
  const toolUseNames = new Map();
1362
+ const sandboxedBashCalls = new Set();
1346
1363
  const emittedToolResultIds = new Set();
1347
1364
  let turnCount = 0;
1348
1365
  const seenMessageIds = new Set();
@@ -1355,6 +1372,9 @@ export class AgentRunner {
1355
1372
  const candidate = pendingSessionId;
1356
1373
  if (!candidate)
1357
1374
  return undefined;
1375
+ if (recoverySessionId && candidate !== recoverySessionId) {
1376
+ throw new Error('Task recovery cannot replace the original Claude session');
1377
+ }
1358
1378
  if (!isResuming && topicBinding) {
1359
1379
  const ready = waitForFile
1360
1380
  ? await waitForClaudeSessionFile(projectPath, candidate)
@@ -1481,6 +1501,12 @@ export class AgentRunner {
1481
1501
  if (event.type === 'stream_event') {
1482
1502
  const streamEvent = event.event;
1483
1503
  if (streamEvent?.type === 'content_block_start'
1504
+ && streamEvent.content_block?.type === 'text') {
1505
+ hasTurnActivity = true;
1506
+ const contentIndex = typeof streamEvent.index === 'number' ? streamEvent.index : 0;
1507
+ yield { type: 'text_start', contentIndex, turn: turnCount || undefined };
1508
+ }
1509
+ else if (streamEvent?.type === 'content_block_start'
1484
1510
  && (streamEvent.content_block?.type === 'thinking' || streamEvent.content_block?.type === 'redacted_thinking')) {
1485
1511
  hasTurnActivity = true;
1486
1512
  yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
@@ -1617,6 +1643,10 @@ export class AgentRunner {
1617
1643
  if (content.type === 'tool_use') {
1618
1644
  if (content.id)
1619
1645
  toolUseNames.set(content.id, content.name);
1646
+ if (content.id && content.name === 'Bash' && content.input?.dangerouslyDisableSandbox !== true
1647
+ && resolvePhaseOneExecutionSandbox(permissionMode, this.permissionContexts.get(sessionId)?.role).state !== 'off') {
1648
+ sandboxedBashCalls.add(content.id);
1649
+ }
1620
1650
  yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars, assistantUuid: event.uuid };
1621
1651
  }
1622
1652
  else if (content.type === 'text' && content.text) {
@@ -1635,6 +1665,31 @@ export class AgentRunner {
1635
1665
  : block.content != null ? JSON.stringify(block.content) : '';
1636
1666
  if (block.tool_use_id)
1637
1667
  emittedToolResultIds.add(block.tool_use_id);
1668
+ if (sandboxedBashCalls.delete(block.tool_use_id) && block.is_error === true
1669
+ && isSandboxInitializationToolResult(block.content)) {
1670
+ const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, block.tool_use_id, permissionMode, new Error(resultContent), [], 'session');
1671
+ // Stop this SDK query before another model/tool retry. Do not use
1672
+ // interrupt(), which waits for this very generator to finish.
1673
+ this.activeMessageStreams.get(sessionId)?.end();
1674
+ const query = sdkStream;
1675
+ try {
1676
+ await query.close?.();
1677
+ }
1678
+ catch (error) {
1679
+ logger.warn(`[ClaudeSandbox] failed to close sandbox-failed query: ${error}`);
1680
+ }
1681
+ yield {
1682
+ type: 'tool_result', name: toolName, result: resultContent, isError: true,
1683
+ error: detail.message, errorCode: 'CAPABILITY_UNAVAILABLE', callId: block.tool_use_id,
1684
+ correlationId: block.tool_use_id, decision: 'error', decisionSource: 'infrastructure',
1685
+ policyCode: SANDBOX_INITIALIZATION_FAILED, executed: false, executionState: 'blocked',
1686
+ };
1687
+ yield {
1688
+ type: 'complete', isError: true, subtype: 'infrastructure',
1689
+ terminalReason: SANDBOX_INITIALIZATION_FAILED, errors: [detail.message], queryFinal: true,
1690
+ };
1691
+ return;
1692
+ }
1638
1693
  yield {
1639
1694
  type: 'tool_result',
1640
1695
  name: toolName,
@@ -1898,6 +1953,17 @@ export class AgentRunner {
1898
1953
  // Validate the daemon-injected session namespace before the SDK inherits
1899
1954
  // the process environment for any temporary state.
1900
1955
  getManagedTaskTempDir(runtimeEnv);
1956
+ this.assertLifecycleQuiescent(sessionId);
1957
+ const staleLifecycle = this.activeStreams.has(sessionId)
1958
+ || this.activeQueries.has(sessionId)
1959
+ || this.activeMessageStreams.has(sessionId)
1960
+ || this.interruptFns.has(sessionId);
1961
+ if (staleLifecycle) {
1962
+ logger.warn(`[AgentRunner] Stale query lifecycle before runQuery; forcing cleanup: session=${sessionId}`);
1963
+ await this.interrupt(sessionId).catch(error => {
1964
+ logger.warn(`[AgentRunner] Stale query interrupt failed before runQuery: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
1965
+ });
1966
+ }
1901
1967
  // 记录当前 evolcore session ID,用于 Agent ctl 环境变量注入
1902
1968
  // 同步用户级配置到内存
1903
1969
  this.syncFromUserSettings();
@@ -1928,6 +1994,10 @@ export class AgentRunner {
1928
1994
  if (topicBinding && topicAgentSessionId !== agentSessionId) {
1929
1995
  agentSessionId = topicAgentSessionId || undefined;
1930
1996
  }
1997
+ const recoverySessionId = modelOverride?.recovery?.agentSessionId;
1998
+ if (modelOverride?.recovery && (!recoverySessionId || agentSessionId !== recoverySessionId)) {
1999
+ throw new Error('Task recovery requires the original Claude session binding');
2000
+ }
1931
2001
  // 验证会话文件是否存在且有效(仅在有 agentSessionId 时)
1932
2002
  if (agentSessionId) {
1933
2003
  const homeDir = os.homedir();
@@ -1942,7 +2012,7 @@ export class AgentRunner {
1942
2012
  for (const line of lines) {
1943
2013
  try {
1944
2014
  const data = JSON.parse(line);
1945
- if (data.sessionId && data.version) {
2015
+ if (data.sessionId && data.version && (!recoverySessionId || data.sessionId === recoverySessionId)) {
1946
2016
  isValid = true;
1947
2017
  break;
1948
2018
  }
@@ -1958,6 +2028,9 @@ export class AgentRunner {
1958
2028
  }
1959
2029
  }
1960
2030
  if (!isValid) {
2031
+ if (recoverySessionId) {
2032
+ throw new Error(`Task recovery backend is unavailable: ${recoverySessionId}`);
2033
+ }
1961
2034
  if (topicBinding) {
1962
2035
  const error = new Error(`bound topic backend is unavailable: ${agentSessionId}`);
1963
2036
  error.code = 'TOPIC_BACKEND_UNAVAILABLE';
@@ -1992,7 +2065,7 @@ export class AgentRunner {
1992
2065
  const executionSandbox = resolvePhaseOneExecutionSandbox(callPermissionMode, runPermissionContext?.role);
1993
2066
  const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
1994
2067
  if (executionSandbox.state !== 'off') {
1995
- const activeSandboxFailure = getActiveSandboxInitializationFailure();
2068
+ const activeSandboxFailure = getActiveSandboxInitializationFailure(undefined, undefined, sessionId);
1996
2069
  if (activeSandboxFailure) {
1997
2070
  const detail = createSandboxInitializationError(new Error(`sandbox bootstrap circuit open; firstObservedAt=${activeSandboxFailure.openedAt} ` +
1998
2071
  `occurrences=${activeSandboxFailure.occurrences}; ${activeSandboxFailure.detail}`));
@@ -2009,6 +2082,11 @@ export class AgentRunner {
2009
2082
  callId: inputId,
2010
2083
  taskId: context?.taskId,
2011
2084
  occurrences: activeSandboxFailure.occurrences,
2085
+ sandboxFailure: {
2086
+ kind: activeSandboxFailure.kind,
2087
+ observation: 'circuit_open',
2088
+ counters: getSandboxInitializationFailureCounters(),
2089
+ },
2012
2090
  });
2013
2091
  await context?.recordExecutionAnomaly?.({
2014
2092
  code: 'capability_unavailable',
@@ -2469,7 +2547,7 @@ export class AgentRunner {
2469
2547
  else {
2470
2548
  logger.info(`[AgentRunner] systemPromptAppend: none`);
2471
2549
  }
2472
- const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel));
2550
+ const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel), runCatalog);
2473
2551
  const capabilityOptions = await this.resolveCapabilityRunOptions(projectPath);
2474
2552
  const managedSettings = callPermissionMode === 'fullaccess'
2475
2553
  ? undefined
@@ -2479,6 +2557,7 @@ export class AgentRunner {
2479
2557
  : await (async () => {
2480
2558
  const sandboxProjectionStartedAt = Date.now();
2481
2559
  ensureClaudeGitWorktreeConfig(projectPath);
2560
+ const enableWeakerNestedSandbox = shouldEnableClaudeWeakerNestedSandbox();
2482
2561
  const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root, {
2483
2562
  denyLClassRead: callPermissionMode === 'readonly',
2484
2563
  });
@@ -2499,10 +2578,12 @@ export class AgentRunner {
2499
2578
  throw new Error(`[ClaudeSandbox] protected filesystem projection exceeded 2000ms: ${sandboxProjectionDurationMs}ms`);
2500
2579
  }
2501
2580
  logger.info(`[ClaudeSandbox] projection ready denyRead=${sandboxFilesystem.denyRead.length} ` +
2502
- `denyWrite=${sandboxFilesystem.denyWrite.length} durationMs=${sandboxProjectionDurationMs}`);
2581
+ `denyWrite=${sandboxFilesystem.denyWrite.length} durationMs=${sandboxProjectionDurationMs} ` +
2582
+ `weakerNestedSandbox=${enableWeakerNestedSandbox}`);
2503
2583
  return {
2504
2584
  enabled: true,
2505
2585
  failIfUnavailable: shouldFailIfClaudeSandboxUnavailable(),
2586
+ enableWeakerNestedSandbox,
2506
2587
  allowUnsandboxedCommands: true,
2507
2588
  network: {
2508
2589
  allowUnixSockets: sandboxUnixSockets,
@@ -2552,7 +2633,7 @@ export class AgentRunner {
2552
2633
  ...(executionSandbox.state === 'off' && callPermissionMode !== 'fullaccess' && process.platform === 'linux' ? {
2553
2634
  spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr),
2554
2635
  } : {}),
2555
- autoCompactWindow: autoCompactWindowForModel(sdkModel),
2636
+ autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel),
2556
2637
  advisorModel: 'haiku',
2557
2638
  canUseTool: canUseToolCallback,
2558
2639
  permissionMode: sdkPermissionMode,
@@ -2702,7 +2783,7 @@ export class AgentRunner {
2702
2783
  // Topic bindings are authoritative for every input shape. Starting
2703
2784
  // an unresumed Claude query for a bound topic would discover a second
2704
2785
  // backend and then fail activation with a binding conflict.
2705
- sdkStream = topicBinding
2786
+ sdkStream = topicBinding || recoverySessionId
2706
2787
  ? createQuery(msgStream, agentSessionId, resumeAt)
2707
2788
  : createQuery(msgStream);
2708
2789
  }
@@ -2747,7 +2828,7 @@ export class AgentRunner {
2747
2828
  this.interruptFns.set(sessionId, () => sdkStream.interrupt());
2748
2829
  }
2749
2830
  // 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
2750
- const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding);
2831
+ const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding, recoverySessionId);
2751
2832
  const self = this;
2752
2833
  return (async function* () {
2753
2834
  try {
@@ -2770,43 +2851,92 @@ export class AgentRunner {
2770
2851
  async interrupt(sessionId) {
2771
2852
  this.permissionContexts.get(sessionId)?.pauseController?.cancel();
2772
2853
  const streamRegistered = this.activeStreams.has(sessionId);
2854
+ const registeredStream = this.activeStreams.get(sessionId);
2773
2855
  const fn = this.interruptFns.get(sessionId);
2774
2856
  const query = this.activeQueries.get(sessionId);
2857
+ const messageStream = this.activeMessageStreams.get(sessionId);
2775
2858
  const cancelledInputIds = [];
2776
2859
  let stillQueued;
2860
+ let interruptConfirmed = false;
2861
+ let queuedCancellationComplete = true;
2777
2862
  if (fn) {
2863
+ let interruptTimer;
2778
2864
  try {
2779
- const receipt = await fn();
2865
+ const receipt = await Promise.race([
2866
+ fn(),
2867
+ new Promise((_, reject) => {
2868
+ interruptTimer = setTimeout(() => reject(new Error('CLAUDE_INTERRUPT_TIMEOUT')), CLAUDE_INTERRUPT_TIMEOUT_MS);
2869
+ interruptTimer.unref?.();
2870
+ }),
2871
+ ]);
2872
+ interruptConfirmed = true;
2780
2873
  stillQueued = Array.isArray(receipt?.still_queued) ? receipt.still_queued.filter((id) => typeof id === 'string') : undefined;
2781
2874
  if (stillQueued && query && typeof query.cancelAsyncMessage === 'function') {
2782
- for (const inputId of stillQueued) {
2875
+ const cancellations = await Promise.all(stillQueued.map(async (inputId) => {
2783
2876
  try {
2784
- if (await query.cancelAsyncMessage(inputId))
2877
+ const cancelled = await withInterruptDeadline(query.cancelAsyncMessage(inputId), 'Claude cancel queued input', CLAUDE_INTERRUPT_TIMEOUT_MS);
2878
+ if (cancelled)
2785
2879
  cancelledInputIds.push(inputId);
2880
+ return cancelled;
2786
2881
  }
2787
2882
  catch (error) {
2788
2883
  logger.debug(`[AgentRunner] Failed to cancel queued input ${inputId}: ${error}`);
2884
+ return false;
2789
2885
  }
2790
- }
2886
+ }));
2887
+ queuedCancellationComplete = cancellations.every(Boolean);
2791
2888
  }
2792
2889
  logger.info(`[AgentRunner] Interrupted session: ${sessionId}`);
2793
2890
  }
2794
2891
  catch (error) {
2795
- logger.warn(`[AgentRunner] Interrupt failed (transport closed): ${sessionId}`);
2892
+ logger.warn(`[AgentRunner] Interrupt failed: ${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
2893
+ }
2894
+ finally {
2895
+ if (interruptTimer)
2896
+ clearTimeout(interruptTimer);
2796
2897
  }
2797
2898
  }
2798
2899
  this.activeMessageStreams.get(sessionId)?.end();
2799
2900
  if (query && typeof query.close === 'function')
2800
2901
  query.close();
2801
2902
  const done = this.streamDone.get(sessionId);
2903
+ let streamDoneSettled = false;
2802
2904
  // A stream interrupted after runQuery resolves but before registerStream has
2803
2905
  // no consumer that can enter the wrapper generator's finally block. Waiting
2804
2906
  // for streamDone here would always burn the full timeout.
2805
2907
  if (done && streamRegistered) {
2806
- await Promise.race([done, new Promise(resolve => setTimeout(resolve, 5_000))]);
2908
+ await Promise.race([
2909
+ done.then(() => { streamDoneSettled = queuedCancellationComplete; }),
2910
+ new Promise(resolve => setTimeout(resolve, CLAUDE_INTERRUPT_TIMEOUT_MS)),
2911
+ ]);
2912
+ }
2913
+ else if (done) {
2914
+ // A runQuery result can exist before ResponseEngine registers a consumer.
2915
+ // In that state there is no wrapper-finally callback to observe. Trust an
2916
+ // acknowledged SDK interrupt; otherwise keep the session fenced.
2917
+ streamDoneSettled = (!query || interruptConfirmed) && queuedCancellationComplete;
2918
+ }
2919
+ // Local queue cleanup cannot authorize a second SDK writer while the old
2920
+ // query remains unconfirmed.
2921
+ // Do not let a late interrupt from an older turn delete a lifecycle that
2922
+ // a newer turn installed while the old SDK call was unwinding.
2923
+ if (this.activeStreams.get(sessionId) === registeredStream
2924
+ && this.interruptFns.get(sessionId) === fn
2925
+ && this.activeQueries.get(sessionId) === query
2926
+ && this.activeMessageStreams.get(sessionId) === messageStream) {
2927
+ if ((done && !streamDoneSettled) || !queuedCancellationComplete) {
2928
+ const fence = done ?? new Promise(() => { });
2929
+ this.unconfirmedLifecycleFences.set(sessionId, fence);
2930
+ if (queuedCancellationComplete) {
2931
+ void fence.then(() => {
2932
+ if (this.unconfirmedLifecycleFences.get(sessionId) === fence) {
2933
+ this.unconfirmedLifecycleFences.delete(sessionId);
2934
+ }
2935
+ });
2936
+ }
2937
+ }
2938
+ this.cleanupStreamLifecycle(sessionId, false, streamDoneSettled);
2807
2939
  }
2808
- this.interruptFns.delete(sessionId);
2809
- this.activeStreams.delete(sessionId);
2810
2940
  this.cleanupSandboxSettingsFiles(sessionId);
2811
2941
  return { stillQueued, cancelledInputIds, closed: true };
2812
2942
  }
@@ -2817,9 +2947,18 @@ export class AgentRunner {
2817
2947
  this.activeStreams.set(key, stream);
2818
2948
  }
2819
2949
  cleanupStream(sessionId) {
2820
- this.activeMessageStreams.get(sessionId)?.end();
2950
+ this.cleanupStreamLifecycle(sessionId, true, true);
2951
+ }
2952
+ assertLifecycleQuiescent(sessionId) {
2953
+ if (this.unconfirmedLifecycleFences.has(sessionId))
2954
+ throw unsettledInterruptError('Claude');
2955
+ }
2956
+ cleanupStreamLifecycle(sessionId, endMessageStream, resolveDone) {
2957
+ if (endMessageStream)
2958
+ this.activeMessageStreams.get(sessionId)?.end();
2821
2959
  this.activeMessageStreams.delete(sessionId);
2822
- this.streamDoneResolvers.get(sessionId)?.();
2960
+ if (resolveDone)
2961
+ this.streamDoneResolvers.get(sessionId)?.();
2823
2962
  this.streamDoneResolvers.delete(sessionId);
2824
2963
  this.streamDone.delete(sessionId);
2825
2964
  this.activeStreams.delete(sessionId);
@@ -2891,7 +3030,7 @@ export class AgentRunner {
2891
3030
  prompt,
2892
3031
  options: {
2893
3032
  cwd: projectPath,
2894
- ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel)) } : {}),
3033
+ ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel), catalog) } : {}),
2895
3034
  resume: agentSessionId,
2896
3035
  maxTurns: 1,
2897
3036
  tools: [],
@@ -210,8 +210,8 @@ export class CodexAppServerClient {
210
210
  }
211
211
  return undefined;
212
212
  }
213
- async threadRead(threadId, includeTurns = true) {
214
- return this.request('thread/read', { threadId, includeTurns });
213
+ async threadRead(threadId, includeTurns = true, timeoutMs) {
214
+ return this.request('thread/read', { threadId, includeTurns }, timeoutMs);
215
215
  }
216
216
  async threadRollback(threadId, numTurns) {
217
217
  return this.request('thread/rollback', { threadId, numTurns });
@@ -232,7 +232,7 @@ export class CodexAppServerClient {
232
232
  : { sandbox: options?.sandbox ?? null }),
233
233
  config: this.buildThreadConfig(effort, options?.config),
234
234
  excludeTurns: false,
235
- persistExtendedHistory: false,
235
+ ...(options?.lastTurnId ? { lastTurnId: options.lastTurnId } : {}),
236
236
  });
237
237
  const forkedThreadId = response.thread?.id;
238
238
  if (title && forkedThreadId) {
@@ -252,16 +252,19 @@ export class CodexAppServerClient {
252
252
  await this.request('thread/compact/start', { threadId });
253
253
  return true;
254
254
  }
255
- async threadTurnsList(threadId, limit) {
255
+ async threadTurnsList(threadId, limit, cursor) {
256
256
  return this.request('thread/turns/list', {
257
257
  threadId,
258
258
  ...(limit ? { limit } : {}),
259
- sortDirection: 'asc',
259
+ ...(cursor ? { cursor } : {}),
260
+ // The app-server defaults to newest-first. `nextCursor` then walks
261
+ // toward older turns, which is the direction needed to hydrate history.
262
+ sortDirection: 'desc',
260
263
  itemsView: 'full',
261
264
  });
262
265
  }
263
266
  async threadTurnsItemsList(threadId, turnId, limit) {
264
- return this.request('thread/turns/items/list', {
267
+ return this.request('thread/items/list', {
265
268
  threadId,
266
269
  turnId,
267
270
  ...(limit ? { limit } : {}),
@@ -619,7 +622,7 @@ export class CodexAppServerClient {
619
622
  env.TMPDIR = managedTmpDir;
620
623
  delete env.EVOLCORE_RUNTIME_LOCK_DIR;
621
624
  if (managedTmpDir) {
622
- const runtimeLockDir = path.join(managedTmpDir, 'evolcore-locks');
625
+ const runtimeLockDir = path.join(managedTmpDir, 'locks');
623
626
  try {
624
627
  // The parent is the already-validated process root, so only create
625
628
  // and validate the final component. Never follow a pre-existing
@@ -643,7 +646,12 @@ export class CodexAppServerClient {
643
646
  logger.warn(`[CodexAppServer] managed runtime lock directory unavailable: ${runtimeLockDir}`);
644
647
  }
645
648
  }
646
- env.CODEX_API_KEY = this.options.apiKey;
649
+ if (this.options.authType === 'bearerToken') {
650
+ delete env.CODEX_API_KEY;
651
+ }
652
+ else {
653
+ env.CODEX_API_KEY = this.options.apiKey;
654
+ }
647
655
  env.EVOLCORE_INSTANCE_SOCKET = resolvePaths().instanceSocket;
648
656
  for (const [name, value] of Object.entries(this.options.headers ?? {})) {
649
657
  env[codexHeaderEnvName(name)] = value;
@@ -760,30 +768,63 @@ export class CodexAppServerClient {
760
768
  }
761
769
  return args;
762
770
  }
763
- async request(method, params) {
764
- await this.ensureReadyFor(method);
765
- const proc = this.proc;
766
- if (!proc)
767
- throw new Error('Codex app-server is not running');
768
- const id = String(this.nextId++);
769
- const payload = { id, method };
770
- if (params !== undefined && params !== null)
771
- payload.params = params;
772
- return new Promise((resolve, reject) => {
773
- this.pending.set(id, {
774
- resolve,
775
- reject,
776
- method,
777
- params,
778
- stderrStartSequence: this.stderrSequence,
779
- });
780
- proc.stdin.write(`${JSON.stringify(payload)}\n`, error => {
781
- if (!error)
782
- return;
783
- this.pending.delete(id);
784
- reject(error);
771
+ async request(method, params, timeoutMs) {
772
+ // Only lifecycle RPCs get short deadlines. Model work has its own policy.
773
+ const deadline = timeoutMs ?? (method === 'turn/interrupt' ? 5_000 : undefined);
774
+ let timer;
775
+ let requestId;
776
+ let expired = false;
777
+ const operation = (async () => {
778
+ await this.ensureReadyFor(method);
779
+ if (expired)
780
+ throw new Error(`Codex ${method} expired before dispatch`);
781
+ const proc = this.proc;
782
+ if (!proc)
783
+ throw new Error('Codex app-server is not running');
784
+ const id = String(this.nextId++);
785
+ requestId = id;
786
+ const payload = { id, method };
787
+ if (params !== undefined && params !== null)
788
+ payload.params = params;
789
+ return new Promise((resolve, reject) => {
790
+ this.pending.set(id, {
791
+ resolve,
792
+ reject,
793
+ method,
794
+ params,
795
+ stderrStartSequence: this.stderrSequence,
796
+ });
797
+ proc.stdin.write(`${JSON.stringify(payload)}\n`, error => {
798
+ if (!error)
799
+ return;
800
+ this.pending.delete(id);
801
+ reject(error);
802
+ });
785
803
  });
786
- });
804
+ })();
805
+ if (deadline === undefined)
806
+ return operation;
807
+ try {
808
+ return await Promise.race([
809
+ operation,
810
+ new Promise((_, reject) => {
811
+ timer = setTimeout(() => {
812
+ expired = true;
813
+ reject(new Error(`Codex ${method} deadline exceeded`));
814
+ }, deadline);
815
+ timer.unref?.();
816
+ }),
817
+ ]);
818
+ }
819
+ finally {
820
+ if (timer)
821
+ clearTimeout(timer);
822
+ if (requestId && expired) {
823
+ const pending = this.pending.get(requestId);
824
+ this.pending.delete(requestId);
825
+ pending?.reject(new Error(`Codex ${method} deadline exceeded`));
826
+ }
827
+ }
787
828
  }
788
829
  respond(id, result) {
789
830
  if (!this.proc)
@@ -862,6 +903,10 @@ export class CodexAppServerClient {
862
903
  else if (/already has an active writer/i.test(detail)) {
863
904
  error.code = CODEX_THREAD_ACTIVE_WRITER;
864
905
  }
906
+ else if (pending.method === 'turn/steer' && /\bno active turn\b/i.test(detail)) {
907
+ // Explicit server rejection proves the input was not accepted. Transport errors do not.
908
+ error.code = 'CODEX_NO_ACTIVE_TURN';
909
+ }
865
910
  pending.reject(error);
866
911
  return;
867
912
  }
@@ -28,7 +28,7 @@ export async function codexNativeCatalog(context) {
28
28
  if (context.baseagent !== 'codex')
29
29
  return null;
30
30
  const client = new CodexAppServerClient({
31
- apiKey: context.apiKey ?? '', baseUrl: context.baseUrl, provider: context.provider,
31
+ apiKey: context.apiKey ?? '', authType: context.authType, baseUrl: context.baseUrl, provider: context.provider,
32
32
  headers: context.headers, queryParams: context.queryParams,
33
33
  });
34
34
  let timer;