@vibelet/cli 0.1.37 → 1.0.0

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 (323) hide show
  1. package/README.md +80 -0
  2. package/bin/cloudflared-quick-tunnel.mjs +11 -0
  3. package/bin/cloudflared-resolver.mjs +171 -0
  4. package/bin/vibelet-runtime-policy.mjs +36 -0
  5. package/bin/vibelet.cjs +12 -0
  6. package/bin/vibelet.mjs +1062 -0
  7. package/dist/index.cjs +126 -0
  8. package/package.json +25 -24
  9. package/app.json +0 -5
  10. package/dist/advertised-hosts.d.ts +0 -34
  11. package/dist/advertised-hosts.d.ts.map +0 -1
  12. package/dist/advertised-hosts.js +0 -176
  13. package/dist/advertised-hosts.js.map +0 -1
  14. package/dist/advertised-hosts.test.d.ts +0 -2
  15. package/dist/advertised-hosts.test.d.ts.map +0 -1
  16. package/dist/advertised-hosts.test.js +0 -96
  17. package/dist/advertised-hosts.test.js.map +0 -1
  18. package/dist/audit.d.ts +0 -30
  19. package/dist/audit.d.ts.map +0 -1
  20. package/dist/audit.js +0 -73
  21. package/dist/audit.js.map +0 -1
  22. package/dist/audit.test.d.ts +0 -2
  23. package/dist/audit.test.d.ts.map +0 -1
  24. package/dist/audit.test.js +0 -33
  25. package/dist/audit.test.js.map +0 -1
  26. package/dist/auth.d.ts +0 -6
  27. package/dist/auth.d.ts.map +0 -1
  28. package/dist/auth.js +0 -27
  29. package/dist/auth.js.map +0 -1
  30. package/dist/claude-hooks.d.ts +0 -58
  31. package/dist/claude-hooks.d.ts.map +0 -1
  32. package/dist/claude-hooks.js +0 -129
  33. package/dist/claude-hooks.js.map +0 -1
  34. package/dist/cli-version.d.ts +0 -3
  35. package/dist/cli-version.d.ts.map +0 -1
  36. package/dist/cli-version.js +0 -35
  37. package/dist/cli-version.js.map +0 -1
  38. package/dist/cli-version.test.d.ts +0 -2
  39. package/dist/cli-version.test.d.ts.map +0 -1
  40. package/dist/cli-version.test.js +0 -38
  41. package/dist/cli-version.test.js.map +0 -1
  42. package/dist/config.d.ts +0 -30
  43. package/dist/config.d.ts.map +0 -1
  44. package/dist/config.js +0 -327
  45. package/dist/config.js.map +0 -1
  46. package/dist/config.test.d.ts +0 -2
  47. package/dist/config.test.d.ts.map +0 -1
  48. package/dist/config.test.js +0 -184
  49. package/dist/config.test.js.map +0 -1
  50. package/dist/dev-auth.test.d.ts +0 -2
  51. package/dist/dev-auth.test.d.ts.map +0 -1
  52. package/dist/dev-auth.test.js +0 -154
  53. package/dist/dev-auth.test.js.map +0 -1
  54. package/dist/dev-script.test.d.ts +0 -2
  55. package/dist/dev-script.test.d.ts.map +0 -1
  56. package/dist/dev-script.test.js +0 -412
  57. package/dist/dev-script.test.js.map +0 -1
  58. package/dist/drivers/claude.d.ts +0 -34
  59. package/dist/drivers/claude.d.ts.map +0 -1
  60. package/dist/drivers/claude.js +0 -413
  61. package/dist/drivers/claude.js.map +0 -1
  62. package/dist/drivers/claude.test.d.ts +0 -2
  63. package/dist/drivers/claude.test.d.ts.map +0 -1
  64. package/dist/drivers/claude.test.js +0 -951
  65. package/dist/drivers/claude.test.js.map +0 -1
  66. package/dist/drivers/codex.d.ts +0 -38
  67. package/dist/drivers/codex.d.ts.map +0 -1
  68. package/dist/drivers/codex.js +0 -771
  69. package/dist/drivers/codex.js.map +0 -1
  70. package/dist/drivers/codex.test.d.ts +0 -2
  71. package/dist/drivers/codex.test.d.ts.map +0 -1
  72. package/dist/drivers/codex.test.js +0 -939
  73. package/dist/drivers/codex.test.js.map +0 -1
  74. package/dist/drivers/types.d.ts +0 -14
  75. package/dist/drivers/types.d.ts.map +0 -1
  76. package/dist/drivers/types.js +0 -2
  77. package/dist/drivers/types.js.map +0 -1
  78. package/dist/e2e.test.d.ts +0 -2
  79. package/dist/e2e.test.d.ts.map +0 -1
  80. package/dist/e2e.test.js +0 -111
  81. package/dist/e2e.test.js.map +0 -1
  82. package/dist/identity.d.ts +0 -10
  83. package/dist/identity.d.ts.map +0 -1
  84. package/dist/identity.js +0 -66
  85. package/dist/identity.js.map +0 -1
  86. package/dist/identity.test.d.ts +0 -2
  87. package/dist/identity.test.d.ts.map +0 -1
  88. package/dist/identity.test.js +0 -25
  89. package/dist/identity.test.js.map +0 -1
  90. package/dist/index-entry.test.d.ts +0 -2
  91. package/dist/index-entry.test.d.ts.map +0 -1
  92. package/dist/index-entry.test.js +0 -272
  93. package/dist/index-entry.test.js.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js +0 -707
  97. package/dist/index.js.map +0 -1
  98. package/dist/logger.d.ts +0 -31
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -75
  101. package/dist/logger.js.map +0 -1
  102. package/dist/metrics.d.ts +0 -52
  103. package/dist/metrics.d.ts.map +0 -1
  104. package/dist/metrics.js +0 -89
  105. package/dist/metrics.js.map +0 -1
  106. package/dist/pairing-store.d.ts +0 -29
  107. package/dist/pairing-store.d.ts.map +0 -1
  108. package/dist/pairing-store.js +0 -131
  109. package/dist/pairing-store.js.map +0 -1
  110. package/dist/pairing-store.test.d.ts +0 -2
  111. package/dist/pairing-store.test.d.ts.map +0 -1
  112. package/dist/pairing-store.test.js +0 -47
  113. package/dist/pairing-store.test.js.map +0 -1
  114. package/dist/paths.d.ts +0 -16
  115. package/dist/paths.d.ts.map +0 -1
  116. package/dist/paths.js +0 -18
  117. package/dist/paths.js.map +0 -1
  118. package/dist/perf-compare.d.ts +0 -13
  119. package/dist/perf-compare.d.ts.map +0 -1
  120. package/dist/perf-compare.js +0 -125
  121. package/dist/perf-compare.js.map +0 -1
  122. package/dist/port-conflict.d.ts +0 -9
  123. package/dist/port-conflict.d.ts.map +0 -1
  124. package/dist/port-conflict.js +0 -33
  125. package/dist/port-conflict.js.map +0 -1
  126. package/dist/port-conflict.test.d.ts +0 -2
  127. package/dist/port-conflict.test.d.ts.map +0 -1
  128. package/dist/port-conflict.test.js +0 -38
  129. package/dist/port-conflict.test.js.map +0 -1
  130. package/dist/process-scanner.d.ts +0 -43
  131. package/dist/process-scanner.d.ts.map +0 -1
  132. package/dist/process-scanner.js +0 -453
  133. package/dist/process-scanner.js.map +0 -1
  134. package/dist/process-scanner.perf.test.d.ts +0 -2
  135. package/dist/process-scanner.perf.test.d.ts.map +0 -1
  136. package/dist/process-scanner.perf.test.js +0 -186
  137. package/dist/process-scanner.perf.test.js.map +0 -1
  138. package/dist/process-scanner.test.d.ts +0 -2
  139. package/dist/process-scanner.test.d.ts.map +0 -1
  140. package/dist/process-scanner.test.js +0 -399
  141. package/dist/process-scanner.test.js.map +0 -1
  142. package/dist/push-protocol.d.ts +0 -15
  143. package/dist/push-protocol.d.ts.map +0 -1
  144. package/dist/push-protocol.js +0 -23
  145. package/dist/push-protocol.js.map +0 -1
  146. package/dist/push-protocol.test.d.ts +0 -2
  147. package/dist/push-protocol.test.d.ts.map +0 -1
  148. package/dist/push-protocol.test.js +0 -57
  149. package/dist/push-protocol.test.js.map +0 -1
  150. package/dist/push-store.d.ts +0 -22
  151. package/dist/push-store.d.ts.map +0 -1
  152. package/dist/push-store.js +0 -103
  153. package/dist/push-store.js.map +0 -1
  154. package/dist/push-store.test.d.ts +0 -2
  155. package/dist/push-store.test.d.ts.map +0 -1
  156. package/dist/push-store.test.js +0 -79
  157. package/dist/push-store.test.js.map +0 -1
  158. package/dist/push.d.ts +0 -65
  159. package/dist/push.d.ts.map +0 -1
  160. package/dist/push.js +0 -202
  161. package/dist/push.js.map +0 -1
  162. package/dist/push.test.d.ts +0 -2
  163. package/dist/push.test.d.ts.map +0 -1
  164. package/dist/push.test.js +0 -199
  165. package/dist/push.test.js.map +0 -1
  166. package/dist/safe-stdio.d.ts +0 -3
  167. package/dist/safe-stdio.d.ts.map +0 -1
  168. package/dist/safe-stdio.js +0 -46
  169. package/dist/safe-stdio.js.map +0 -1
  170. package/dist/scanner.d.ts +0 -30
  171. package/dist/scanner.d.ts.map +0 -1
  172. package/dist/scanner.js +0 -859
  173. package/dist/scanner.js.map +0 -1
  174. package/dist/scanner.perf.test.d.ts +0 -2
  175. package/dist/scanner.perf.test.d.ts.map +0 -1
  176. package/dist/scanner.perf.test.js +0 -320
  177. package/dist/scanner.perf.test.js.map +0 -1
  178. package/dist/scanner.test.d.ts +0 -2
  179. package/dist/scanner.test.d.ts.map +0 -1
  180. package/dist/scanner.test.js +0 -948
  181. package/dist/scanner.test.js.map +0 -1
  182. package/dist/session-inventory.d.ts +0 -63
  183. package/dist/session-inventory.d.ts.map +0 -1
  184. package/dist/session-inventory.js +0 -525
  185. package/dist/session-inventory.js.map +0 -1
  186. package/dist/session-inventory.perf.test.d.ts +0 -2
  187. package/dist/session-inventory.perf.test.d.ts.map +0 -1
  188. package/dist/session-inventory.perf.test.js +0 -220
  189. package/dist/session-inventory.perf.test.js.map +0 -1
  190. package/dist/session-inventory.test.d.ts +0 -2
  191. package/dist/session-inventory.test.d.ts.map +0 -1
  192. package/dist/session-inventory.test.js +0 -712
  193. package/dist/session-inventory.test.js.map +0 -1
  194. package/dist/session-manager.d.ts +0 -75
  195. package/dist/session-manager.d.ts.map +0 -1
  196. package/dist/session-manager.js +0 -1515
  197. package/dist/session-manager.js.map +0 -1
  198. package/dist/session-manager.test.d.ts +0 -2
  199. package/dist/session-manager.test.d.ts.map +0 -1
  200. package/dist/session-manager.test.js +0 -2861
  201. package/dist/session-manager.test.js.map +0 -1
  202. package/dist/session-store.d.ts +0 -42
  203. package/dist/session-store.d.ts.map +0 -1
  204. package/dist/session-store.js +0 -163
  205. package/dist/session-store.js.map +0 -1
  206. package/dist/session-store.test.d.ts +0 -2
  207. package/dist/session-store.test.d.ts.map +0 -1
  208. package/dist/session-store.test.js +0 -236
  209. package/dist/session-store.test.js.map +0 -1
  210. package/dist/session-title.d.ts +0 -6
  211. package/dist/session-title.d.ts.map +0 -1
  212. package/dist/session-title.js +0 -105
  213. package/dist/session-title.js.map +0 -1
  214. package/dist/session-title.perf.test.d.ts +0 -2
  215. package/dist/session-title.perf.test.d.ts.map +0 -1
  216. package/dist/session-title.perf.test.js +0 -99
  217. package/dist/session-title.perf.test.js.map +0 -1
  218. package/dist/session-title.test.d.ts +0 -2
  219. package/dist/session-title.test.d.ts.map +0 -1
  220. package/dist/session-title.test.js +0 -199
  221. package/dist/session-title.test.js.map +0 -1
  222. package/dist/shutdown-endpoint.test.d.ts +0 -2
  223. package/dist/shutdown-endpoint.test.d.ts.map +0 -1
  224. package/dist/shutdown-endpoint.test.js +0 -93
  225. package/dist/shutdown-endpoint.test.js.map +0 -1
  226. package/dist/storage-housekeeping.d.ts +0 -28
  227. package/dist/storage-housekeeping.d.ts.map +0 -1
  228. package/dist/storage-housekeeping.js +0 -76
  229. package/dist/storage-housekeeping.js.map +0 -1
  230. package/dist/storage-housekeeping.test.d.ts +0 -2
  231. package/dist/storage-housekeeping.test.d.ts.map +0 -1
  232. package/dist/storage-housekeeping.test.js +0 -65
  233. package/dist/storage-housekeeping.test.js.map +0 -1
  234. package/dist/test-daemon-harness.d.ts +0 -31
  235. package/dist/test-daemon-harness.d.ts.map +0 -1
  236. package/dist/test-daemon-harness.js +0 -337
  237. package/dist/test-daemon-harness.js.map +0 -1
  238. package/dist/token-auth.test.d.ts +0 -2
  239. package/dist/token-auth.test.d.ts.map +0 -1
  240. package/dist/token-auth.test.js +0 -52
  241. package/dist/token-auth.test.js.map +0 -1
  242. package/dist/utils.d.ts +0 -4
  243. package/dist/utils.d.ts.map +0 -1
  244. package/dist/utils.js +0 -40
  245. package/dist/utils.js.map +0 -1
  246. package/dist/utils.test.d.ts +0 -2
  247. package/dist/utils.test.d.ts.map +0 -1
  248. package/dist/utils.test.js +0 -54
  249. package/dist/utils.test.js.map +0 -1
  250. package/dist/ws-data.d.ts +0 -4
  251. package/dist/ws-data.d.ts.map +0 -1
  252. package/dist/ws-data.js +0 -20
  253. package/dist/ws-data.js.map +0 -1
  254. package/dist/ws-data.test.d.ts +0 -2
  255. package/dist/ws-data.test.d.ts.map +0 -1
  256. package/dist/ws-data.test.js +0 -17
  257. package/dist/ws-data.test.js.map +0 -1
  258. package/perf-reporter.mjs +0 -138
  259. package/scripts/build-release.mjs +0 -41
  260. package/scripts/dev.mjs +0 -537
  261. package/src/advertised-hosts.test.ts +0 -125
  262. package/src/advertised-hosts.ts +0 -225
  263. package/src/audit.test.ts +0 -38
  264. package/src/audit.ts +0 -117
  265. package/src/auth.ts +0 -31
  266. package/src/claude-hooks.ts +0 -195
  267. package/src/cli-version.test.ts +0 -36
  268. package/src/cli-version.ts +0 -46
  269. package/src/config.test.ts +0 -254
  270. package/src/config.ts +0 -324
  271. package/src/dev-auth.test.ts +0 -183
  272. package/src/dev-script.test.ts +0 -511
  273. package/src/drivers/claude.test.ts +0 -1186
  274. package/src/drivers/claude.ts +0 -443
  275. package/src/drivers/codex.test.ts +0 -1096
  276. package/src/drivers/codex.ts +0 -879
  277. package/src/drivers/types.ts +0 -15
  278. package/src/e2e.test.ts +0 -139
  279. package/src/identity.test.ts +0 -26
  280. package/src/identity.ts +0 -82
  281. package/src/index-entry.test.ts +0 -336
  282. package/src/index.ts +0 -781
  283. package/src/logger.ts +0 -112
  284. package/src/metrics.ts +0 -117
  285. package/src/pairing-store.test.ts +0 -53
  286. package/src/pairing-store.ts +0 -154
  287. package/src/paths.ts +0 -19
  288. package/src/perf-compare.ts +0 -164
  289. package/src/port-conflict.test.ts +0 -45
  290. package/src/port-conflict.ts +0 -44
  291. package/src/process-scanner.perf.test.ts +0 -222
  292. package/src/process-scanner.test.ts +0 -575
  293. package/src/process-scanner.ts +0 -514
  294. package/src/push-protocol.test.ts +0 -74
  295. package/src/push-protocol.ts +0 -36
  296. package/src/push-store.test.ts +0 -89
  297. package/src/push-store.ts +0 -126
  298. package/src/push.test.ts +0 -234
  299. package/src/push.ts +0 -318
  300. package/src/safe-stdio.ts +0 -51
  301. package/src/scanner.perf.test.ts +0 -359
  302. package/src/scanner.test.ts +0 -1045
  303. package/src/scanner.ts +0 -924
  304. package/src/session-inventory.perf.test.ts +0 -250
  305. package/src/session-inventory.test.ts +0 -1002
  306. package/src/session-inventory.ts +0 -721
  307. package/src/session-manager.test.ts +0 -3430
  308. package/src/session-manager.ts +0 -1775
  309. package/src/session-store.test.ts +0 -276
  310. package/src/session-store.ts +0 -202
  311. package/src/session-title.perf.test.ts +0 -118
  312. package/src/session-title.test.ts +0 -286
  313. package/src/session-title.ts +0 -108
  314. package/src/shutdown-endpoint.test.ts +0 -95
  315. package/src/storage-housekeeping.test.ts +0 -78
  316. package/src/storage-housekeeping.ts +0 -111
  317. package/src/test-daemon-harness.ts +0 -410
  318. package/src/token-auth.test.ts +0 -67
  319. package/src/utils.test.ts +0 -65
  320. package/src/utils.ts +0 -47
  321. package/src/ws-data.test.ts +0 -20
  322. package/src/ws-data.ts +0 -26
  323. package/tsconfig.json +0 -12
