funolio-agent 1.0.53 → 1.1.65

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 (276) hide show
  1. package/dist/approval.d.ts +1 -6
  2. package/dist/approval.d.ts.map +1 -1
  3. package/dist/approval.js +2 -7
  4. package/dist/approval.js.map +1 -1
  5. package/dist/auth/credential-reader.d.ts.map +1 -1
  6. package/dist/auth/credential-reader.js +4 -3
  7. package/dist/auth/credential-reader.js.map +1 -1
  8. package/dist/auth/token-refresh.d.ts +8 -0
  9. package/dist/auth/token-refresh.d.ts.map +1 -1
  10. package/dist/auth/token-refresh.js +82 -52
  11. package/dist/auth/token-refresh.js.map +1 -1
  12. package/dist/auto-organizer.d.ts.map +1 -1
  13. package/dist/auto-organizer.js +6 -7
  14. package/dist/auto-organizer.js.map +1 -1
  15. package/dist/bench-prefix.d.ts +16 -0
  16. package/dist/bench-prefix.d.ts.map +1 -0
  17. package/dist/bench-prefix.js +25 -0
  18. package/dist/bench-prefix.js.map +1 -0
  19. package/dist/bot-manager.d.ts +5 -1
  20. package/dist/bot-manager.d.ts.map +1 -1
  21. package/dist/bot-manager.js +46 -27
  22. package/dist/bot-manager.js.map +1 -1
  23. package/dist/chat-sync.d.ts +42 -0
  24. package/dist/chat-sync.d.ts.map +1 -0
  25. package/dist/chat-sync.js +95 -0
  26. package/dist/chat-sync.js.map +1 -0
  27. package/dist/clerk-model.d.ts +7 -0
  28. package/dist/clerk-model.d.ts.map +1 -1
  29. package/dist/clerk-model.js +42 -8
  30. package/dist/clerk-model.js.map +1 -1
  31. package/dist/cli-bootstrap-history.d.ts +10 -0
  32. package/dist/cli-bootstrap-history.d.ts.map +1 -0
  33. package/dist/cli-bootstrap-history.js +112 -0
  34. package/dist/cli-bootstrap-history.js.map +1 -0
  35. package/dist/cli-models.d.ts +8 -0
  36. package/dist/cli-models.d.ts.map +1 -0
  37. package/dist/cli-models.js +91 -0
  38. package/dist/cli-models.js.map +1 -0
  39. package/dist/cli-session-epoch.d.ts +13 -3
  40. package/dist/cli-session-epoch.d.ts.map +1 -1
  41. package/dist/cli-session-epoch.js +53 -4
  42. package/dist/cli-session-epoch.js.map +1 -1
  43. package/dist/cli-session-registry.d.ts +35 -0
  44. package/dist/cli-session-registry.d.ts.map +1 -0
  45. package/dist/cli-session-registry.js +177 -0
  46. package/dist/cli-session-registry.js.map +1 -0
  47. package/dist/cli.js +62 -0
  48. package/dist/cli.js.map +1 -1
  49. package/dist/codex-app-server-manager.d.ts +189 -0
  50. package/dist/codex-app-server-manager.d.ts.map +1 -0
  51. package/dist/codex-app-server-manager.js +1468 -0
  52. package/dist/codex-app-server-manager.js.map +1 -0
  53. package/dist/commands/init.d.ts.map +1 -1
  54. package/dist/commands/init.js +8 -30
  55. package/dist/commands/init.js.map +1 -1
  56. package/dist/commands/pool.d.ts +32 -0
  57. package/dist/commands/pool.d.ts.map +1 -1
  58. package/dist/commands/pool.js +145 -66
  59. package/dist/commands/pool.js.map +1 -1
  60. package/dist/commands/setup.d.ts +4 -1
  61. package/dist/commands/setup.d.ts.map +1 -1
  62. package/dist/commands/setup.js +9 -25
  63. package/dist/commands/setup.js.map +1 -1
  64. package/dist/commands/start.d.ts +21 -0
  65. package/dist/commands/start.d.ts.map +1 -1
  66. package/dist/commands/start.js +559 -63
  67. package/dist/commands/start.js.map +1 -1
  68. package/dist/commands/status.d.ts.map +1 -1
  69. package/dist/commands/status.js +5 -2
  70. package/dist/commands/status.js.map +1 -1
  71. package/dist/completion-marker.d.ts +7 -0
  72. package/dist/completion-marker.d.ts.map +1 -0
  73. package/dist/completion-marker.js +28 -0
  74. package/dist/completion-marker.js.map +1 -0
  75. package/dist/config.d.ts +7 -2
  76. package/dist/config.d.ts.map +1 -1
  77. package/dist/config.js +184 -60
  78. package/dist/config.js.map +1 -1
  79. package/dist/context-window.d.ts +37 -1
  80. package/dist/context-window.d.ts.map +1 -1
  81. package/dist/context-window.js +210 -17
  82. package/dist/context-window.js.map +1 -1
  83. package/dist/live-activity.d.ts +31 -0
  84. package/dist/live-activity.d.ts.map +1 -0
  85. package/dist/live-activity.js +36 -0
  86. package/dist/live-activity.js.map +1 -0
  87. package/dist/local-chat-execution.d.ts +114 -0
  88. package/dist/local-chat-execution.d.ts.map +1 -0
  89. package/dist/local-chat-execution.js +349 -0
  90. package/dist/local-chat-execution.js.map +1 -0
  91. package/dist/local-cli-pty-manager.d.ts +186 -0
  92. package/dist/local-cli-pty-manager.d.ts.map +1 -1
  93. package/dist/local-cli-pty-manager.js +2581 -164
  94. package/dist/local-cli-pty-manager.js.map +1 -1
  95. package/dist/local-conversation-gateway.d.ts +110 -0
  96. package/dist/local-conversation-gateway.d.ts.map +1 -0
  97. package/dist/local-conversation-gateway.js +175 -0
  98. package/dist/local-conversation-gateway.js.map +1 -0
  99. package/dist/local-data.d.ts +276 -5
  100. package/dist/local-data.d.ts.map +1 -1
  101. package/dist/local-data.js +1201 -86
  102. package/dist/local-data.js.map +1 -1
  103. package/dist/local-db.d.ts +6 -0
  104. package/dist/local-db.d.ts.map +1 -1
  105. package/dist/local-db.js +428 -2
  106. package/dist/local-db.js.map +1 -1
  107. package/dist/local-funnel.d.ts.map +1 -1
  108. package/dist/local-funnel.js +6 -5
  109. package/dist/local-funnel.js.map +1 -1
  110. package/dist/local-server.d.ts +55 -0
  111. package/dist/local-server.d.ts.map +1 -1
  112. package/dist/local-server.js +3281 -441
  113. package/dist/local-server.js.map +1 -1
  114. package/dist/managed-process-registry.d.ts +59 -0
  115. package/dist/managed-process-registry.d.ts.map +1 -0
  116. package/dist/managed-process-registry.js +390 -0
  117. package/dist/managed-process-registry.js.map +1 -0
  118. package/dist/mcp/claude-config-writer.d.ts +5 -5
  119. package/dist/mcp/claude-config-writer.d.ts.map +1 -1
  120. package/dist/mcp/claude-config-writer.js +19 -11
  121. package/dist/mcp/claude-config-writer.js.map +1 -1
  122. package/dist/mcp/index.d.ts +4 -2
  123. package/dist/mcp/index.d.ts.map +1 -1
  124. package/dist/mcp/index.js.map +1 -1
  125. package/dist/mcp/sync-cli-config.d.ts +42 -4
  126. package/dist/mcp/sync-cli-config.d.ts.map +1 -1
  127. package/dist/mcp/sync-cli-config.js +497 -17
  128. package/dist/mcp/sync-cli-config.js.map +1 -1
  129. package/dist/message-loop.d.ts +6 -0
  130. package/dist/message-loop.d.ts.map +1 -1
  131. package/dist/message-loop.js +281 -89
  132. package/dist/message-loop.js.map +1 -1
  133. package/dist/mqtt-client.d.ts +44 -1
  134. package/dist/mqtt-client.d.ts.map +1 -1
  135. package/dist/mqtt-client.js +284 -46
  136. package/dist/mqtt-client.js.map +1 -1
  137. package/dist/mqtt-data-relay.d.ts +44 -0
  138. package/dist/mqtt-data-relay.d.ts.map +1 -0
  139. package/dist/mqtt-data-relay.js +106 -0
  140. package/dist/mqtt-data-relay.js.map +1 -0
  141. package/dist/oauth.d.ts.map +1 -1
  142. package/dist/oauth.js +69 -29
  143. package/dist/oauth.js.map +1 -1
  144. package/dist/orchestration/capabilities.d.ts +13 -0
  145. package/dist/orchestration/capabilities.d.ts.map +1 -0
  146. package/dist/orchestration/capabilities.js +152 -0
  147. package/dist/orchestration/capabilities.js.map +1 -0
  148. package/dist/orchestration/dispatch-executor.d.ts +83 -0
  149. package/dist/orchestration/dispatch-executor.d.ts.map +1 -0
  150. package/dist/orchestration/dispatch-executor.js +266 -0
  151. package/dist/orchestration/dispatch-executor.js.map +1 -0
  152. package/dist/orchestration/dispatch-hint.d.ts +134 -0
  153. package/dist/orchestration/dispatch-hint.d.ts.map +1 -0
  154. package/dist/orchestration/dispatch-hint.js +247 -0
  155. package/dist/orchestration/dispatch-hint.js.map +1 -0
  156. package/dist/orchestration/dispatch-runner.d.ts +106 -0
  157. package/dist/orchestration/dispatch-runner.d.ts.map +1 -0
  158. package/dist/orchestration/dispatch-runner.js +604 -0
  159. package/dist/orchestration/dispatch-runner.js.map +1 -0
  160. package/dist/orchestration/dispatch-tools.d.ts +167 -0
  161. package/dist/orchestration/dispatch-tools.d.ts.map +1 -0
  162. package/dist/orchestration/dispatch-tools.js +328 -0
  163. package/dist/orchestration/dispatch-tools.js.map +1 -0
  164. package/dist/orchestration/front-door-policy.d.ts +35 -10
  165. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  166. package/dist/orchestration/front-door-policy.js +30 -267
  167. package/dist/orchestration/front-door-policy.js.map +1 -1
  168. package/dist/orchestration/orchestrator-dispatch-prompt.d.ts +43 -0
  169. package/dist/orchestration/orchestrator-dispatch-prompt.d.ts.map +1 -0
  170. package/dist/orchestration/orchestrator-dispatch-prompt.js +267 -0
  171. package/dist/orchestration/orchestrator-dispatch-prompt.js.map +1 -0
  172. package/dist/orchestration/orchestrator-operating-prompt.d.ts +15 -0
  173. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  174. package/dist/orchestration/orchestrator-operating-prompt.js +206 -20
  175. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  176. package/dist/orchestration/plan-import.d.ts +39 -0
  177. package/dist/orchestration/plan-import.d.ts.map +1 -0
  178. package/dist/orchestration/plan-import.js +547 -0
  179. package/dist/orchestration/plan-import.js.map +1 -0
  180. package/dist/orchestration/validation.d.ts +40 -0
  181. package/dist/orchestration/validation.d.ts.map +1 -0
  182. package/dist/orchestration/validation.js +203 -0
  183. package/dist/orchestration/validation.js.map +1 -0
  184. package/dist/orchestration/worker-operating-prompt.d.ts +2 -0
  185. package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -1
  186. package/dist/orchestration/worker-operating-prompt.js +36 -46
  187. package/dist/orchestration/worker-operating-prompt.js.map +1 -1
  188. package/dist/orchestrator.d.ts +214 -33
  189. package/dist/orchestrator.d.ts.map +1 -1
  190. package/dist/orchestrator.js +2200 -1100
  191. package/dist/orchestrator.js.map +1 -1
  192. package/dist/providers/anthropic.d.ts.map +1 -1
  193. package/dist/providers/anthropic.js +8 -4
  194. package/dist/providers/anthropic.js.map +1 -1
  195. package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
  196. package/dist/providers/claude-cli-prompt.js +49 -5
  197. package/dist/providers/claude-cli-prompt.js.map +1 -1
  198. package/dist/providers/claude-cli.d.ts.map +1 -1
  199. package/dist/providers/claude-cli.js +81 -5
  200. package/dist/providers/claude-cli.js.map +1 -1
  201. package/dist/providers/codex-cli.d.ts +10 -6
  202. package/dist/providers/codex-cli.d.ts.map +1 -1
  203. package/dist/providers/codex-cli.js +204 -26
  204. package/dist/providers/codex-cli.js.map +1 -1
  205. package/dist/providers/google.d.ts.map +1 -1
  206. package/dist/providers/google.js +15 -5
  207. package/dist/providers/google.js.map +1 -1
  208. package/dist/providers/index.d.ts +15 -1
  209. package/dist/providers/index.d.ts.map +1 -1
  210. package/dist/providers/index.js.map +1 -1
  211. package/dist/providers/openai.d.ts +1 -1
  212. package/dist/providers/openai.d.ts.map +1 -1
  213. package/dist/providers/openai.js +13 -5
  214. package/dist/providers/openai.js.map +1 -1
  215. package/dist/response-guard.js +1 -1
  216. package/dist/response-guard.js.map +1 -1
  217. package/dist/server-adapter.d.ts +8 -0
  218. package/dist/server-adapter.d.ts.map +1 -1
  219. package/dist/server-adapter.js +7 -0
  220. package/dist/server-adapter.js.map +1 -1
  221. package/dist/service-mode.d.ts +1 -1
  222. package/dist/service-mode.d.ts.map +1 -1
  223. package/dist/service-mode.js +64 -1
  224. package/dist/service-mode.js.map +1 -1
  225. package/dist/service-setup-only.d.ts +8 -0
  226. package/dist/service-setup-only.d.ts.map +1 -0
  227. package/dist/service-setup-only.js +37 -0
  228. package/dist/service-setup-only.js.map +1 -0
  229. package/dist/slash-commands.d.ts +21 -0
  230. package/dist/slash-commands.d.ts.map +1 -0
  231. package/dist/slash-commands.js +99 -0
  232. package/dist/slash-commands.js.map +1 -0
  233. package/dist/subagent/index.d.ts +4 -2
  234. package/dist/subagent/index.d.ts.map +1 -1
  235. package/dist/subagent/index.js.map +1 -1
  236. package/dist/summarization-pipeline.d.ts.map +1 -1
  237. package/dist/summarization-pipeline.js +1 -9
  238. package/dist/summarization-pipeline.js.map +1 -1
  239. package/dist/token-counter.d.ts.map +1 -1
  240. package/dist/token-counter.js +11 -4
  241. package/dist/token-counter.js.map +1 -1
  242. package/dist/tool-filter.d.ts.map +1 -1
  243. package/dist/tool-filter.js +10 -6
  244. package/dist/tool-filter.js.map +1 -1
  245. package/dist/tools/admin-tools.d.ts.map +1 -1
  246. package/dist/tools/admin-tools.js +20 -5
  247. package/dist/tools/admin-tools.js.map +1 -1
  248. package/dist/tools/index.d.ts.map +1 -1
  249. package/dist/tools/index.js +2 -1
  250. package/dist/tools/index.js.map +1 -1
  251. package/dist/tools/run-command.d.ts.map +1 -1
  252. package/dist/tools/run-command.js +5 -1
  253. package/dist/tools/run-command.js.map +1 -1
  254. package/dist/tools/search-conversation-history.d.ts +16 -0
  255. package/dist/tools/search-conversation-history.d.ts.map +1 -0
  256. package/dist/tools/search-conversation-history.js +334 -0
  257. package/dist/tools/search-conversation-history.js.map +1 -0
  258. package/dist/tools/todo-tasks.d.ts.map +1 -1
  259. package/dist/tools/todo-tasks.js +77 -5
  260. package/dist/tools/todo-tasks.js.map +1 -1
  261. package/dist/usage-log.d.ts +62 -0
  262. package/dist/usage-log.d.ts.map +1 -0
  263. package/dist/usage-log.js +98 -0
  264. package/dist/usage-log.js.map +1 -0
  265. package/dist/wizard-state.d.ts +20 -0
  266. package/dist/wizard-state.d.ts.map +1 -1
  267. package/dist/wizard-state.js +90 -3
  268. package/dist/wizard-state.js.map +1 -1
  269. package/dist/wizard-support.d.ts.map +1 -1
  270. package/dist/wizard-support.js +27 -1
  271. package/dist/wizard-support.js.map +1 -1
  272. package/dist/workflow-engine.d.ts +44 -2
  273. package/dist/workflow-engine.d.ts.map +1 -1
  274. package/dist/workflow-engine.js +932 -111
  275. package/dist/workflow-engine.js.map +1 -1
  276. package/package.json +2 -2
@@ -0,0 +1,1468 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__codexAppServerTestUtils = exports.CodexAppServerManager = void 0;
4
+ exports.getCodexAppServerManager = getCodexAppServerManager;
5
+ exports.runCodexAppServerTurnForTest = runCodexAppServerTurnForTest;
6
+ const child_process_1 = require("child_process");
7
+ const crypto_1 = require("crypto");
8
+ const runtime_context_1 = require("./runtime-context");
9
+ const sync_cli_config_1 = require("./mcp/sync-cli-config");
10
+ const managed_process_registry_1 = require("./managed-process-registry");
11
+ const LOCAL_ONLY_ERROR = 'Codex app-server manager is local_desktop only';
12
+ const CODEX_APP_SERVER_WARM_TIMEOUT_MS = 10_000;
13
+ const DEFAULT_CODEX_SETTINGS = {
14
+ reasoningEffort: 'low',
15
+ reasoningSummary: 'detailed',
16
+ personality: 'friendly',
17
+ serviceTier: 'fast',
18
+ sandboxPolicy: 'danger-full-access',
19
+ approvalPolicy: 'never',
20
+ };
21
+ const VALID_CODEX_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
22
+ const VALID_CODEX_REASONING_SUMMARIES = new Set(['auto', 'concise', 'detailed', 'none']);
23
+ const VALID_CODEX_PERSONALITIES = new Set(['friendly', 'pragmatic']);
24
+ const VALID_CODEX_SERVICE_TIERS = new Set(['fast', 'flex']);
25
+ const VALID_CODEX_SANDBOX_POLICIES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
26
+ const VALID_CODEX_APPROVAL_POLICIES = new Set(['untrusted', 'on-failure', 'on-request', 'never']);
27
+ let _manager = null;
28
+ let _prepareCodexCliConfigChain = Promise.resolve();
29
+ function delay(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ function buildAbortError() {
33
+ const err = new Error('Codex app-server turn aborted');
34
+ err.name = 'AbortError';
35
+ return err;
36
+ }
37
+ function throwIfNotLocalDesktop(runtimeMode) {
38
+ if (runtimeMode && !(0, runtime_context_1.isLocalDesktopRuntime)(runtimeMode)) {
39
+ throw new Error(LOCAL_ONLY_ERROR);
40
+ }
41
+ }
42
+ function sessionKey(conversationId, botId) {
43
+ return `${conversationId}::${botId}`;
44
+ }
45
+ function hasProcessExited(child) {
46
+ const exitCode = child?.exitCode;
47
+ const signalCode = child?.signalCode;
48
+ return ((exitCode !== null && exitCode !== undefined)
49
+ || (signalCode !== null && signalCode !== undefined));
50
+ }
51
+ function collectProcessRestartReasons(session, cwd, requestedEnvFingerprint, expectedThreadId) {
52
+ const reasons = [];
53
+ if (session.cwd !== cwd)
54
+ reasons.push('cwd_mismatch');
55
+ if (session.closed || hasProcessExited(session.child))
56
+ reasons.push('process_unhealthy');
57
+ if (session.envFingerprint !== requestedEnvFingerprint)
58
+ reasons.push('env_mismatch');
59
+ if (expectedThreadId && session.threadId && session.threadId !== expectedThreadId) {
60
+ reasons.push('incompatible_thread_request');
61
+ }
62
+ return reasons;
63
+ }
64
+ function buildWarmTimeoutError(timeoutMs) {
65
+ const err = new Error(`codex app-server warm session timed out after ${Math.floor(timeoutMs / 1000)}s`);
66
+ err.code = 'CODEX_APP_SERVER_WARM_TIMEOUT';
67
+ err.name = 'CodexAppServerWarmTimeoutError';
68
+ return err;
69
+ }
70
+ function withWarmTimeout(promise, timeoutMs, onTimeout) {
71
+ let timeout = null;
72
+ const timeoutPromise = new Promise((_, reject) => {
73
+ timeout = setTimeout(() => {
74
+ try {
75
+ onTimeout();
76
+ }
77
+ finally {
78
+ reject(buildWarmTimeoutError(timeoutMs));
79
+ }
80
+ }, timeoutMs);
81
+ });
82
+ return Promise.race([promise, timeoutPromise]).finally(() => {
83
+ if (timeout)
84
+ clearTimeout(timeout);
85
+ });
86
+ }
87
+ function createEventLog() {
88
+ return {
89
+ schema: 'funolio.codex-app-server.event-log@1',
90
+ events: [],
91
+ };
92
+ }
93
+ function appendEvent(eventLog, direction, kind, payload) {
94
+ if (!eventLog)
95
+ return;
96
+ eventLog.events.push({
97
+ ts: new Date().toISOString(),
98
+ direction,
99
+ kind,
100
+ payload,
101
+ });
102
+ }
103
+ function serializeEventLog(eventLog) {
104
+ return JSON.stringify(eventLog);
105
+ }
106
+ function normalizeDetail(text) {
107
+ return String(text || '').replace(/\s+/g, ' ').trim();
108
+ }
109
+ function isImportantLiveDetail(text) {
110
+ return /\b(ERROR|Error|failed|failure|Exit code:\s*[1-9]|authentication|rate limit|timeout|timed out|denied|unauthorized)\b/i.test(text);
111
+ }
112
+ function isGeneratedOrDependencyNoise(text) {
113
+ return (/\.map(?::|\b)/i.test(text)
114
+ || /(?:^|[\\/])node_modules[\\/]/i.test(text)
115
+ || /[\\/]?\.cargo[\\/]registry[\\/]/i.test(text)
116
+ || /src-tauri[\\/]gen[\\/]schemas/i.test(text)
117
+ || /\bpackage-lock\.json\b/i.test(text)
118
+ || /\bCargo\.lock\b/i.test(text)
119
+ || /\bdist[\\/]assets[\\/][^ ]+\.(?:js|css)\b/i.test(text));
120
+ }
121
+ function looksLikeMinifiedBlob(text) {
122
+ if (text.length < 1000)
123
+ return false;
124
+ const whitespaceCount = (text.match(/\s/g) || []).length;
125
+ const punctuationCount = (text.match(/[{}()[\],;:=]/g) || []).length;
126
+ return whitespaceCount / text.length < 0.08 && punctuationCount > 80;
127
+ }
128
+ function looksLikeRawSourceLine(text) {
129
+ return /^\s*(const|let|function|import|export|type|interface|class|return|if |for |while |case |await |async |try |catch|}\)|}}|\]|\)|\{)\b/.test(text);
130
+ }
131
+ function looksLikeSearchOutput(text) {
132
+ return /^(?:src|packages|prisma|scripts|public|app|C:\\|\.github)[\\/].*:\d+/.test(text)
133
+ || /^\s*[\w.\-/\\]+:\d+/.test(text);
134
+ }
135
+ function truncateLiveDetail(text, maxLength) {
136
+ if (text.length <= maxLength)
137
+ return text;
138
+ return `${text.slice(0, maxLength)} ...[truncated raw output]`;
139
+ }
140
+ function prepareLiveDetailForUser(activeTurn, detail, source) {
141
+ const normalized = normalizeDetail(detail);
142
+ if (!normalized)
143
+ return null;
144
+ if (source === 'status') {
145
+ return truncateLiveDetail(normalized, 600);
146
+ }
147
+ if (isImportantLiveDetail(normalized)) {
148
+ return truncateLiveDetail(normalized, 900);
149
+ }
150
+ if (isGeneratedOrDependencyNoise(normalized) || looksLikeMinifiedBlob(normalized)) {
151
+ return null;
152
+ }
153
+ if (looksLikeSearchOutput(normalized)) {
154
+ const count = activeTurn.liveDetailCategoryCounts.get('search_output') || 0;
155
+ activeTurn.liveDetailCategoryCounts.set('search_output', count + 1);
156
+ if (count >= 25)
157
+ return null;
158
+ return truncateLiveDetail(normalized, 500);
159
+ }
160
+ if (normalized.length > 1000) {
161
+ return null;
162
+ }
163
+ if ((source === 'command_output' || source === 'file_output') && looksLikeRawSourceLine(normalized)) {
164
+ const count = activeTurn.liveDetailCategoryCounts.get('raw_source_line') || 0;
165
+ activeTurn.liveDetailCategoryCounts.set('raw_source_line', count + 1);
166
+ if (count >= 40)
167
+ return null;
168
+ }
169
+ return truncateLiveDetail(normalized, 600);
170
+ }
171
+ function scrubCodexAppServerEnv(baseEnv, extraEnv, sessionKey) {
172
+ const env = {};
173
+ for (const [key, value] of Object.entries(baseEnv)) {
174
+ if (value == null)
175
+ continue;
176
+ if (key === 'CODEX_THREAD_ID')
177
+ continue;
178
+ if (key === 'FUNOLIO_TOOL_TODO_ID')
179
+ continue;
180
+ env[key] = value;
181
+ }
182
+ for (const [key, value] of Object.entries(extraEnv)) {
183
+ if (!value)
184
+ continue;
185
+ env[key] = value;
186
+ }
187
+ if (sessionKey) {
188
+ const marker = (0, managed_process_registry_1.getMarkerEnv)(sessionKey);
189
+ for (const [k, v] of Object.entries(marker)) {
190
+ env[k] = v;
191
+ }
192
+ }
193
+ return env;
194
+ }
195
+ const VOLATILE_ENV_KEYS = new Set(['FUNOLIO_TOOL_TODO_ID', 'FUNOLIO_MANAGED_SESSION']);
196
+ function computeEnvFingerprint(env) {
197
+ return JSON.stringify(Object.entries(env)
198
+ .filter(([key, value]) => typeof value === 'string' && !VOLATILE_ENV_KEYS.has(key))
199
+ .sort(([a], [b]) => a.localeCompare(b)));
200
+ }
201
+ function buildCodexAppServerToolEnv(input) {
202
+ const env = {};
203
+ const actorId = input.botName?.trim() || input.botId.trim();
204
+ if (actorId)
205
+ env.FUNOLIO_TOOL_ACTOR_ID = actorId;
206
+ if (input.projectId !== undefined && input.projectId !== null)
207
+ env.FUNOLIO_TOOL_PROJECT_ID = String(input.projectId);
208
+ return env;
209
+ }
210
+ function buildTurnMcpToolEnv(sessionEnv, currentTodoTaskId) {
211
+ const toolEnv = {};
212
+ for (const k of ['FUNOLIO_TOOL_ACTOR_ID', 'FUNOLIO_TOOL_PROJECT_ID']) {
213
+ if (sessionEnv[k])
214
+ toolEnv[k] = sessionEnv[k];
215
+ }
216
+ if (currentTodoTaskId !== undefined && currentTodoTaskId !== null) {
217
+ toolEnv.FUNOLIO_TOOL_TODO_ID = String(currentTodoTaskId);
218
+ }
219
+ return toolEnv;
220
+ }
221
+ function extractLatestUserText(messages) {
222
+ const renderContent = (content) => (typeof content === 'string'
223
+ ? content
224
+ : content.map((part) => (part.type === 'text'
225
+ ? part.text
226
+ : `[image:${part.mimeType};${Math.ceil((part.data?.length || 0) * 0.75)} bytes]`)).join('\n'));
227
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
228
+ if (lastMessage?.role === 'user') {
229
+ return renderContent(lastMessage.content);
230
+ }
231
+ if (messages.length === 0)
232
+ return '';
233
+ const rendered = messages.map((message) => {
234
+ const label = message.role === 'user' ? 'USER'
235
+ : message.role === 'assistant' ? 'ASSISTANT'
236
+ : message.role === 'tool' ? `TOOL (${message.toolName || 'tool'})`
237
+ : String(message.role || 'MESSAGE').toUpperCase();
238
+ return `${label}:\n${renderContent(message.content)}`;
239
+ });
240
+ return rendered.join('\n\n');
241
+ }
242
+ function buildCodexAppServerInput(messages) {
243
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
244
+ const content = lastMessage?.role === 'user' ? lastMessage.content : null;
245
+ // If the last user message has multimodal content, build image + text blocks
246
+ if (content && typeof content !== 'string') {
247
+ const blocks = [];
248
+ for (const part of content) {
249
+ if (part.type === 'text') {
250
+ blocks.push({ type: 'text', text: part.text });
251
+ }
252
+ else if (part.type === 'image' && part.data) {
253
+ blocks.push({
254
+ type: 'image',
255
+ url: `data:${part.mimeType || 'image/png'};base64,${part.data}`,
256
+ });
257
+ }
258
+ }
259
+ if (blocks.length > 0)
260
+ return blocks;
261
+ }
262
+ // Fallback: text-only
263
+ return [{ type: 'text', text: extractLatestUserText(messages) }];
264
+ }
265
+ function buildTurnUsage(tokenUsage) {
266
+ const last = tokenUsage?.last;
267
+ if (!last || typeof last !== 'object')
268
+ return undefined;
269
+ // token.txt Change 1: keep inputTokens sum + add breakdown.
270
+ const fresh = Number(last.inputTokens || 0);
271
+ const cacheRead = Number(last.cachedInputTokens || 0);
272
+ const inputTokens = fresh + cacheRead;
273
+ const outputTokens = Number(last.outputTokens || 0) + Number(last.reasoningOutputTokens || 0);
274
+ if (!Number.isFinite(inputTokens) || !Number.isFinite(outputTokens))
275
+ return undefined;
276
+ return {
277
+ inputTokens,
278
+ inputTokensFresh: fresh,
279
+ inputTokensCacheCreation: 0,
280
+ inputTokensCacheRead: cacheRead,
281
+ outputTokens,
282
+ };
283
+ }
284
+ function extractThreadId(payload) {
285
+ const id = payload?.thread?.id ?? payload?.threadId ?? null;
286
+ return typeof id === 'string' && id.trim() ? id.trim() : null;
287
+ }
288
+ function extractTurnId(payload) {
289
+ const id = payload?.turn?.id ?? payload?.turnId ?? null;
290
+ return typeof id === 'string' && id.trim() ? id.trim() : null;
291
+ }
292
+ function extractTurnStatus(payload) {
293
+ const status = payload?.turn?.status ?? null;
294
+ return typeof status === 'string' ? status : null;
295
+ }
296
+ function extractTurnFailureMessage(payload) {
297
+ const message = payload?.turn?.error?.message ?? payload?.error?.message ?? null;
298
+ return typeof message === 'string' && message.trim() ? message.trim() : null;
299
+ }
300
+ function isResponseMessage(value) {
301
+ return !!value && typeof value === 'object' && value.id !== undefined && ('result' in value || 'error' in value);
302
+ }
303
+ function isServerRequestMessage(value) {
304
+ return !!value && typeof value === 'object' && value.id !== undefined && typeof value.method === 'string';
305
+ }
306
+ function isNotificationMessage(value) {
307
+ return !!value && typeof value === 'object' && value.id === undefined && typeof value.method === 'string';
308
+ }
309
+ function isTurnMatch(activeTurn, params) {
310
+ if (!activeTurn)
311
+ return false;
312
+ if (activeTurn.threadId && params?.threadId && activeTurn.threadId !== params.threadId)
313
+ return false;
314
+ if (activeTurn.turnId && params?.turnId && activeTurn.turnId !== params.turnId)
315
+ return false;
316
+ return true;
317
+ }
318
+ function safeJsonParse(line) {
319
+ try {
320
+ return JSON.parse(line);
321
+ }
322
+ catch {
323
+ return null;
324
+ }
325
+ }
326
+ function describeStartupStatus(params) {
327
+ const name = typeof params?.name === 'string' ? params.name : 'MCP server';
328
+ const status = typeof params?.status === 'string' ? params.status : 'unknown';
329
+ const error = typeof params?.error === 'string' && params.error.trim() ? ` (${params.error.trim()})` : '';
330
+ return `${name}: ${status}${error}`;
331
+ }
332
+ function extractItemId(params) {
333
+ const id = params?.item?.id ?? params?.itemId ?? null;
334
+ return typeof id === 'string' && id.trim() ? id.trim() : null;
335
+ }
336
+ function extractAgentMessagePhase(params) {
337
+ if (params?.item?.type !== 'agentMessage')
338
+ return null;
339
+ const phase = params?.item?.phase ?? null;
340
+ return typeof phase === 'string' && phase.trim() ? phase.trim().toLowerCase() : null;
341
+ }
342
+ function describeItemProgress(method, params) {
343
+ if (!params?.item || typeof params.item !== 'object')
344
+ return null;
345
+ const itemType = typeof params.item.type === 'string' ? params.item.type : 'item';
346
+ const label = typeof params.item.name === 'string' ? params.item.name
347
+ : typeof params.item.title === 'string' ? params.item.title
348
+ : typeof params.item.serverName === 'string' ? params.item.serverName
349
+ : typeof params.item.toolName === 'string' ? params.item.toolName
350
+ : '';
351
+ const status = typeof params.item.status === 'string' ? ` (${params.item.status})`
352
+ : '';
353
+ const prefix = method === 'item/started' ? 'Started' : 'Completed';
354
+ return `${prefix} ${itemType}${label ? `: ${label}` : ''}${status}`;
355
+ }
356
+ /**
357
+ * Build a structured tool event from a Codex app-server item/started or
358
+ * item/completed notification, when the item type represents a tool-like
359
+ * operation. Returns null for non-tool items (e.g. agent_message, reasoning).
360
+ *
361
+ * Recognized tool item types:
362
+ * - command_execution → shell command (PTY exec)
363
+ * - file_change → file edit / write / delete
364
+ * - mcpToolCall → MCP tool invocation
365
+ * - applyPatch → patch apply
366
+ */
367
+ function buildToolEventFromItem(kind, params) {
368
+ const item = params?.item;
369
+ if (!item || typeof item !== 'object')
370
+ return null;
371
+ const itemType = typeof item.type === 'string' ? item.type : '';
372
+ const toolItemTypes = new Set([
373
+ 'command_execution',
374
+ 'file_change',
375
+ 'mcpToolCall',
376
+ 'mcp_tool_call',
377
+ 'applyPatch',
378
+ 'apply_patch',
379
+ ]);
380
+ if (!toolItemTypes.has(itemType))
381
+ return null;
382
+ const toolName = (typeof item.name === 'string' && item.name)
383
+ || (typeof item.toolName === 'string' && item.toolName)
384
+ || (typeof item.serverName === 'string' && item.serverName)
385
+ || itemType;
386
+ const toolCallId = (typeof item.id === 'string' && item.id)
387
+ || (typeof item.itemId === 'string' && item.itemId)
388
+ || `codex-${itemType}-${Date.now()}`;
389
+ const event = {
390
+ kind,
391
+ toolName,
392
+ toolCallId,
393
+ };
394
+ if (kind === 'call') {
395
+ if (item.command || item.input || item.arguments || item.path || item.changes) {
396
+ event.arguments = {
397
+ ...(item.command ? { command: item.command } : {}),
398
+ ...(item.input ? { input: item.input } : {}),
399
+ ...(item.arguments ? item.arguments : {}),
400
+ ...(item.path ? { path: item.path } : {}),
401
+ ...(item.changes ? { changes: item.changes } : {}),
402
+ };
403
+ }
404
+ }
405
+ else {
406
+ // result
407
+ const output = (typeof item.output === 'string' && item.output)
408
+ || (typeof item.stdout === 'string' && item.stdout)
409
+ || (typeof item.result === 'string' && item.result)
410
+ || '';
411
+ if (output)
412
+ event.output = output;
413
+ const status = typeof item.status === 'string' ? item.status.toLowerCase() : '';
414
+ event.isError = status === 'failed' || status === 'error' || !!item.error;
415
+ }
416
+ return event;
417
+ }
418
+ function normalizeCodexSettings(settings) {
419
+ const reasoningEffort = String(settings?.reasoningEffort || '').trim().toLowerCase();
420
+ const reasoningSummary = String(settings?.reasoningSummary || '').trim().toLowerCase();
421
+ const personality = String(settings?.personality || '').trim().toLowerCase();
422
+ const serviceTier = String(settings?.serviceTier || '').trim().toLowerCase();
423
+ const sandboxPolicy = String(settings?.sandboxPolicy || '').trim().toLowerCase();
424
+ const approvalPolicy = String(settings?.approvalPolicy || '').trim().toLowerCase();
425
+ return {
426
+ reasoningEffort: VALID_CODEX_REASONING_EFFORTS.has(reasoningEffort)
427
+ ? reasoningEffort
428
+ : DEFAULT_CODEX_SETTINGS.reasoningEffort,
429
+ reasoningSummary: VALID_CODEX_REASONING_SUMMARIES.has(reasoningSummary)
430
+ ? reasoningSummary
431
+ : DEFAULT_CODEX_SETTINGS.reasoningSummary,
432
+ personality: VALID_CODEX_PERSONALITIES.has(personality)
433
+ ? personality
434
+ : DEFAULT_CODEX_SETTINGS.personality,
435
+ serviceTier: VALID_CODEX_SERVICE_TIERS.has(serviceTier)
436
+ ? serviceTier
437
+ : DEFAULT_CODEX_SETTINGS.serviceTier,
438
+ sandboxPolicy: VALID_CODEX_SANDBOX_POLICIES.has(sandboxPolicy)
439
+ ? sandboxPolicy
440
+ : DEFAULT_CODEX_SETTINGS.sandboxPolicy,
441
+ approvalPolicy: VALID_CODEX_APPROVAL_POLICIES.has(approvalPolicy)
442
+ ? approvalPolicy
443
+ : DEFAULT_CODEX_SETTINGS.approvalPolicy,
444
+ };
445
+ }
446
+ function buildThreadSandbox(sandboxPolicy) {
447
+ switch (sandboxPolicy) {
448
+ case 'read-only':
449
+ return 'read-only';
450
+ case 'workspace-write':
451
+ return 'workspace-write';
452
+ default:
453
+ return 'danger-full-access';
454
+ }
455
+ }
456
+ function buildTurnSandboxPolicy(sandboxPolicy) {
457
+ switch (sandboxPolicy) {
458
+ case 'read-only':
459
+ return { type: 'readOnly' };
460
+ case 'workspace-write':
461
+ return { type: 'workspaceWrite' };
462
+ default:
463
+ return { type: 'dangerFullAccess' };
464
+ }
465
+ }
466
+ /**
467
+ * SHA-256 of the params that, together, define a unique Codex thread.
468
+ * If any of these inputs differ between warm-up and Send, the warmed
469
+ * thread is stale and must be invalidated so a fresh one is created
470
+ * with the current settings. systemPrompt is hashed rather than stored
471
+ * directly to avoid keeping a second copy of a potentially large string.
472
+ */
473
+ function computeThreadBootstrapFingerprint(opts) {
474
+ const settings = normalizeCodexSettings(opts.codexSettings);
475
+ const parts = [
476
+ opts.systemPrompt || '',
477
+ opts.model || '',
478
+ settings.approvalPolicy,
479
+ settings.sandboxPolicy,
480
+ settings.personality,
481
+ settings.reasoningEffort,
482
+ settings.serviceTier,
483
+ opts.resumeSessionId || '',
484
+ ];
485
+ return (0, crypto_1.createHash)('sha256').update(parts.join('\u0000')).digest('hex');
486
+ }
487
+ function buildApprovalResponse(method, params) {
488
+ switch (method) {
489
+ case 'item/commandExecution/requestApproval':
490
+ return { decision: 'accept' };
491
+ case 'item/fileChange/requestApproval':
492
+ return { decision: 'accept' };
493
+ case 'item/permissions/requestApproval':
494
+ return { permissions: params?.permissions || {}, scope: 'session' };
495
+ case 'applyPatchApproval':
496
+ return { decision: 'approved' };
497
+ case 'execCommandApproval':
498
+ return { decision: 'approved' };
499
+ case 'item/tool/requestUserInput':
500
+ return { answers: {} };
501
+ case 'mcpServer/elicitation/request':
502
+ return { action: 'decline' };
503
+ default:
504
+ return null;
505
+ }
506
+ }
507
+ async function defaultPrepareCliConfig() {
508
+ _prepareCodexCliConfigChain = _prepareCodexCliConfigChain.then(async () => {
509
+ await (0, sync_cli_config_1.syncMcpToCliConfig)('codex-cli');
510
+ });
511
+ return _prepareCodexCliConfigChain;
512
+ }
513
+ class CodexAppServerManager {
514
+ sessions = new Map();
515
+ now;
516
+ spawnProcess;
517
+ prepareCliConfig;
518
+ logInfo;
519
+ logWarn;
520
+ clientInfo;
521
+ constructor(deps = {}) {
522
+ this.now = deps.now || (() => Date.now());
523
+ this.spawnProcess = deps.spawnProcess || ((command, args, options) => (0, child_process_1.spawn)(command, args, options));
524
+ this.prepareCliConfig = deps.prepareCliConfig || defaultPrepareCliConfig;
525
+ this.logInfo = deps.logInfo || ((message) => console.log(message));
526
+ this.logWarn = deps.logWarn || ((message) => console.warn(message));
527
+ this.clientInfo = deps.clientInfo || { name: 'funolio-agent', version: '1.1.5' };
528
+ }
529
+ async warmSession(opts) {
530
+ throwIfNotLocalDesktop(opts.runtimeMode);
531
+ const key = sessionKey(opts.conversationId, opts.botId);
532
+ const requestedEnv = scrubCodexAppServerEnv(process.env, buildCodexAppServerToolEnv({
533
+ botId: opts.botId,
534
+ botName: opts.botName,
535
+ projectId: opts.projectId,
536
+ }), key);
537
+ const requestedEnvFingerprint = computeEnvFingerprint(requestedEnv);
538
+ const wantsThreadBootstrap = typeof opts.systemPrompt === 'string' && opts.systemPrompt.length > 0;
539
+ let hiddenPrimerCompleted = false;
540
+ const requestedBootstrapFingerprint = wantsThreadBootstrap
541
+ ? computeThreadBootstrapFingerprint({
542
+ systemPrompt: opts.systemPrompt,
543
+ model: opts.model,
544
+ codexSettings: opts.codexSettings,
545
+ resumeSessionId: opts.resumeSessionId,
546
+ })
547
+ : null;
548
+ let session = this.sessions.get(key);
549
+ if (session
550
+ && (session.cwd !== opts.cwd
551
+ || session.closed
552
+ || session.envFingerprint !== requestedEnvFingerprint)) {
553
+ this.closeSession(key);
554
+ session = undefined;
555
+ }
556
+ if (session
557
+ && wantsThreadBootstrap
558
+ && session.threadId
559
+ && session.threadBootstrapFingerprint
560
+ && session.threadBootstrapFingerprint !== requestedBootstrapFingerprint) {
561
+ session.threadId = null;
562
+ session.threadBootstrapFingerprint = null;
563
+ session.bootstrapPromise = null;
564
+ session.warmPromise = null;
565
+ session.warmRequestedAtMs = null;
566
+ session.warmReadyAtMs = null;
567
+ session.bootstrapGeneration++;
568
+ }
569
+ const reusedExistingSession = !!session;
570
+ if (!session) {
571
+ session = await this.createSession(key, opts.cwd, requestedEnv, opts.currentTodoTaskId);
572
+ this.sessions.set(key, session);
573
+ }
574
+ // Fast path: session exists, handshake already complete, AND either
575
+ // (a) the caller didn't ask for a thread bootstrap, or (b) the thread
576
+ // is already bootstrapped with the current fingerprint.
577
+ const bootstrapAlreadyCorrect = !wantsThreadBootstrap
578
+ || (!!session.threadId && session.threadBootstrapFingerprint === requestedBootstrapFingerprint);
579
+ if (reusedExistingSession
580
+ && !session.warmPromise
581
+ && !session.warmReadyAtMs
582
+ && session.readyResolved
583
+ && bootstrapAlreadyCorrect) {
584
+ (0, managed_process_registry_1.markReused)(key);
585
+ return {
586
+ reusedExistingSession,
587
+ readyAgeMs: Math.max(0, this.now() - session.lastUsedAtMs),
588
+ sessionId: session.threadId || null,
589
+ hiddenPrimerCompleted: false,
590
+ };
591
+ }
592
+ if (!session.warmPromise && !session.warmReadyAtMs) {
593
+ session.warmRequestedAtMs = this.now();
594
+ session.warmPromise = withWarmTimeout((async () => {
595
+ await session.readyPromise;
596
+ if (session.closed) {
597
+ throw new Error('Codex app-server warm session closed before it was ready');
598
+ }
599
+ if (wantsThreadBootstrap && !session.threadId && !session.bootstrapPromise) {
600
+ session.bootstrapPromise = this.bootstrapWarmThread(session, opts, requestedBootstrapFingerprint);
601
+ const primerMessages = Array.isArray(opts.primerMessages) && opts.primerMessages.length > 0
602
+ ? opts.primerMessages
603
+ : null;
604
+ if (primerMessages) {
605
+ session.bootstrapPromise = session.bootstrapPromise.then(async () => {
606
+ if (!session || session.closed || !session.threadId)
607
+ return;
608
+ try {
609
+ await this.runTurnInternal(session, {
610
+ runtimeMode: opts.runtimeMode,
611
+ conversationId: opts.conversationId,
612
+ botId: opts.botId,
613
+ botName: opts.botName,
614
+ cwd: opts.cwd,
615
+ systemPrompt: opts.systemPrompt || '',
616
+ messages: primerMessages,
617
+ model: opts.model || null,
618
+ projectId: opts.projectId,
619
+ currentTodoTaskId: opts.currentTodoTaskId,
620
+ codexSettings: {
621
+ ...(opts.codexSettings || {}),
622
+ reasoningEffort: 'low',
623
+ reasoningSummary: 'none',
624
+ },
625
+ resumeSessionId: null,
626
+ }, {
627
+ skipBootstrapWait: true,
628
+ suppressSendPathLog: true,
629
+ });
630
+ hiddenPrimerCompleted = true;
631
+ this.logInfo(`[cli-warm] codex hidden primer completed threadId=${JSON.stringify(session.threadId)}`);
632
+ }
633
+ catch (err) {
634
+ this.logWarn(`[cli-warm] codex hidden primer failed: ${err?.message || String(err)}`);
635
+ }
636
+ });
637
+ }
638
+ }
639
+ if (session.bootstrapPromise) {
640
+ await session.bootstrapPromise;
641
+ session.bootstrapPromise = null;
642
+ }
643
+ session.warmReadyAtMs = this.now();
644
+ })(), opts.timeoutMs ?? CODEX_APP_SERVER_WARM_TIMEOUT_MS, () => this.closeSession(key)).catch((err) => {
645
+ if (this.sessions.get(key) === session) {
646
+ this.closeSession(key);
647
+ }
648
+ throw err;
649
+ });
650
+ }
651
+ await session.warmPromise;
652
+ return {
653
+ reusedExistingSession,
654
+ readyAgeMs: session.warmReadyAtMs ? Math.max(0, this.now() - session.warmReadyAtMs) : 0,
655
+ sessionId: session.threadId || null,
656
+ hiddenPrimerCompleted,
657
+ };
658
+ }
659
+ /**
660
+ * Issues thread/start (or thread/resume if resumeSessionId is provided)
661
+ * during warm-up, populating session.threadId so runTurn can skip the
662
+ * call. Failure leaves the session with no thread id — runTurn will
663
+ * retry from scratch via its normal path.
664
+ */
665
+ async bootstrapWarmThread(session, opts, fingerprint) {
666
+ const settings = normalizeCodexSettings(opts.codexSettings);
667
+ const gen = session.bootstrapGeneration;
668
+ const startMs = this.now();
669
+ const BOOTSTRAP_TIMEOUT_MS = 30_000;
670
+ try {
671
+ const rpcParams = opts.resumeSessionId
672
+ ? {
673
+ threadId: opts.resumeSessionId,
674
+ cwd: opts.cwd,
675
+ developerInstructions: opts.systemPrompt || null,
676
+ approvalPolicy: settings.approvalPolicy,
677
+ sandbox: buildThreadSandbox(settings.sandboxPolicy),
678
+ model: opts.model || null,
679
+ personality: settings.personality,
680
+ modelProvider: 'openai',
681
+ }
682
+ : {
683
+ cwd: opts.cwd,
684
+ developerInstructions: opts.systemPrompt || null,
685
+ approvalPolicy: settings.approvalPolicy,
686
+ sandbox: buildThreadSandbox(settings.sandboxPolicy),
687
+ model: opts.model || null,
688
+ personality: settings.personality,
689
+ modelProvider: 'openai',
690
+ ephemeral: false,
691
+ };
692
+ const method = opts.resumeSessionId ? 'thread/resume' : 'thread/start';
693
+ const response = await Promise.race([
694
+ this.sendRequest(session, method, rpcParams),
695
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Thread bootstrap timed out')), BOOTSTRAP_TIMEOUT_MS)),
696
+ ]);
697
+ if (session.bootstrapGeneration !== gen)
698
+ return;
699
+ session.threadId = extractThreadId(response);
700
+ session.threadBootstrapFingerprint = fingerprint;
701
+ this.logInfo(`[cli-warm] codex thread bootstrap completed in ${this.now() - startMs}ms threadId=${JSON.stringify(session.threadId)}`);
702
+ }
703
+ catch (err) {
704
+ if (session.bootstrapGeneration !== gen)
705
+ return;
706
+ session.threadId = null;
707
+ session.threadBootstrapFingerprint = null;
708
+ this.logWarn(`[cli-warm] codex thread bootstrap failed: ${err?.message || String(err)}`);
709
+ }
710
+ }
711
+ async runTurn(opts) {
712
+ throwIfNotLocalDesktop(opts.runtimeMode);
713
+ const key = sessionKey(opts.conversationId, opts.botId);
714
+ const requestedEnv = scrubCodexAppServerEnv(process.env, buildCodexAppServerToolEnv({
715
+ botId: opts.botId,
716
+ botName: opts.botName,
717
+ projectId: opts.projectId,
718
+ }), key);
719
+ const requestedEnvFingerprint = computeEnvFingerprint(requestedEnv);
720
+ let session = this.sessions.get(key);
721
+ const expectedThreadId = opts.forceFreshSession ? null : (opts.resumeSessionId || null);
722
+ const restartReasons = session
723
+ ? collectProcessRestartReasons(session, opts.cwd, requestedEnvFingerprint, expectedThreadId)
724
+ : [];
725
+ if (session && restartReasons.length > 0) {
726
+ this.logInfo(`[codex-app-server] codex_process_restarted conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} pid=${session.child.pid ?? 'unknown'} reasons=${JSON.stringify(restartReasons)} requestedThreadId=${JSON.stringify(expectedThreadId)} existingThreadId=${JSON.stringify(session.threadId)}`);
727
+ this.closeSession(key);
728
+ session = undefined;
729
+ }
730
+ // Stale warm-thread invalidation. If warmSession bootstrapped a thread
731
+ // with a different systemPrompt / model / settings than this turn is
732
+ // using, the thread is wrong and must be discarded. Keeping the
733
+ // session + handshake alive; only the threadId is cleared so the
734
+ // existing "no thread → create one" branch of runTurnInternal fires.
735
+ if (session && session.threadId && session.threadBootstrapFingerprint) {
736
+ const currentFingerprint = computeThreadBootstrapFingerprint({
737
+ systemPrompt: opts.systemPrompt,
738
+ model: opts.model,
739
+ codexSettings: opts.codexSettings,
740
+ resumeSessionId: opts.forceFreshSession ? null : (opts.resumeSessionId || null),
741
+ });
742
+ if (currentFingerprint !== session.threadBootstrapFingerprint) {
743
+ session.threadId = null;
744
+ session.threadBootstrapFingerprint = null;
745
+ session.bootstrapPromise = null;
746
+ session.bootstrapGeneration++;
747
+ }
748
+ }
749
+ if (session?.warmPromise) {
750
+ try {
751
+ await session.warmPromise;
752
+ this.logInfo(`[cli-warm] warm_reused conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} provider="codex-cli" ageMs=${session.warmReadyAtMs ? this.now() - session.warmReadyAtMs : 0}`);
753
+ session.warmPromise = null;
754
+ session.warmRequestedAtMs = null;
755
+ session.warmReadyAtMs = null;
756
+ }
757
+ catch {
758
+ if (this.sessions.get(key) === session) {
759
+ this.closeSession(key);
760
+ }
761
+ session = undefined;
762
+ }
763
+ }
764
+ else if (session?.warmReadyAtMs) {
765
+ this.logInfo(`[cli-warm] warm_reused conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} provider="codex-cli" ageMs=${this.now() - session.warmReadyAtMs}`);
766
+ session.warmPromise = null;
767
+ session.warmRequestedAtMs = null;
768
+ session.warmReadyAtMs = null;
769
+ }
770
+ if (!session) {
771
+ this.logInfo(`[cli-warm] send_cold conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} provider="codex-cli"`);
772
+ session = await this.createSession(key, opts.cwd, requestedEnv, opts.currentTodoTaskId);
773
+ this.sessions.set(key, session);
774
+ }
775
+ else {
776
+ (0, managed_process_registry_1.markReused)(key);
777
+ this.logInfo(`[codex-app-server] codex_process_reuse conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} pid=${session.child.pid ?? 'unknown'} forceFreshSession=${opts.forceFreshSession ? 'true' : 'false'} threadId=${JSON.stringify(session.threadId)}`);
778
+ }
779
+ const run = async () => this.runTurnInternal(session, opts);
780
+ const queued = session.chain.then(run, run);
781
+ session.chain = queued.then(() => undefined, () => undefined);
782
+ return queued;
783
+ }
784
+ hasActiveSession(conversationId, botId) {
785
+ const session = this.sessions.get(sessionKey(conversationId, botId));
786
+ return !!session && !session.closed;
787
+ }
788
+ closeSessionByConversation(conversationId, botId) {
789
+ this.closeSession(sessionKey(conversationId, botId));
790
+ }
791
+ closeSessionsByConversation(conversationId) {
792
+ const prefix = `${conversationId}::`;
793
+ let closed = 0;
794
+ for (const key of [...this.sessions.keys()]) {
795
+ if (key.startsWith(prefix)) {
796
+ this.closeSession(key);
797
+ closed++;
798
+ }
799
+ }
800
+ return closed;
801
+ }
802
+ closeWarmSessionsByConversation(conversationId) {
803
+ const prefix = `${conversationId}::`;
804
+ let closed = 0;
805
+ for (const [key, session] of [...this.sessions.entries()]) {
806
+ if (key.startsWith(prefix) && (session.warmPromise || session.warmReadyAtMs)) {
807
+ this.closeSession(key);
808
+ closed++;
809
+ }
810
+ }
811
+ return closed;
812
+ }
813
+ closeSessionsByBotId(botId) {
814
+ const suffix = `::${botId}`;
815
+ for (const key of [...this.sessions.keys()]) {
816
+ if (key.endsWith(suffix)) {
817
+ this.closeSession(key);
818
+ }
819
+ }
820
+ }
821
+ closeAll() {
822
+ for (const key of [...this.sessions.keys()]) {
823
+ this.closeSession(key);
824
+ }
825
+ }
826
+ closeSessionByKey(key) {
827
+ this.closeSession(key);
828
+ }
829
+ closeSession(key) {
830
+ const session = this.sessions.get(key);
831
+ if (!session)
832
+ return;
833
+ session.closed = true;
834
+ session.bootstrapPromise = null;
835
+ session.bootstrapGeneration++;
836
+ const pid = session.child.pid;
837
+ let closeFailed = false;
838
+ if (pid) {
839
+ const killResult = (0, managed_process_registry_1.killProcessTreeDetailed)(pid);
840
+ closeFailed = !killResult.killed || !!killResult.error;
841
+ }
842
+ else {
843
+ try {
844
+ session.child.kill();
845
+ }
846
+ catch {
847
+ closeFailed = true;
848
+ }
849
+ }
850
+ for (const pending of session.pendingRequests.values()) {
851
+ pending.reject(new Error('Codex app-server session closed'));
852
+ }
853
+ session.pendingRequests.clear();
854
+ if (session.activeTurn) {
855
+ session.activeTurn.rejectCompletion(new Error('Codex app-server session closed during active turn'));
856
+ session.activeTurn = null;
857
+ }
858
+ this.sessions.delete(key);
859
+ (0, managed_process_registry_1.unregisterProcess)(key, false, closeFailed);
860
+ }
861
+ async createSession(key, cwd, env, currentTodoTaskId) {
862
+ // Serialize config-write + spawn as an atomic unit through the global
863
+ // chain so concurrent Codex sessions can't interleave config.toml writes.
864
+ // Codex reads config.toml at startup to discover MCP servers and their env,
865
+ // so the write must complete before spawn AND no other write can happen
866
+ // between this write and the spawned process reading its config.
867
+ const toolEnv = buildTurnMcpToolEnv(env, currentTodoTaskId);
868
+ const child = await new Promise((resolve, reject) => {
869
+ _prepareCodexCliConfigChain = _prepareCodexCliConfigChain.then(async () => {
870
+ await (0, sync_cli_config_1.syncMcpToCliConfig)('codex-cli', toolEnv);
871
+ resolve(this.spawnProcess(process.platform === 'win32' ? 'codex' : 'codex', ['app-server'], {
872
+ stdio: 'pipe',
873
+ shell: process.platform === 'win32',
874
+ windowsHide: true,
875
+ cwd,
876
+ env: env,
877
+ }));
878
+ }).catch(reject);
879
+ });
880
+ const session = {
881
+ key,
882
+ cwd,
883
+ env,
884
+ envFingerprint: computeEnvFingerprint(env),
885
+ child,
886
+ stdoutCarry: '',
887
+ createdAtMs: this.now(),
888
+ lastUsedAtMs: this.now(),
889
+ nextRequestId: 1,
890
+ pendingRequests: new Map(),
891
+ activeTurn: null,
892
+ threadId: null,
893
+ readyPromise: Promise.resolve(),
894
+ readyResolved: false,
895
+ warmPromise: null,
896
+ warmRequestedAtMs: null,
897
+ warmReadyAtMs: null,
898
+ threadBootstrapFingerprint: null,
899
+ bootstrapPromise: null,
900
+ bootstrapGeneration: 0,
901
+ closed: false,
902
+ chain: Promise.resolve(),
903
+ };
904
+ if (child.pid) {
905
+ this.logInfo(`[codex-app-server] spawned key=${key} pid=${child.pid} cwd=${cwd}`);
906
+ const keyParts = key.split('::');
907
+ (0, managed_process_registry_1.registerProcess)({
908
+ sessionKey: key,
909
+ provider: 'codex-app-server',
910
+ conversationId: keyParts[0] || '',
911
+ botId: keyParts[1] || '',
912
+ pid: child.pid,
913
+ cwd,
914
+ createdAt: new Date(session.createdAtMs).toISOString(),
915
+ lastUsedAt: new Date(session.lastUsedAtMs).toISOString(),
916
+ });
917
+ }
918
+ child.stdout.on('data', (chunk) => {
919
+ this.handleStdout(session, chunk.toString());
920
+ });
921
+ child.stderr.on('data', (chunk) => {
922
+ this.handleStderr(session, chunk.toString());
923
+ });
924
+ child.on('error', (err) => {
925
+ this.handleProcessError(session, err);
926
+ });
927
+ child.on('close', (code, signal) => {
928
+ this.handleProcessClose(session, code, signal);
929
+ });
930
+ session.readyPromise = this.initializeSession(session);
931
+ return session;
932
+ }
933
+ async initializeSession(session) {
934
+ await this.sendRequest(session, 'initialize', {
935
+ clientInfo: this.clientInfo,
936
+ });
937
+ this.sendNotification(session, 'initialized');
938
+ session.readyResolved = true;
939
+ }
940
+ syncTurnMcpConfig(session, currentTodoTaskId) {
941
+ const toolEnv = buildTurnMcpToolEnv(session.env, currentTodoTaskId);
942
+ _prepareCodexCliConfigChain = _prepareCodexCliConfigChain.then(async () => {
943
+ await (0, sync_cli_config_1.syncMcpToCliConfig)('codex-cli', toolEnv);
944
+ });
945
+ return _prepareCodexCliConfigChain;
946
+ }
947
+ async runTurnInternal(session, opts, internalOpts) {
948
+ await session.readyPromise;
949
+ session.lastUsedAtMs = this.now();
950
+ (0, managed_process_registry_1.updateLastUsed)(session.key);
951
+ (0, managed_process_registry_1.markTurnStarted)(session.key, 'codex_turn');
952
+ const codexSettings = normalizeCodexSettings(opts.codexSettings);
953
+ await this.syncTurnMcpConfig(session, opts.currentTodoTaskId);
954
+ const eventLog = createEventLog();
955
+ const completionPromise = new Promise((resolve, reject) => {
956
+ const activeTurn = {
957
+ threadId: session.threadId,
958
+ turnId: null,
959
+ finalContent: '',
960
+ thinkingText: '',
961
+ commentaryItemIds: new Set(),
962
+ commentaryTextByItemId: new Map(),
963
+ lastCommentaryText: '',
964
+ done: false,
965
+ aborted: false,
966
+ callbackChain: Promise.resolve(),
967
+ detailFingerprints: new Set(),
968
+ liveDetailCategoryCounts: new Map(),
969
+ eventLog,
970
+ onChunk: opts.onChunk,
971
+ onCommentary: opts.onCommentary,
972
+ onThinkingChunk: opts.onThinkingChunk,
973
+ onDetail: opts.onDetail,
974
+ onToolEvent: opts.onToolEvent,
975
+ resolveCompletion: resolve,
976
+ rejectCompletion: reject,
977
+ };
978
+ session.activeTurn = activeTurn;
979
+ });
980
+ const activeTurn = session.activeTurn;
981
+ const abortHandler = async () => {
982
+ activeTurn.aborted = true;
983
+ if (activeTurn.turnId) {
984
+ try {
985
+ await this.sendRequest(session, 'turn/interrupt', { turnId: activeTurn.turnId });
986
+ }
987
+ catch {
988
+ // best effort; close handler will reject if needed
989
+ }
990
+ }
991
+ activeTurn.rejectCompletion(buildAbortError());
992
+ session.activeTurn = null;
993
+ };
994
+ opts.abortSignal?.addEventListener('abort', abortHandler, { once: true });
995
+ try {
996
+ const requestedThreadId = opts.forceFreshSession ? null : (opts.resumeSessionId || null);
997
+ if (opts.forceFreshSession) {
998
+ const hasWarmBootstrap = !!session.threadBootstrapFingerprint || !!session.bootstrapPromise;
999
+ if (!hasWarmBootstrap) {
1000
+ session.threadId = null;
1001
+ session.bootstrapPromise = null;
1002
+ session.bootstrapGeneration++;
1003
+ }
1004
+ }
1005
+ let awaitedBootstrap = false;
1006
+ let bootstrapTimedOut = false;
1007
+ if (!internalOpts?.skipBootstrapWait && session.bootstrapPromise) {
1008
+ awaitedBootstrap = true;
1009
+ try {
1010
+ await session.bootstrapPromise;
1011
+ }
1012
+ catch {
1013
+ bootstrapTimedOut = true;
1014
+ }
1015
+ session.bootstrapPromise = null;
1016
+ }
1017
+ let coldThreadCreated = false;
1018
+ if (!session.threadId) {
1019
+ if (requestedThreadId) {
1020
+ const response = await this.sendRequest(session, 'thread/resume', {
1021
+ threadId: requestedThreadId,
1022
+ cwd: opts.cwd,
1023
+ developerInstructions: opts.systemPrompt || null,
1024
+ approvalPolicy: codexSettings.approvalPolicy,
1025
+ sandbox: buildThreadSandbox(codexSettings.sandboxPolicy),
1026
+ model: opts.model || null,
1027
+ personality: codexSettings.personality,
1028
+ modelProvider: 'openai',
1029
+ });
1030
+ session.threadId = extractThreadId(response);
1031
+ coldThreadCreated = true;
1032
+ }
1033
+ else {
1034
+ const response = await this.sendRequest(session, 'thread/start', {
1035
+ cwd: opts.cwd,
1036
+ developerInstructions: opts.systemPrompt || null,
1037
+ approvalPolicy: codexSettings.approvalPolicy,
1038
+ sandbox: buildThreadSandbox(codexSettings.sandboxPolicy),
1039
+ model: opts.model || null,
1040
+ personality: codexSettings.personality,
1041
+ modelProvider: 'openai',
1042
+ ephemeral: false,
1043
+ });
1044
+ session.threadId = extractThreadId(response);
1045
+ coldThreadCreated = true;
1046
+ }
1047
+ }
1048
+ if (!internalOpts?.suppressSendPathLog) {
1049
+ if (bootstrapTimedOut) {
1050
+ this.logInfo(`[cli-warm] send_path=bootstrap_timeout fallback=cold`);
1051
+ }
1052
+ else if (awaitedBootstrap && session.threadId && !coldThreadCreated) {
1053
+ this.logInfo(`[cli-warm] send_path=waited_on_warm`);
1054
+ }
1055
+ else if (session.threadId && !awaitedBootstrap && !coldThreadCreated) {
1056
+ this.logInfo(`[cli-warm] send_path=reused_warm_thread`);
1057
+ }
1058
+ else {
1059
+ this.logInfo(`[cli-warm] send_path=cold_start`);
1060
+ }
1061
+ }
1062
+ if (session.threadId && !coldThreadCreated) {
1063
+ const reuseMode = awaitedBootstrap
1064
+ ? 'awaited_bootstrap'
1065
+ : session.threadBootstrapFingerprint
1066
+ ? 'warm_bootstrap'
1067
+ : 'existing_thread';
1068
+ this.logInfo(`[codex-app-server] codex_thread_reuse conversationId=${JSON.stringify(opts.conversationId)} botId=${JSON.stringify(opts.botId)} threadId=${JSON.stringify(session.threadId)} mode=${reuseMode} forceFreshSession=${opts.forceFreshSession ? 'true' : 'false'}`);
1069
+ }
1070
+ const threadId = session.threadId;
1071
+ if (!threadId) {
1072
+ throw new Error('Codex app-server did not return a thread id');
1073
+ }
1074
+ activeTurn.threadId = threadId;
1075
+ const turnStartParams = {
1076
+ threadId,
1077
+ cwd: opts.cwd,
1078
+ approvalPolicy: codexSettings.approvalPolicy,
1079
+ effort: codexSettings.reasoningEffort,
1080
+ sandboxPolicy: buildTurnSandboxPolicy(codexSettings.sandboxPolicy),
1081
+ personality: codexSettings.personality,
1082
+ input: buildCodexAppServerInput(opts.messages),
1083
+ model: opts.model || null,
1084
+ serviceTier: codexSettings.serviceTier,
1085
+ };
1086
+ if (codexSettings.reasoningSummary && codexSettings.reasoningSummary !== 'none') {
1087
+ turnStartParams.summary = codexSettings.reasoningSummary;
1088
+ }
1089
+ const turnStartResponse = await this.sendRequest(session, 'turn/start', turnStartParams);
1090
+ activeTurn.turnId = extractTurnId(turnStartResponse);
1091
+ (0, managed_process_registry_1.markTurnActivity)(session.key, 'codex_turn_started');
1092
+ return await completionPromise;
1093
+ }
1094
+ finally {
1095
+ opts.abortSignal?.removeEventListener('abort', abortHandler);
1096
+ (0, managed_process_registry_1.markTurnFinished)(session.key, 'codex_turn_finished');
1097
+ }
1098
+ }
1099
+ sendNotification(session, method, params) {
1100
+ const payload = params === undefined
1101
+ ? { jsonrpc: '2.0', method }
1102
+ : { jsonrpc: '2.0', method, params };
1103
+ appendEvent(session.activeTurn?.eventLog, 'out', 'notification', payload);
1104
+ session.child.stdin.write(`${JSON.stringify(payload)}\n`);
1105
+ }
1106
+ sendResponse(session, id, result, error) {
1107
+ const payload = error
1108
+ ? { jsonrpc: '2.0', id, error }
1109
+ : { jsonrpc: '2.0', id, result };
1110
+ appendEvent(session.activeTurn?.eventLog, 'out', 'response', payload);
1111
+ session.child.stdin.write(`${JSON.stringify(payload)}\n`);
1112
+ }
1113
+ sendRequest(session, method, params) {
1114
+ if (session.closed) {
1115
+ return Promise.reject(new Error('Codex app-server session is closed'));
1116
+ }
1117
+ const id = session.nextRequestId++;
1118
+ const payload = params === undefined
1119
+ ? { jsonrpc: '2.0', id, method }
1120
+ : { jsonrpc: '2.0', id, method, params };
1121
+ appendEvent(session.activeTurn?.eventLog, 'out', 'request', payload);
1122
+ return new Promise((resolve, reject) => {
1123
+ session.pendingRequests.set(id, { method, resolve, reject });
1124
+ session.child.stdin.write(`${JSON.stringify(payload)}\n`);
1125
+ });
1126
+ }
1127
+ handleStdout(session, chunk) {
1128
+ if (chunk)
1129
+ (0, managed_process_registry_1.markTurnActivity)(session.key, 'codex_stdout');
1130
+ session.stdoutCarry += chunk;
1131
+ const lines = session.stdoutCarry.split(/\r?\n/);
1132
+ session.stdoutCarry = lines.pop() || '';
1133
+ for (const line of lines) {
1134
+ const trimmed = line.trim();
1135
+ if (!trimmed)
1136
+ continue;
1137
+ const parsed = safeJsonParse(trimmed);
1138
+ if (!parsed) {
1139
+ appendEvent(session.activeTurn?.eventLog, 'in', 'parse_error', trimmed);
1140
+ this.logWarn(`[codex-app-server] Non-JSON stdout line: ${trimmed}`);
1141
+ continue;
1142
+ }
1143
+ if (isResponseMessage(parsed)) {
1144
+ appendEvent(session.activeTurn?.eventLog, 'in', 'response', parsed);
1145
+ this.handleResponse(session, parsed);
1146
+ continue;
1147
+ }
1148
+ if (isServerRequestMessage(parsed)) {
1149
+ appendEvent(session.activeTurn?.eventLog, 'in', 'server_request', parsed);
1150
+ void this.handleServerRequest(session, parsed);
1151
+ continue;
1152
+ }
1153
+ if (isNotificationMessage(parsed)) {
1154
+ appendEvent(session.activeTurn?.eventLog, 'in', 'notification', parsed);
1155
+ void this.handleNotification(session, parsed);
1156
+ continue;
1157
+ }
1158
+ appendEvent(session.activeTurn?.eventLog, 'in', 'parse_error', parsed);
1159
+ this.logWarn('[codex-app-server] Unrecognized stdout payload');
1160
+ }
1161
+ }
1162
+ handleResponse(session, message) {
1163
+ const pending = session.pendingRequests.get(message.id);
1164
+ if (!pending)
1165
+ return;
1166
+ session.pendingRequests.delete(message.id);
1167
+ if (message.error) {
1168
+ pending.reject(new Error(`${pending.method} failed: ${message.error.message || 'Unknown error'}`));
1169
+ return;
1170
+ }
1171
+ pending.resolve(message.result);
1172
+ }
1173
+ async handleServerRequest(session, message) {
1174
+ const approvalResponse = buildApprovalResponse(message.method, message.params);
1175
+ if (approvalResponse !== null) {
1176
+ await this.emitDetail(session.activeTurn, `Auto-handled ${message.method}`);
1177
+ this.sendResponse(session, message.id, approvalResponse);
1178
+ return;
1179
+ }
1180
+ this.sendResponse(session, message.id, undefined, {
1181
+ code: -32601,
1182
+ message: `Unsupported server request: ${message.method}`,
1183
+ });
1184
+ }
1185
+ async handleNotification(session, message) {
1186
+ const params = message.params || {};
1187
+ if (message.method === 'thread/started') {
1188
+ const threadId = extractThreadId(params);
1189
+ if (threadId) {
1190
+ session.threadId = threadId;
1191
+ if (session.activeTurn)
1192
+ session.activeTurn.threadId = threadId;
1193
+ }
1194
+ return;
1195
+ }
1196
+ if (message.method === 'turn/started') {
1197
+ const turnId = extractTurnId(params);
1198
+ if (session.activeTurn && turnId) {
1199
+ session.activeTurn.turnId = turnId;
1200
+ }
1201
+ return;
1202
+ }
1203
+ if (message.method === 'thread/tokenUsage/updated' && isTurnMatch(session.activeTurn, params)) {
1204
+ const usage = buildTurnUsage(params.tokenUsage);
1205
+ if (usage && session.activeTurn) {
1206
+ session.activeTurn.usage = usage;
1207
+ }
1208
+ return;
1209
+ }
1210
+ if (message.method === 'item/agentMessage/delta' && isTurnMatch(session.activeTurn, params) && session.activeTurn) {
1211
+ const delta = typeof params.delta === 'string' ? params.delta : '';
1212
+ const itemId = extractItemId(params);
1213
+ if (delta) {
1214
+ if (itemId && session.activeTurn.commentaryItemIds.has(itemId)) {
1215
+ const existing = session.activeTurn.commentaryTextByItemId.get(itemId) || '';
1216
+ session.activeTurn.commentaryTextByItemId.set(itemId, existing + delta);
1217
+ return;
1218
+ }
1219
+ session.activeTurn.finalContent += delta;
1220
+ await this.emitChunk(session.activeTurn, delta);
1221
+ }
1222
+ return;
1223
+ }
1224
+ if (message.method === 'item/reasoning/summaryTextDelta' && isTurnMatch(session.activeTurn, params) && session.activeTurn) {
1225
+ const delta = typeof params.delta === 'string' ? params.delta : '';
1226
+ if (delta) {
1227
+ session.activeTurn.thinkingText += delta;
1228
+ await this.emitThinkingChunk(session.activeTurn, delta);
1229
+ }
1230
+ return;
1231
+ }
1232
+ if (message.method === 'item/commandExecution/outputDelta' && isTurnMatch(session.activeTurn, params)) {
1233
+ const delta = typeof params.delta === 'string' ? params.delta : '';
1234
+ if (delta) {
1235
+ await this.emitDetail(session.activeTurn, delta, 'command_output');
1236
+ }
1237
+ return;
1238
+ }
1239
+ if (message.method === 'item/fileChange/outputDelta' && isTurnMatch(session.activeTurn, params)) {
1240
+ const delta = typeof params.delta === 'string' ? params.delta : '';
1241
+ if (delta) {
1242
+ await this.emitDetail(session.activeTurn, delta, 'file_output');
1243
+ }
1244
+ return;
1245
+ }
1246
+ if (message.method === 'item/mcpToolCall/progress' && isTurnMatch(session.activeTurn, params)) {
1247
+ if (typeof params.message === 'string') {
1248
+ await this.emitDetail(session.activeTurn, params.message, 'mcp_progress');
1249
+ }
1250
+ return;
1251
+ }
1252
+ if (message.method === 'mcpServer/startupStatus/updated') {
1253
+ const detail = describeStartupStatus(params);
1254
+ if (detail)
1255
+ await this.emitDetail(session.activeTurn, detail);
1256
+ return;
1257
+ }
1258
+ if (message.method === 'item/started' && isTurnMatch(session.activeTurn, params)) {
1259
+ const commentaryItemId = extractItemId(params);
1260
+ if (session.activeTurn && commentaryItemId && extractAgentMessagePhase(params) === 'commentary') {
1261
+ session.activeTurn.commentaryItemIds.add(commentaryItemId);
1262
+ session.activeTurn.commentaryTextByItemId.set(commentaryItemId, '');
1263
+ return;
1264
+ }
1265
+ // Emit structured tool call event for tool-like items (command_execution,
1266
+ // file_change, mcpToolCall, applyPatch). Also emit a textual detail line
1267
+ // for the live activity log.
1268
+ const toolEvent = buildToolEventFromItem('call', params);
1269
+ if (toolEvent && session.activeTurn?.onToolEvent) {
1270
+ try {
1271
+ await session.activeTurn.onToolEvent(toolEvent);
1272
+ }
1273
+ catch { /* swallow */ }
1274
+ }
1275
+ const detail = describeItemProgress(message.method, params);
1276
+ if (detail)
1277
+ await this.emitDetail(session.activeTurn, detail);
1278
+ return;
1279
+ }
1280
+ if (message.method === 'item/completed' && isTurnMatch(session.activeTurn, params)) {
1281
+ const commentaryItemId = extractItemId(params);
1282
+ if (session.activeTurn && commentaryItemId && session.activeTurn.commentaryItemIds.has(commentaryItemId)) {
1283
+ const completedText = (typeof params?.item?.text === 'string' && params.item.text)
1284
+ || session.activeTurn.commentaryTextByItemId.get(commentaryItemId)
1285
+ || '';
1286
+ session.activeTurn.commentaryItemIds.delete(commentaryItemId);
1287
+ session.activeTurn.commentaryTextByItemId.delete(commentaryItemId);
1288
+ await this.emitCommentary(session.activeTurn, completedText);
1289
+ return;
1290
+ }
1291
+ // Emit structured tool result event for tool-like items.
1292
+ const toolEvent = buildToolEventFromItem('result', params);
1293
+ if (toolEvent && session.activeTurn?.onToolEvent) {
1294
+ try {
1295
+ await session.activeTurn.onToolEvent(toolEvent);
1296
+ }
1297
+ catch { /* swallow */ }
1298
+ }
1299
+ const detail = describeItemProgress(message.method, params);
1300
+ if (detail)
1301
+ await this.emitDetail(session.activeTurn, detail);
1302
+ return;
1303
+ }
1304
+ if (message.method === 'turn/completed' && isTurnMatch(session.activeTurn, params) && session.activeTurn) {
1305
+ const activeTurn = session.activeTurn;
1306
+ activeTurn.done = true;
1307
+ try {
1308
+ await activeTurn.callbackChain;
1309
+ }
1310
+ catch {
1311
+ // Callback failures are already logged by the emit helpers.
1312
+ }
1313
+ const status = extractTurnStatus(params);
1314
+ if (status === 'failed') {
1315
+ const messageText = extractTurnFailureMessage(params) || 'Codex app-server turn failed';
1316
+ session.activeTurn = null;
1317
+ activeTurn.rejectCompletion(new Error(messageText));
1318
+ return;
1319
+ }
1320
+ if (status === 'interrupted' && activeTurn.aborted) {
1321
+ session.activeTurn = null;
1322
+ activeTurn.rejectCompletion(buildAbortError());
1323
+ return;
1324
+ }
1325
+ const result = {
1326
+ content: activeTurn.finalContent.trim(),
1327
+ sessionId: session.threadId,
1328
+ usage: activeTurn.usage,
1329
+ rawOutput: serializeEventLog(activeTurn.eventLog),
1330
+ eventLog: activeTurn.eventLog,
1331
+ thinking: activeTurn.thinkingText || undefined,
1332
+ };
1333
+ session.activeTurn = null;
1334
+ activeTurn.resolveCompletion(result);
1335
+ }
1336
+ }
1337
+ handleStderr(session, chunk) {
1338
+ if (chunk)
1339
+ (0, managed_process_registry_1.markTurnActivity)(session.key, 'codex_stderr');
1340
+ appendEvent(session.activeTurn?.eventLog, 'stderr', 'stderr', chunk);
1341
+ void this.emitDetail(session.activeTurn, chunk, 'stderr');
1342
+ }
1343
+ handleProcessError(session, err) {
1344
+ this.logWarn(`[codex-app-server] Process error for ${session.key}: ${err.stack || err.message}`);
1345
+ if (session.activeTurn) {
1346
+ const activeTurn = session.activeTurn;
1347
+ session.activeTurn = null;
1348
+ activeTurn.rejectCompletion(err);
1349
+ }
1350
+ for (const pending of session.pendingRequests.values()) {
1351
+ pending.reject(err);
1352
+ }
1353
+ session.pendingRequests.clear();
1354
+ session.closed = true;
1355
+ this.sessions.delete(session.key);
1356
+ (0, managed_process_registry_1.unregisterProcess)(session.key, false);
1357
+ }
1358
+ handleProcessClose(session, code, signal) {
1359
+ this.logWarn(`[codex-app-server] Process closed for ${session.key}: code=${code ?? 'null'} signal=${signal ?? 'null'} activeTurn=${session.activeTurn ? 'yes' : 'no'} pendingRequests=${session.pendingRequests.size}`);
1360
+ if (session.stdoutCarry.trim()) {
1361
+ const parsed = safeJsonParse(session.stdoutCarry.trim());
1362
+ if (parsed) {
1363
+ if (isResponseMessage(parsed)) {
1364
+ appendEvent(session.activeTurn?.eventLog, 'in', 'response', parsed);
1365
+ this.handleResponse(session, parsed);
1366
+ }
1367
+ else if (isServerRequestMessage(parsed)) {
1368
+ appendEvent(session.activeTurn?.eventLog, 'in', 'server_request', parsed);
1369
+ void this.handleServerRequest(session, parsed);
1370
+ }
1371
+ else if (isNotificationMessage(parsed)) {
1372
+ appendEvent(session.activeTurn?.eventLog, 'in', 'notification', parsed);
1373
+ void this.handleNotification(session, parsed);
1374
+ }
1375
+ }
1376
+ }
1377
+ session.closed = true;
1378
+ this.sessions.delete(session.key);
1379
+ (0, managed_process_registry_1.unregisterProcess)(session.key, false);
1380
+ const closeError = new Error(`Codex app-server exited${code !== null ? ` with code ${code}` : ''}${signal ? ` (${signal})` : ''}`);
1381
+ for (const pending of session.pendingRequests.values()) {
1382
+ pending.reject(closeError);
1383
+ }
1384
+ session.pendingRequests.clear();
1385
+ if (session.activeTurn) {
1386
+ const activeTurn = session.activeTurn;
1387
+ session.activeTurn = null;
1388
+ if (!activeTurn.done) {
1389
+ activeTurn.rejectCompletion(closeError);
1390
+ }
1391
+ }
1392
+ }
1393
+ async emitChunk(activeTurn, chunk) {
1394
+ if (!activeTurn?.onChunk || !chunk)
1395
+ return;
1396
+ activeTurn.callbackChain = activeTurn.callbackChain.then(async () => {
1397
+ await activeTurn.onChunk?.(chunk);
1398
+ }).catch((err) => {
1399
+ this.logWarn(`[codex-app-server] onChunk callback failed: ${err instanceof Error ? err.message : String(err)}`);
1400
+ });
1401
+ await activeTurn.callbackChain;
1402
+ }
1403
+ async emitCommentary(activeTurn, text) {
1404
+ if (!activeTurn?.onCommentary)
1405
+ return;
1406
+ const normalized = normalizeDetail(text);
1407
+ if (!normalized || normalized === activeTurn.lastCommentaryText)
1408
+ return;
1409
+ activeTurn.lastCommentaryText = normalized;
1410
+ activeTurn.callbackChain = activeTurn.callbackChain.then(async () => {
1411
+ await activeTurn.onCommentary?.(normalized);
1412
+ }).catch((err) => {
1413
+ this.logWarn(`[codex-app-server] onCommentary callback failed: ${err instanceof Error ? err.message : String(err)}`);
1414
+ });
1415
+ await activeTurn.callbackChain;
1416
+ }
1417
+ async emitThinkingChunk(activeTurn, chunk) {
1418
+ if (!activeTurn?.onThinkingChunk || !chunk)
1419
+ return;
1420
+ activeTurn.callbackChain = activeTurn.callbackChain.then(async () => {
1421
+ await activeTurn.onThinkingChunk?.(chunk);
1422
+ }).catch((err) => {
1423
+ this.logWarn(`[codex-app-server] onThinkingChunk callback failed: ${err instanceof Error ? err.message : String(err)}`);
1424
+ });
1425
+ await activeTurn.callbackChain;
1426
+ }
1427
+ async emitDetail(activeTurn, detail, source = 'status') {
1428
+ if (!activeTurn?.onDetail)
1429
+ return;
1430
+ const normalized = prepareLiveDetailForUser(activeTurn, detail, source);
1431
+ if (!normalized)
1432
+ return;
1433
+ if (activeTurn.detailFingerprints.has(normalized))
1434
+ return;
1435
+ activeTurn.detailFingerprints.add(normalized);
1436
+ activeTurn.callbackChain = activeTurn.callbackChain.then(async () => {
1437
+ await activeTurn.onDetail?.(normalized);
1438
+ }).catch((err) => {
1439
+ this.logWarn(`[codex-app-server] onDetail callback failed: ${err instanceof Error ? err.message : String(err)}`);
1440
+ });
1441
+ await activeTurn.callbackChain;
1442
+ }
1443
+ }
1444
+ exports.CodexAppServerManager = CodexAppServerManager;
1445
+ function getCodexAppServerManager() {
1446
+ if (!_manager) {
1447
+ _manager = new CodexAppServerManager();
1448
+ }
1449
+ return _manager;
1450
+ }
1451
+ async function runCodexAppServerTurnForTest(manager, opts) {
1452
+ return manager.runTurn(opts);
1453
+ }
1454
+ exports.__codexAppServerTestUtils = {
1455
+ LOCAL_ONLY_ERROR,
1456
+ buildCodexAppServerInput,
1457
+ buildCodexAppServerToolEnv,
1458
+ buildTurnMcpToolEnv,
1459
+ scrubCodexAppServerEnv,
1460
+ computeEnvFingerprint,
1461
+ buildApprovalResponse,
1462
+ buildTurnUsage,
1463
+ createEventLog,
1464
+ serializeEventLog,
1465
+ sessionKey,
1466
+ delay,
1467
+ };
1468
+ //# sourceMappingURL=codex-app-server-manager.js.map