@@ -1,879 +0,0 @@
1
- import { spawn, type ChildProcess } from 'child_process';
2
- import { existsSync } from 'fs';
3
- import type { ApprovalRequestPayload } from '@vibelet/shared';
4
- import type { Driver, MessageHandler } from './types.js';
5
- import { config } from '../config.js';
6
- import { logger as rootLogger } from '../logger.js';
7
- import { metrics } from '../metrics.js';
8
- import { audit } from '../audit.js';
9
- import { CLI_VERSION } from '../cli-version.js';
10
-
11
- const log = rootLogger.child({ module: 'codex' });
12
-
13
- type CodexApprovalRequest =
14
- | {
15
- kind: 'command-execution';
16
- responseKind: 'v2';
17
- rpcId: number;
18
- toolName: string;
19
- input: Record<string, unknown>;
20
- }
21
- | {
22
- kind: 'file-change';
23
- responseKind: 'v2';
24
- rpcId: number;
25
- toolName: string;
26
- input: Record<string, unknown>;
27
- }
28
- | {
29
- kind: 'request-user-input-approval';
30
- rpcId: number;
31
- questionId: string;
32
- approveLabel: string;
33
- denyLabel: string;
34
- toolName: string;
35
- input: Record<string, unknown>;
36
- }
37
- | {
38
- kind: 'exec-command-legacy';
39
- rpcId: number;
40
- toolName: string;
41
- input: Record<string, unknown>;
42
- }
43
- | {
44
- kind: 'apply-patch-legacy';
45
- rpcId: number;
46
- toolName: string;
47
- input: Record<string, unknown>;
48
- };
49
-
50
- type CodexToolContext = {
51
- toolName: string;
52
- input: Record<string, unknown>;
53
- };
54
-
55
- function serializeApprovalContext(request: CodexApprovalRequest): NonNullable<ApprovalRequestPayload['approvalContext']> {
56
- switch (request.kind) {
57
- case 'request-user-input-approval':
58
- return {
59
- provider: 'codex',
60
- kind: request.kind,
61
- rpcId: request.rpcId,
62
- questionId: request.questionId,
63
- approveLabel: request.approveLabel,
64
- denyLabel: request.denyLabel,
65
- };
66
- default:
67
- return {
68
- provider: 'codex',
69
- kind: request.kind,
70
- rpcId: request.rpcId,
71
- };
72
- }
73
- }
74
-
75
- function restoreApprovalRequest(approval: ApprovalRequestPayload): CodexApprovalRequest | null {
76
- const context = approval.approvalContext;
77
- if (!context || context.provider !== 'codex') {
78
- return null;
79
- }
80
-
81
- switch (context.kind) {
82
- case 'command-execution':
83
- return {
84
- kind: context.kind,
85
- responseKind: 'v2',
86
- rpcId: context.rpcId,
87
- toolName: approval.toolName,
88
- input: approval.input,
89
- };
90
- case 'file-change':
91
- return {
92
- kind: context.kind,
93
- responseKind: 'v2',
94
- rpcId: context.rpcId,
95
- toolName: approval.toolName,
96
- input: approval.input,
97
- };
98
- case 'request-user-input-approval':
99
- if (!context.questionId || !context.approveLabel || !context.denyLabel) {
100
- return null;
101
- }
102
- return {
103
- kind: context.kind,
104
- rpcId: context.rpcId,
105
- questionId: context.questionId,
106
- approveLabel: context.approveLabel,
107
- denyLabel: context.denyLabel,
108
- toolName: approval.toolName,
109
- input: approval.input,
110
- };
111
- case 'exec-command-legacy':
112
- return {
113
- kind: context.kind,
114
- rpcId: context.rpcId,
115
- toolName: approval.toolName,
116
- input: approval.input,
117
- };
118
- case 'apply-patch-legacy':
119
- return {
120
- kind: context.kind,
121
- rpcId: context.rpcId,
122
- toolName: approval.toolName,
123
- input: approval.input,
124
- };
125
- default:
126
- return null;
127
- }
128
- }
129
-
130
- function asRecord(value: unknown): Record<string, unknown> | null {
131
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
132
- return value as Record<string, unknown>;
133
- }
134
-
135
- function readString(value: unknown): string | null {
136
- return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
137
- }
138
-
139
- const INTERRUPTED_TURN_STATUSES = new Set(['aborted', 'interrupted', 'cancelled', 'canceled']);
140
-
141
- function normalizeTurnStatus(value: unknown): string | null {
142
- const text = readString(value);
143
- return text ? text.toLowerCase() : null;
144
- }
145
-
146
- function normalizeItemType(value: unknown): string | null {
147
- const text = readString(value);
148
- return text ? text.replace(/[^a-z0-9]/gi, '').toLowerCase() : null;
149
- }
150
-
151
- function readItemId(record: Record<string, unknown> | null | undefined): string | null {
152
- if (!record) return null;
153
- return readString(record.itemId) ?? readString(record.id) ?? readString(record.callId) ?? readString(record.call_id);
154
- }
155
-
156
- function omitKeys(record: Record<string, unknown>, keys: string[]): Record<string, unknown> {
157
- const next: Record<string, unknown> = {};
158
- for (const [key, value] of Object.entries(record)) {
159
- if (!keys.includes(key)) next[key] = value;
160
- }
161
- return next;
162
- }
163
-
164
- function isApproveLabel(label: string): boolean {
165
- return /\bapprove\b|\ballow\b|\baccept\b|\byes\b|\bcontinue\b|\bproceed\b|\brun\b|\bapply\b/i.test(label);
166
- }
167
-
168
- function isDenyLabel(label: string): boolean {
169
- return /\bdeny\b|\breject\b|\bdecline\b|\bno\b|\bcancel\b|\babort\b|\bstop\b/i.test(label);
170
- }
171
-
172
- function resolveApprovalQuestion(questions: unknown): {
173
- questionId: string;
174
- approveLabel: string;
175
- denyLabel: string;
176
- } | null {
177
- if (!Array.isArray(questions)) return null;
178
- for (const question of questions) {
179
- const record = asRecord(question);
180
- if (!record) continue;
181
-
182
- const questionId = readString(record.id);
183
- const options = Array.isArray(record.options)
184
- ? record.options.map((option) => asRecord(option)).filter((option): option is Record<string, unknown> => Boolean(option))
185
- : [];
186
- if (!questionId || options.length === 0) continue;
187
-
188
- const labels = options
189
- .map((option) => readString(option.label))
190
- .filter((label): label is string => Boolean(label));
191
- if (labels.length < 2) continue;
192
-
193
- const approveLabel = labels.find((label) => isApproveLabel(label)) ?? labels[0];
194
- const denyLabel = labels.find((label) => isDenyLabel(label)) ?? labels[labels.length - 1];
195
-
196
- if (!approveLabel || !denyLabel || approveLabel === denyLabel) {
197
- continue;
198
- }
199
-
200
- return {
201
- questionId,
202
- approveLabel,
203
- denyLabel,
204
- };
205
- }
206
-
207
- return null;
208
- }
209
-
210
- function looksLikeApprovalRequestUserInput(questions: unknown): boolean {
211
- if (!Array.isArray(questions) || questions.length === 0) return false;
212
- const labels = questions
213
- .map((question) => asRecord(question))
214
- .filter((question): question is Record<string, unknown> => Boolean(question))
215
- .flatMap((question) => {
216
- const options = Array.isArray(question.options) ? question.options : [];
217
- return options
218
- .map((option) => asRecord(option))
219
- .filter((option): option is Record<string, unknown> => Boolean(option))
220
- .map((option) => readString(option.label))
221
- .filter((label): label is string => Boolean(label));
222
- });
223
- const hasApproval = labels.some((label) => isApproveLabel(label));
224
- const hasDeny = labels.some((label) => isDenyLabel(label));
225
- return hasApproval && hasDeny;
226
- }
227
-
228
- function extractToolContext(item: Record<string, unknown>): CodexToolContext | null {
229
- const itemType = normalizeItemType(item.type ?? item.itemType);
230
- if (itemType === 'commandexecution') {
231
- return {
232
- toolName: 'Bash',
233
- input: omitKeys(item, ['id', 'itemId', 'type', 'itemType', 'stdout', 'stderr', 'exitCode', 'exit_code', 'status', 'success', 'error']),
234
- };
235
- }
236
- if (itemType === 'filechange') {
237
- return {
238
- toolName: 'Patch',
239
- input: omitKeys(item, ['id', 'itemId', 'type', 'itemType', 'stdout', 'stderr', 'exitCode', 'exit_code', 'status', 'success', 'error']),
240
- };
241
- }
242
- if (itemType === 'mcptoolcall') {
243
- const server = readString(item.server);
244
- const tool = readString(item.tool) ?? readString(item.name);
245
- if (!tool) return null;
246
- return {
247
- toolName: server ? `mcp__${server}__${tool}` : tool,
248
- input: (asRecord(item.arguments) ?? asRecord(item.input) ?? {}) as Record<string, unknown>,
249
- };
250
- }
251
- return null;
252
- }
253
-
254
- function readRequestUserInputDescription(questions: unknown): string {
255
- if (!Array.isArray(questions)) return '';
256
- for (const question of questions) {
257
- const record = asRecord(question);
258
- if (!record) continue;
259
- const questionText = readString(record.question) ?? readString(record.header);
260
- if (questionText) return questionText;
261
- }
262
- return '';
263
- }
264
-
265
- function resolveCodexPolicy(approvalMode: string | undefined, cwd: string): {
266
- approvalPolicy: 'on-request' | 'never';
267
- sandbox: 'workspace-write' | 'danger-full-access';
268
- sandboxPolicy: Record<string, unknown>;
269
- } {
270
- if (approvalMode === 'autoApprove') {
271
- return {
272
- approvalPolicy: 'never',
273
- sandbox: 'danger-full-access',
274
- sandboxPolicy: { type: 'dangerFullAccess' },
275
- };
276
- }
277
- if (approvalMode === 'plan') {
278
- return {
279
- approvalPolicy: 'on-request',
280
- sandbox: 'workspace-write',
281
- sandboxPolicy: {
282
- type: 'workspaceWrite',
283
- writableRoots: [],
284
- readOnlyAccess: { type: 'fullAccess' },
285
- networkAccess: true,
286
- excludeTmpdirEnvVar: false,
287
- excludeSlashTmp: false,
288
- },
289
- };
290
- }
291
- return {
292
- approvalPolicy: 'on-request',
293
- sandbox: 'workspace-write',
294
- sandboxPolicy: {
295
- type: 'workspaceWrite',
296
- writableRoots: [cwd],
297
- readOnlyAccess: { type: 'fullAccess' },
298
- networkAccess: true,
299
- excludeTmpdirEnvVar: false,
300
- excludeSlashTmp: false,
301
- },
302
- };
303
- }
304
-
305
- export class CodexDriver implements Driver {
306
- private proc: ChildProcess | null = null;
307
- private handler: MessageHandler | null = null;
308
- private exitHandler: ((code: number | null) => void) | null = null;
309
- private buffer = '';
310
- private rpcId = 0;
311
- private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
312
- private threadId = '';
313
- private lastStderr: string[] = [];
314
- private approvalRequests = new Map<string, CodexApprovalRequest>();
315
- private toolContextByCallId = new Map<string, CodexToolContext>();
316
- private turnStartInFlight = false;
317
- private interruptRequestedDuringTurnStart = false;
318
-
319
- private approvalMode?: string;
320
- private cwd = '';
321
-
322
- async start(cwd: string, resumeSessionId?: string, approvalMode?: string): Promise<string> {
323
- this.approvalMode = approvalMode;
324
- this.cwd = cwd;
325
- this.approvalRequests.clear();
326
- this.toolContextByCallId.clear();
327
- this.turnStartInFlight = false;
328
- this.interruptRequestedDuringTurnStart = false;
329
- const codexPath = config.codexPath;
330
- let spawnCmd: string;
331
- let spawnArgs = this.buildSpawnArgs();
332
-
333
- if (config.isTransientPath(codexPath)) {
334
- const resolved = config.execViaLoginShell('codex', spawnArgs);
335
- spawnCmd = resolved.command;
336
- spawnArgs = resolved.args;
337
- log.info({ spawnCmd, argsPreview: spawnArgs.slice(0, 2) }, 'spawning via login shell');
338
- } else {
339
- spawnCmd = codexPath;
340
- log.info({ spawnCmd, args: spawnArgs }, 'spawning');
341
- }
342
-
343
- audit.emit('driver.spawn', { agent: 'codex', cwd, resumeSessionId });
344
- this.lastStderr = [];
345
-
346
- this.proc = spawn(spawnCmd, spawnArgs, {
347
- cwd: cwd || undefined,
348
- stdio: ['pipe', 'pipe', 'pipe'],
349
- env: config.buildSanitizedEnv(),
350
- ...(process.platform === 'win32' ? { shell: true } : {}),
351
- });
352
-
353
- this.proc.on('error', (err) => {
354
- let message = err.message;
355
- if ((err as NodeJS.ErrnoException).code === 'ENOENT' && cwd && !existsSync(cwd)) {
356
- message = `Working directory does not exist: ${cwd}`;
357
- }
358
- log.error({ error: message, cwd }, 'spawn error');
359
- audit.emit('driver.error', { agent: 'codex', error: message });
360
- });
361
-
362
- this.proc.stdout!.on('data', (chunk: Buffer) => {
363
- this.buffer += chunk.toString();
364
- const lines = this.buffer.split('\n');
365
- this.buffer = lines.pop()!;
366
- for (const line of lines) {
367
- if (!line.trim()) continue;
368
- try {
369
- this.handleRaw(JSON.parse(line));
370
- } catch {
371
- log.warn({ linePreview: line.slice(0, 200) }, 'failed to parse stdout line');
372
- }
373
- }
374
- });
375
-
376
- this.proc.stderr!.on('data', (chunk: Buffer) => {
377
- const text = chunk.toString().trim();
378
- if (text) {
379
- log.debug({ stderr: text }, 'stderr');
380
- this.lastStderr.push(text);
381
- if (this.lastStderr.length > 10) this.lastStderr.shift();
382
- }
383
- });
384
-
385
- this.proc.on('exit', (code) => {
386
- log.info({ threadId: this.threadId, exitCode: code }, 'process exited');
387
- if (code && code !== 0) {
388
- log.error({ threadId: this.threadId, exitCode: code, lastStderr: this.lastStderr.slice(-3) }, 'abnormal exit');
389
- }
390
- this.proc = null;
391
- this.approvalRequests.clear();
392
- this.toolContextByCallId.clear();
393
- this.turnStartInFlight = false;
394
- this.interruptRequestedDuringTurnStart = false;
395
- for (const [, { reject }] of this.pending) {
396
- reject(new Error(`Codex process exited with code ${code}`));
397
- }
398
- this.pending.clear();
399
- this.exitHandler?.(code);
400
- if (code && this.handler && this.threadId) {
401
- this.handler({ type: 'error', sessionId: this.threadId, message: `Codex process exited with code ${code}` });
402
- }
403
- });
404
-
405
- const endInit = metrics.startTimer('rpc.duration');
406
- await this.rpc('initialize', {
407
- clientInfo: { name: '@vibelet/cli', version: CLI_VERSION },
408
- capabilities: {
409
- experimentalApi: true,
410
- },
411
- });
412
- endInit();
413
- this.rpcNotify('initialized', {});
414
-
415
- const policy = resolveCodexPolicy(this.approvalMode, cwd);
416
-
417
- if (resumeSessionId) {
418
- const res = await this.rpc('thread/resume', {
419
- threadId: resumeSessionId,
420
- approvalPolicy: policy.approvalPolicy,
421
- sandbox: policy.sandbox,
422
- persistExtendedHistory: true,
423
- }) as any;
424
- this.threadId = res?.thread?.id ?? resumeSessionId;
425
- log.info({ threadId: this.threadId }, 'thread resumed');
426
- audit.emit('driver.init', { agent: 'codex', sessionId: this.threadId });
427
- } else {
428
- const res = await this.rpc('thread/start', {
429
- cwd,
430
- approvalPolicy: policy.approvalPolicy,
431
- sandbox: policy.sandbox,
432
- experimentalRawEvents: true,
433
- persistExtendedHistory: true,
434
- }) as any;
435
- this.threadId = res?.thread?.id ?? res?.threadId ?? '';
436
- log.info({ threadId: this.threadId }, 'thread created');
437
- audit.emit('driver.init', { agent: 'codex', sessionId: this.threadId });
438
- }
439
-
440
- return this.threadId;
441
- }
442
-
443
- private buildSpawnArgs(): string[] {
444
- return ['app-server', '--listen', 'stdio://'];
445
- }
446
-
447
- sendPrompt(text: string): void {
448
- const policy = resolveCodexPolicy(this.approvalMode, this.cwd || process.cwd());
449
- this.turnStartInFlight = true;
450
- this.interruptRequestedDuringTurnStart = false;
451
- log.info({ threadId: this.threadId, promptPreview: text.slice(0, 50) }, 'turn/start');
452
- this.rpc('turn/start', {
453
- threadId: this.threadId,
454
- input: [{ type: 'text', text }],
455
- approvalPolicy: policy.approvalPolicy,
456
- sandboxPolicy: policy.sandboxPolicy,
457
- })
458
- .then((res) => {
459
- log.debug({ resultPreview: JSON.stringify(res).slice(0, 200) }, 'turn/start result');
460
- this.turnStartInFlight = false;
461
- if (this.interruptRequestedDuringTurnStart) {
462
- this.interruptRequestedDuringTurnStart = false;
463
- this.requestTurnInterrupt();
464
- }
465
- })
466
- .catch((e) => {
467
- this.turnStartInFlight = false;
468
- this.interruptRequestedDuringTurnStart = false;
469
- log.error({ threadId: this.threadId, error: e.message }, 'sendPrompt error');
470
- this.handler?.({ type: 'error', sessionId: this.threadId, message: e.message });
471
- });
472
- }
473
-
474
- respondApproval(requestId: string, approved: boolean): boolean {
475
- if (!this.proc?.stdin?.writable) return false;
476
- const tracked = this.approvalRequests.get(requestId);
477
- const rpcId = tracked?.rpcId ?? Number(requestId);
478
- if (!Number.isFinite(rpcId)) return false;
479
-
480
- let result: Record<string, unknown>;
481
- if (!tracked) {
482
- result = approved
483
- ? { decision: 'approve' }
484
- : { decision: 'deny', reason: 'User denied from Vibelet' };
485
- } else {
486
- switch (tracked.kind) {
487
- case 'command-execution':
488
- case 'file-change':
489
- result = { decision: approved ? 'accept' : 'decline' };
490
- break;
491
- case 'request-user-input-approval':
492
- result = {
493
- answers: {
494
- [tracked.questionId]: {
495
- answers: [approved ? tracked.approveLabel : tracked.denyLabel],
496
- },
497
- },
498
- };
499
- break;
500
- case 'exec-command-legacy':
501
- case 'apply-patch-legacy':
502
- result = { decision: approved ? 'approved' : 'denied' };
503
- break;
504
- default:
505
- result = { decision: approved ? 'accept' : 'decline' };
506
- break;
507
- }
508
- this.approvalRequests.delete(requestId);
509
- }
510
-
511
- log.info({ threadId: this.threadId, rpcId, approved }, 'approval response');
512
- this.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: rpcId, result }) + '\n');
513
- return true;
514
- }
515
-
516
- restorePendingApproval(approval: ApprovalRequestPayload): void {
517
- const request = restoreApprovalRequest(approval);
518
- if (!request) {
519
- return;
520
- }
521
- this.approvalRequests.set(approval.requestId, request);
522
- }
523
-
524
- interrupt(): void {
525
- if (this.turnStartInFlight) {
526
- this.interruptRequestedDuringTurnStart = true;
527
- }
528
- this.requestTurnInterrupt();
529
- }
530
-
531
- setApprovalMode(mode: string): void {
532
- this.approvalMode = mode;
533
- log.info({ approvalMode: mode }, 'approval mode updated (takes effect on subsequent turns)');
534
- }
535
-
536
- stop(): void {
537
- if (!this.proc) return;
538
- this.proc.kill('SIGTERM');
539
- const p = this.proc;
540
- setTimeout(() => {
541
- if (!p.killed) p.kill('SIGKILL');
542
- }, 5000).unref();
543
- this.proc = null;
544
- this.approvalRequests.clear();
545
- this.toolContextByCallId.clear();
546
- this.turnStartInFlight = false;
547
- this.interruptRequestedDuringTurnStart = false;
548
- for (const [, { reject }] of this.pending) {
549
- reject(new Error('Process stopped'));
550
- }
551
- this.pending.clear();
552
- }
553
-
554
- onMessage(handler: MessageHandler): void {
555
- this.handler = handler;
556
- }
557
-
558
- onExit(handler: (code: number | null) => void): void {
559
- this.exitHandler = handler;
560
- }
561
-
562
- private rpc(method: string, params: unknown): Promise<unknown> {
563
- const id = ++this.rpcId;
564
- const endTimer = metrics.startTimer('rpc.duration');
565
- return new Promise((resolve, reject) => {
566
- this.pending.set(id, {
567
- resolve: (v) => { endTimer(); resolve(v); },
568
- reject: (e) => { endTimer(); reject(e); },
569
- });
570
- if (!this.proc?.stdin?.writable) {
571
- this.pending.delete(id);
572
- endTimer();
573
- reject(new Error('Process not available'));
574
- return;
575
- }
576
- this.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, id, params }) + '\n');
577
- setTimeout(() => {
578
- if (this.pending.has(id)) {
579
- this.pending.delete(id);
580
- log.error({ method, rpcId: id, threadId: this.threadId }, 'RPC timeout');
581
- endTimer();
582
- reject(new Error(`RPC timeout: ${method}`));
583
- }
584
- }, 30000).unref();
585
- });
586
- }
587
-
588
- private requestTurnInterrupt(): void {
589
- this.rpc('turn/interrupt', { threadId: this.threadId }).catch(() => {});
590
- }
591
-
592
- private rpcNotify(method: string, params: unknown): void {
593
- if (!this.proc?.stdin?.writable) return;
594
- this.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n');
595
- }
596
-
597
- private writeServerRequestError(rpcId: number, message: string): void {
598
- if (!this.proc?.stdin?.writable) return;
599
- this.proc.stdin.write(JSON.stringify({
600
- jsonrpc: '2.0',
601
- id: rpcId,
602
- error: { code: -32601, message },
603
- }) + '\n');
604
- }
605
-
606
- private resolveToolContext(params: Record<string, unknown> | undefined): {
607
- callId: string;
608
- toolContext: CodexToolContext;
609
- } | null {
610
- const item = asRecord(params?.item) ?? params ?? null;
611
- const callId = readItemId(item);
612
- if (!callId) return null;
613
-
614
- const extracted = item ? extractToolContext(item) : null;
615
- if (extracted) {
616
- this.toolContextByCallId.set(callId, extracted);
617
- return { callId, toolContext: extracted };
618
- }
619
-
620
- const remembered = this.toolContextByCallId.get(callId);
621
- if (!remembered) return null;
622
- return { callId, toolContext: remembered };
623
- }
624
-
625
- private createFallbackInput(params: Record<string, unknown> | undefined, excludedKeys: string[]): Record<string, unknown> {
626
- return params ? omitKeys(params, ['threadId', 'turnId', ...excludedKeys]) : {};
627
- }
628
-
629
- private queueApprovalRequest(requestId: string, request: CodexApprovalRequest, description: string): void {
630
- this.approvalRequests.set(requestId, request);
631
- audit.emit('approval.request', { agent: 'codex', sessionId: this.threadId, toolName: request.toolName });
632
-
633
- if (!this.handler) {
634
- log.warn({ threadId: this.threadId, requestId, toolName: request.toolName }, 'approval request received without handler');
635
- if (!this.respondApproval(requestId, false)) {
636
- this.writeServerRequestError(request.rpcId, 'Approval handler unavailable');
637
- }
638
- return;
639
- }
640
-
641
- this.handler({
642
- type: 'approval.request',
643
- sessionId: this.threadId,
644
- requestId,
645
- toolName: request.toolName,
646
- input: request.input,
647
- description,
648
- approvalContext: serializeApprovalContext(request),
649
- });
650
- }
651
-
652
- private handleServerRequest(method: string, rpcId: number, params: Record<string, unknown> | undefined): void {
653
- const requestId = String(rpcId);
654
- const resolved = this.resolveToolContext(params);
655
-
656
- if (method === 'item/commandExecution/requestApproval') {
657
- this.queueApprovalRequest(requestId, {
658
- kind: 'command-execution',
659
- responseKind: 'v2',
660
- rpcId,
661
- toolName: resolved?.toolContext.toolName ?? 'Bash',
662
- input: resolved?.toolContext.input ?? this.createFallbackInput(params, ['itemId', 'reason', 'description']),
663
- }, readString(params?.reason) ?? readString(params?.description) ?? '');
664
- return;
665
- }
666
-
667
- if (method === 'item/fileChange/requestApproval') {
668
- this.queueApprovalRequest(requestId, {
669
- kind: 'file-change',
670
- responseKind: 'v2',
671
- rpcId,
672
- toolName: resolved?.toolContext.toolName ?? 'Patch',
673
- input: resolved?.toolContext.input ?? this.createFallbackInput(params, ['itemId', 'reason', 'description']),
674
- }, readString(params?.reason) ?? readString(params?.description) ?? '');
675
- return;
676
- }
677
-
678
- if (method === 'item/tool/requestUserInput') {
679
- const questions = params?.questions;
680
- if (!looksLikeApprovalRequestUserInput(questions)) {
681
- log.warn({ threadId: this.threadId, rpcId }, 'unsupported requestUserInput request');
682
- this.writeServerRequestError(rpcId, 'Unsupported interactive request from Codex');
683
- return;
684
- }
685
-
686
- const question = resolveApprovalQuestion(questions);
687
- if (!question) {
688
- this.writeServerRequestError(rpcId, 'Malformed requestUserInput approval request');
689
- return;
690
- }
691
-
692
- this.queueApprovalRequest(requestId, {
693
- kind: 'request-user-input-approval',
694
- rpcId,
695
- questionId: question.questionId,
696
- approveLabel: question.approveLabel,
697
- denyLabel: question.denyLabel,
698
- toolName: resolved?.toolContext.toolName ?? 'Bash',
699
- input: resolved?.toolContext.input ?? this.createFallbackInput(params, ['itemId', 'questions']),
700
- }, readRequestUserInputDescription(questions));
701
- return;
702
- }
703
-
704
- if (method === 'execCommandApproval') {
705
- this.queueApprovalRequest(requestId, {
706
- kind: 'exec-command-legacy',
707
- rpcId,
708
- toolName: resolved?.toolContext.toolName ?? 'Bash',
709
- input: resolved?.toolContext.input ?? this.createFallbackInput(params, []),
710
- }, readString(params?.reason) ?? readString(params?.description) ?? '');
711
- return;
712
- }
713
-
714
- if (method === 'applyPatchApproval') {
715
- this.queueApprovalRequest(requestId, {
716
- kind: 'apply-patch-legacy',
717
- rpcId,
718
- toolName: resolved?.toolContext.toolName ?? 'Patch',
719
- input: resolved?.toolContext.input ?? this.createFallbackInput(params, []),
720
- }, readString(params?.reason) ?? readString(params?.description) ?? '');
721
- return;
722
- }
723
-
724
- if (method === 'item/permissions/requestApproval') {
725
- log.warn({ threadId: this.threadId, rpcId }, 'unsupported permissions approval request');
726
- this.writeServerRequestError(rpcId, 'Unsupported permissions approval request');
727
- return;
728
- }
729
-
730
- log.warn({ threadId: this.threadId, rpcId, method }, 'unsupported server request');
731
- this.writeServerRequestError(rpcId, `Unsupported server request: ${method}`);
732
- }
733
-
734
- private handleRaw(msg: Record<string, unknown>): void {
735
- log.debug({ method: msg.method ?? '(response)', rpcId: msg.id ?? '-' }, 'handleRaw');
736
-
737
- if (msg.id != null && !msg.method && this.pending.has(msg.id as number)) {
738
- const p = this.pending.get(msg.id as number)!;
739
- this.pending.delete(msg.id as number);
740
- if (msg.error) {
741
- p.reject(new Error((msg.error as any).message ?? 'RPC error'));
742
- } else {
743
- p.resolve(msg.result);
744
- }
745
- return;
746
- }
747
-
748
- const method = typeof msg.method === 'string' ? msg.method : undefined;
749
- const params = asRecord(msg.params);
750
-
751
- if (method && msg.id != null) {
752
- this.handleServerRequest(method, msg.id as number, params ?? undefined);
753
- return;
754
- }
755
-
756
- if (method) {
757
- log.debug({ method, paramsPreview: JSON.stringify(params ?? {}).slice(0, 300) }, 'notification');
758
- }
759
-
760
- switch (method) {
761
- case 'item/agentMessage/delta': {
762
- const deltaText = (params?.text ?? params?.delta ?? params?.content ?? '') as string;
763
- if (deltaText && this.handler) {
764
- this.handler({
765
- type: 'text.delta',
766
- sessionId: this.threadId,
767
- content: deltaText,
768
- });
769
- } else if (!deltaText) {
770
- log.debug({ paramsKeys: Object.keys(params ?? {}) }, 'agentMessage/delta with empty text');
771
- }
772
- break;
773
- }
774
-
775
- case 'item/commandExecution/outputDelta':
776
- if (params?.output && this.handler) {
777
- this.handler({
778
- type: 'tool.result',
779
- sessionId: this.threadId,
780
- toolCallId: (params.itemId as string) ?? '',
781
- output: String(params.output),
782
- });
783
- }
784
- break;
785
-
786
- case 'item/started': {
787
- const item = asRecord(params?.item);
788
- const callId = readItemId(item);
789
- const toolContext = item ? extractToolContext(item) : null;
790
- if (callId && toolContext) {
791
- this.toolContextByCallId.set(callId, toolContext);
792
- if (this.handler) {
793
- this.handler({
794
- type: 'tool.call',
795
- sessionId: this.threadId,
796
- toolName: toolContext.toolName,
797
- input: toolContext.input,
798
- toolCallId: callId,
799
- });
800
- }
801
- }
802
- break;
803
- }
804
-
805
- case 'item/completed': {
806
- const item = asRecord(params?.item) ?? params;
807
- const callId = readItemId(item);
808
- if (callId) this.toolContextByCallId.delete(callId);
809
- break;
810
- }
811
-
812
- case 'turn/aborted': {
813
- this.turnStartInFlight = false;
814
- this.interruptRequestedDuringTurnStart = false;
815
- const reason = readString(params?.reason) ?? readString(asRecord(params?.turn)?.reason);
816
- log.info({ threadId: this.threadId, reason }, 'turn interrupted');
817
- audit.emit('session.interrupted', {
818
- agent: 'codex',
819
- sessionId: this.threadId,
820
- ...(reason ? { reason } : {}),
821
- });
822
- if (this.handler) {
823
- this.handler({
824
- type: 'session.interrupted',
825
- sessionId: this.threadId,
826
- });
827
- }
828
- break;
829
- }
830
-
831
- case 'turn/completed': {
832
- this.turnStartInFlight = false;
833
- this.interruptRequestedDuringTurnStart = false;
834
- const turn = asRecord(params?.turn);
835
- const turnStatus = normalizeTurnStatus(turn?.status);
836
- const rawUsage = asRecord(params?.usage);
837
- const usage = rawUsage
838
- ? {
839
- inputTokens: Number(rawUsage.input_tokens ?? rawUsage.inputTokens ?? 0),
840
- outputTokens: Number(rawUsage.output_tokens ?? rawUsage.outputTokens ?? 0),
841
- }
842
- : undefined;
843
- if (turnStatus && INTERRUPTED_TURN_STATUSES.has(turnStatus)) {
844
- log.info({ threadId: this.threadId, status: turnStatus }, 'turn interrupted');
845
- audit.emit('session.interrupted', {
846
- agent: 'codex',
847
- sessionId: this.threadId,
848
- status: turnStatus,
849
- ...(usage ? { usage } : {}),
850
- });
851
- if (this.handler) {
852
- this.handler({
853
- type: 'session.interrupted',
854
- sessionId: this.threadId,
855
- });
856
- }
857
- break;
858
- }
859
-
860
- log.info({ threadId: this.threadId, status: turnStatus ?? 'completed' }, 'turn completed');
861
- audit.emit('session.done', { agent: 'codex', sessionId: this.threadId, usage });
862
- if (this.handler) {
863
- this.handler({
864
- type: 'session.done',
865
- sessionId: this.threadId,
866
- usage,
867
- });
868
- }
869
- break;
870
- }
871
-
872
- default:
873
- if (method) {
874
- log.debug({ method }, 'unhandled notification');
875
- }
876
- break;
877
- }
878
- }
879
- }