@undefineds.co/linx 0.2.19 → 0.2.22

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 (125) hide show
  1. package/README.md +23 -21
  2. package/dist/index.js +93 -142
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/ai-command.js +34 -30
  5. package/dist/lib/ai-command.js.map +1 -1
  6. package/dist/lib/auto-mode/archive.js +235 -0
  7. package/dist/lib/auto-mode/archive.js.map +1 -0
  8. package/dist/lib/{watch → auto-mode}/auth.js +6 -6
  9. package/dist/lib/auto-mode/auth.js.map +1 -0
  10. package/dist/lib/{watch → auto-mode}/codex-composer.js +1 -1
  11. package/dist/lib/auto-mode/codex-composer.js.map +1 -0
  12. package/dist/lib/auto-mode/codex-footer.js.map +1 -0
  13. package/dist/lib/auto-mode/codex-overlay.js.map +1 -0
  14. package/dist/lib/{watch → auto-mode}/codex-request-form.js +2 -2
  15. package/dist/lib/auto-mode/codex-request-form.js.map +1 -0
  16. package/dist/lib/auto-mode/codex-request-input.js.map +1 -0
  17. package/dist/lib/{watch → auto-mode}/display.js +63 -57
  18. package/dist/lib/auto-mode/display.js.map +1 -0
  19. package/dist/lib/{watch → auto-mode}/format.js +24 -16
  20. package/dist/lib/auto-mode/format.js.map +1 -0
  21. package/dist/lib/{watch → auto-mode}/hooks/claude.js +3 -2
  22. package/dist/lib/auto-mode/hooks/claude.js.map +1 -0
  23. package/dist/lib/{watch → auto-mode}/hooks/codebuddy.js +3 -2
  24. package/dist/lib/auto-mode/hooks/codebuddy.js.map +1 -0
  25. package/dist/lib/auto-mode/hooks/codex.js +18 -0
  26. package/dist/lib/auto-mode/hooks/codex.js.map +1 -0
  27. package/dist/lib/auto-mode/hooks/index.js +27 -0
  28. package/dist/lib/auto-mode/hooks/index.js.map +1 -0
  29. package/dist/lib/auto-mode/hooks/shared.js.map +1 -0
  30. package/dist/lib/auto-mode/index.js.map +1 -0
  31. package/dist/lib/auto-mode/pod-ai.js +116 -0
  32. package/dist/lib/auto-mode/pod-ai.js.map +1 -0
  33. package/dist/lib/{watch → auto-mode}/pod-approval.js +495 -325
  34. package/dist/lib/auto-mode/pod-approval.js.map +1 -0
  35. package/dist/lib/auto-mode/pod-persistence.js +412 -0
  36. package/dist/lib/auto-mode/pod-persistence.js.map +1 -0
  37. package/dist/lib/{watch → auto-mode}/runner.js +280 -193
  38. package/dist/lib/auto-mode/runner.js.map +1 -0
  39. package/dist/lib/{watch → auto-mode}/secretary.js +8 -8
  40. package/dist/lib/auto-mode/secretary.js.map +1 -0
  41. package/dist/lib/{watch → auto-mode}/types.js.map +1 -1
  42. package/dist/lib/auto-mode-command.js +114 -0
  43. package/dist/lib/auto-mode-command.js.map +1 -0
  44. package/dist/lib/codex-plugin/bridge.js +11 -12
  45. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  46. package/dist/lib/codex-plugin/codex-native-proxy.js +11 -12
  47. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  48. package/dist/lib/linx-tui-contract.js +26 -0
  49. package/dist/lib/linx-tui-contract.js.map +1 -0
  50. package/dist/lib/login-command.js +9 -3
  51. package/dist/lib/login-command.js.map +1 -1
  52. package/dist/lib/models.js +2 -2
  53. package/dist/lib/models.js.map +1 -1
  54. package/dist/lib/oidc-auth.js +83 -104
  55. package/dist/lib/oidc-auth.js.map +1 -1
  56. package/dist/lib/oidc-session-storage.js +7 -1
  57. package/dist/lib/oidc-session-storage.js.map +1 -1
  58. package/dist/lib/pi-adapter/auth.js +9 -15
  59. package/dist/lib/pi-adapter/auth.js.map +1 -1
  60. package/dist/lib/pi-adapter/branding.js +14 -44
  61. package/dist/lib/pi-adapter/branding.js.map +1 -1
  62. package/dist/lib/pi-adapter/interactive.js +19 -23
  63. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  64. package/dist/lib/pi-adapter/pod-approval.js +239 -1
  65. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  66. package/dist/lib/pi-adapter/pod-mirror.js +64 -59
  67. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  68. package/dist/lib/pi-adapter/pod-native.js +479 -0
  69. package/dist/lib/pi-adapter/pod-native.js.map +1 -0
  70. package/dist/lib/pi-adapter/pod-tools.js +76 -40
  71. package/dist/lib/pi-adapter/pod-tools.js.map +1 -1
  72. package/dist/lib/pi-adapter/runtime.js +11 -66
  73. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  74. package/dist/lib/pi-adapter/session.js +276 -33
  75. package/dist/lib/pi-adapter/session.js.map +1 -1
  76. package/dist/lib/pi-adapter/stream.js.map +1 -1
  77. package/dist/lib/pi-adapter/web-fetch.js +47 -30
  78. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  79. package/dist/lib/pod-chat-store.js +36 -38
  80. package/dist/lib/pod-chat-store.js.map +1 -1
  81. package/dist/lib/pod-data-session.js +97 -30
  82. package/dist/lib/pod-data-session.js.map +1 -1
  83. package/package.json +3 -3
  84. package/vendor/agent-runtime/dist/auto-mode.d.ts +287 -0
  85. package/vendor/agent-runtime/dist/auto-mode.js +1498 -0
  86. package/vendor/agent-runtime/dist/index.d.ts +2 -0
  87. package/vendor/agent-runtime/dist/index.js +2 -0
  88. package/vendor/agent-runtime/dist/runtime.d.ts +47 -0
  89. package/vendor/agent-runtime/dist/runtime.js +36 -0
  90. package/vendor/agent-runtime/dist/turn-controller.d.ts +4 -4
  91. package/vendor/agent-runtime/dist/turn-controller.js +7 -7
  92. package/vendor/agent-runtime/package.json +2 -0
  93. package/dist/lib/watch/archive.js +0 -110
  94. package/dist/lib/watch/archive.js.map +0 -1
  95. package/dist/lib/watch/auth.js.map +0 -1
  96. package/dist/lib/watch/codex-composer.js.map +0 -1
  97. package/dist/lib/watch/codex-footer.js.map +0 -1
  98. package/dist/lib/watch/codex-overlay.js.map +0 -1
  99. package/dist/lib/watch/codex-request-form.js.map +0 -1
  100. package/dist/lib/watch/codex-request-input.js.map +0 -1
  101. package/dist/lib/watch/display.js.map +0 -1
  102. package/dist/lib/watch/format.js.map +0 -1
  103. package/dist/lib/watch/hooks/claude.js.map +0 -1
  104. package/dist/lib/watch/hooks/codebuddy.js.map +0 -1
  105. package/dist/lib/watch/hooks/codex.js +0 -17
  106. package/dist/lib/watch/hooks/codex.js.map +0 -1
  107. package/dist/lib/watch/hooks/index.js +0 -24
  108. package/dist/lib/watch/hooks/index.js.map +0 -1
  109. package/dist/lib/watch/hooks/shared.js.map +0 -1
  110. package/dist/lib/watch/index.js.map +0 -1
  111. package/dist/lib/watch/pod-ai.js +0 -160
  112. package/dist/lib/watch/pod-ai.js.map +0 -1
  113. package/dist/lib/watch/pod-approval.js.map +0 -1
  114. package/dist/lib/watch/pod-persistence.js +0 -334
  115. package/dist/lib/watch/pod-persistence.js.map +0 -1
  116. package/dist/lib/watch/runner.js.map +0 -1
  117. package/dist/lib/watch/secretary.js.map +0 -1
  118. package/dist/watch-cli.js +0 -116
  119. package/dist/watch-cli.js.map +0 -1
  120. /package/dist/lib/{watch → auto-mode}/codex-footer.js +0 -0
  121. /package/dist/lib/{watch → auto-mode}/codex-overlay.js +0 -0
  122. /package/dist/lib/{watch → auto-mode}/codex-request-input.js +0 -0
  123. /package/dist/lib/{watch → auto-mode}/hooks/shared.js +0 -0
  124. /package/dist/lib/{watch → auto-mode}/index.js +0 -0
  125. /package/dist/lib/{watch → auto-mode}/types.js +0 -0
@@ -0,0 +1,1498 @@
1
+ export function autoModeApprovalActionUri(request) {
2
+ if (request.kind === 'command-approval') {
3
+ return 'https://undefineds.co/ns#commandExecution';
4
+ }
5
+ if (request.kind === 'file-change-approval') {
6
+ return 'https://undefineds.co/ns#fileChange';
7
+ }
8
+ if (request.kind === 'permissions-approval') {
9
+ return 'https://undefineds.co/ns#permissionRequest';
10
+ }
11
+ return 'https://undefineds.co/ns#runtimeApproval';
12
+ }
13
+ export function autoModeApprovalToolName(request) {
14
+ if (request.kind === 'command-approval') {
15
+ return 'commandExecution';
16
+ }
17
+ if (request.kind === 'file-change-approval') {
18
+ return 'fileChange';
19
+ }
20
+ if (request.kind === 'permissions-approval') {
21
+ return 'permissionRequest';
22
+ }
23
+ return 'runtimeApproval';
24
+ }
25
+ export function autoModeApprovalRisk(request) {
26
+ if (request.kind === 'permissions-approval' || request.kind === 'file-change-approval') {
27
+ return 'high';
28
+ }
29
+ return 'medium';
30
+ }
31
+ export function autoModeApprovalRequestMessage(request) {
32
+ if (request.kind === 'command-approval') {
33
+ return request.command?.trim() || request.message;
34
+ }
35
+ if (request.kind === 'file-change-approval') {
36
+ return request.reason?.trim() || request.message;
37
+ }
38
+ return request.message;
39
+ }
40
+ export const DEFAULT_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 5_000;
41
+ export const MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 5_000;
42
+ export const MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 60_000;
43
+ export const AUTO_MODE_HOME_DIRNAME = 'auto-mode';
44
+ export const AUTO_MODE_SESSIONS_DIRNAME = 'sessions';
45
+ export const AUTO_MODE_SESSION_FILE_NAME = 'session.json';
46
+ export const AUTO_MODE_EVENTS_FILE_NAME = 'events.jsonl';
47
+ function fallbackRandomId() {
48
+ return Math.random().toString(36).slice(2, 10).padEnd(8, '0');
49
+ }
50
+ function extractAutoModeJsonText(value, depth = 0) {
51
+ if (depth > 4) {
52
+ return undefined;
53
+ }
54
+ if (typeof value === 'string') {
55
+ return value;
56
+ }
57
+ if (Array.isArray(value)) {
58
+ const parts = value
59
+ .map((item) => extractAutoModeJsonText(item, depth + 1))
60
+ .filter((item) => typeof item === 'string' && item.length > 0);
61
+ return parts.length > 0 ? parts.join('') : undefined;
62
+ }
63
+ if (!isRecord(value)) {
64
+ return undefined;
65
+ }
66
+ return firstNonEmpty([
67
+ extractAutoModeJsonText(value.text, depth + 1),
68
+ extractAutoModeJsonText(value.delta, depth + 1),
69
+ extractAutoModeJsonText(value.message, depth + 1),
70
+ extractAutoModeJsonText(value.content, depth + 1),
71
+ extractAutoModeJsonText(value.result, depth + 1),
72
+ extractAutoModeJsonText(value.summary, depth + 1),
73
+ extractAutoModeJsonText(value.error, depth + 1),
74
+ ]);
75
+ }
76
+ function extractAutoModeJsonArguments(value) {
77
+ if (isRecord(value)) {
78
+ return value;
79
+ }
80
+ if (typeof value !== 'string') {
81
+ return undefined;
82
+ }
83
+ try {
84
+ const parsed = JSON.parse(value);
85
+ return isRecord(parsed) ? parsed : undefined;
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ function normalizeAutoModeUserInputOption(value) {
92
+ if (!isRecord(value)) {
93
+ return null;
94
+ }
95
+ const label = firstNonEmpty([
96
+ typeof value.label === 'string' ? value.label : undefined,
97
+ typeof value.value === 'string' ? value.value : undefined,
98
+ ]);
99
+ if (!label) {
100
+ return null;
101
+ }
102
+ const description = firstNonEmpty([
103
+ typeof value.description === 'string' ? value.description : undefined,
104
+ typeof value.details === 'string' ? value.details : undefined,
105
+ ]);
106
+ return {
107
+ label,
108
+ ...(description ? { description } : {}),
109
+ };
110
+ }
111
+ export function normalizeAutoModeUserInputQuestion(value, fallbackId = 'question-1') {
112
+ if (!isRecord(value)) {
113
+ return null;
114
+ }
115
+ const header = firstNonEmpty([
116
+ typeof value.header === 'string' ? value.header : undefined,
117
+ typeof value.title === 'string' ? value.title : undefined,
118
+ typeof value.label === 'string' ? value.label : undefined,
119
+ ]) ?? 'Question';
120
+ const question = firstNonEmpty([
121
+ typeof value.question === 'string' ? value.question : undefined,
122
+ typeof value.prompt === 'string' ? value.prompt : undefined,
123
+ typeof value.message === 'string' ? value.message : undefined,
124
+ header,
125
+ ]) ?? header;
126
+ const options = Array.isArray(value.options)
127
+ ? value.options
128
+ .map((option) => normalizeAutoModeUserInputOption(option))
129
+ .filter((option) => option !== null)
130
+ : [];
131
+ return {
132
+ id: firstNonEmpty([typeof value.id === 'string' ? value.id : undefined, fallbackId]) ?? fallbackId,
133
+ header,
134
+ question,
135
+ options,
136
+ };
137
+ }
138
+ export function resolveAutoModeQuestionAnswer(question, answer) {
139
+ const normalized = answer.trim();
140
+ if (!normalized) {
141
+ return [];
142
+ }
143
+ if (question.options.length > 0 && /^\d+$/u.test(normalized)) {
144
+ const index = Number(normalized) - 1;
145
+ const option = question.options[index];
146
+ if (option?.label) {
147
+ return [option.label];
148
+ }
149
+ }
150
+ return [normalized];
151
+ }
152
+ function recordFromUnknown(value) {
153
+ return isRecord(value) ? value : null;
154
+ }
155
+ function extractAcpCommand(value) {
156
+ if (typeof value === 'string' && value.trim()) {
157
+ return value.trim();
158
+ }
159
+ const record = recordFromUnknown(value);
160
+ if (!record) {
161
+ return undefined;
162
+ }
163
+ const command = firstNonEmpty([
164
+ typeof record.command === 'string' ? record.command : undefined,
165
+ typeof record.cmd === 'string' ? record.cmd : undefined,
166
+ ]);
167
+ const args = Array.isArray(record.args)
168
+ ? record.args.filter((item) => typeof item === 'string' && item.trim().length > 0)
169
+ : [];
170
+ if (command && args.length > 0) {
171
+ return `${command} ${args.join(' ')}`;
172
+ }
173
+ return command ?? extractAutoModeJsonText(record);
174
+ }
175
+ function normalizeAcpPermissionOptions(value) {
176
+ if (!Array.isArray(value)) {
177
+ return [];
178
+ }
179
+ return value.filter((item) => isRecord(item));
180
+ }
181
+ export function normalizeAutoModeApprovalOptions(value) {
182
+ return normalizeAcpPermissionOptions(value)
183
+ .map((option) => {
184
+ const optionId = typeof option.optionId === 'string' && option.optionId.trim()
185
+ ? option.optionId.trim()
186
+ : undefined;
187
+ const label = firstNonEmpty([
188
+ typeof option.name === 'string' ? option.name : undefined,
189
+ typeof option.label === 'string' ? option.label : undefined,
190
+ optionId,
191
+ ]);
192
+ if (!optionId || !label) {
193
+ return null;
194
+ }
195
+ const kind = typeof option.kind === 'string' && option.kind.trim()
196
+ ? option.kind.trim()
197
+ : undefined;
198
+ const description = firstNonEmpty([
199
+ typeof option.description === 'string' ? option.description : undefined,
200
+ typeof option.detail === 'string' ? option.detail : undefined,
201
+ ]);
202
+ return {
203
+ optionId,
204
+ label,
205
+ ...(kind ? { kind } : {}),
206
+ ...(description ? { description } : {}),
207
+ };
208
+ })
209
+ .filter((option) => option !== null);
210
+ }
211
+ function normalizeDurationMs(value, unit) {
212
+ const numeric = typeof value === 'number'
213
+ ? value
214
+ : typeof value === 'string' && value.trim()
215
+ ? Number(value)
216
+ : Number.NaN;
217
+ if (!Number.isFinite(numeric) || numeric <= 0) {
218
+ return undefined;
219
+ }
220
+ const milliseconds = unit === 'ms'
221
+ ? numeric
222
+ : unit === 'seconds'
223
+ ? numeric * 1000
224
+ : numeric > 10_000
225
+ ? numeric
226
+ : numeric * 1000;
227
+ return Math.round(milliseconds);
228
+ }
229
+ function normalizeIsoDatetime(value) {
230
+ if (value instanceof Date) {
231
+ return Number.isFinite(value.getTime()) ? value.toISOString() : undefined;
232
+ }
233
+ if (typeof value === 'number' && Number.isFinite(value)) {
234
+ const date = new Date(value);
235
+ return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
236
+ }
237
+ if (typeof value !== 'string' || !value.trim()) {
238
+ return undefined;
239
+ }
240
+ const date = new Date(value);
241
+ return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
242
+ }
243
+ function extractAutoModeApprovalTimeoutMs(params, raw) {
244
+ const meta = isRecord(params._meta)
245
+ ? params._meta
246
+ : isRecord(raw._meta)
247
+ ? raw._meta
248
+ : {};
249
+ return normalizeDurationMs(params.timeoutMs ?? meta.timeoutMs, 'ms')
250
+ ?? normalizeDurationMs(params.timeoutMillis ?? params.timeoutMilliseconds ?? meta.timeoutMillis ?? meta.timeoutMilliseconds, 'ms')
251
+ ?? normalizeDurationMs(params.timeoutSeconds ?? params.timeoutSec ?? meta.timeoutSeconds ?? meta.timeoutSec, 'seconds')
252
+ ?? normalizeDurationMs(params.timeout ?? meta.timeout, 'auto');
253
+ }
254
+ function extractAutoModeApprovalExpiresAt(params, raw) {
255
+ const meta = isRecord(params._meta)
256
+ ? params._meta
257
+ : isRecord(raw._meta)
258
+ ? raw._meta
259
+ : {};
260
+ return normalizeIsoDatetime(params.expiresAt ?? params.deadline ?? params.expires ?? meta.expiresAt ?? meta.deadline ?? meta.expires);
261
+ }
262
+ function extractAutoModeApprovalMetadata(raw, params) {
263
+ const approvalOptions = normalizeAutoModeApprovalOptions(params.options);
264
+ const timeoutMs = extractAutoModeApprovalTimeoutMs(params, raw);
265
+ const expiresAt = extractAutoModeApprovalExpiresAt(params, raw);
266
+ return {
267
+ ...(approvalOptions.length > 0 ? { approvalOptions } : {}),
268
+ ...(timeoutMs ? { timeoutMs } : {}),
269
+ ...(expiresAt ? { expiresAt } : {}),
270
+ };
271
+ }
272
+ function selectAcpPermissionOption(options, decision) {
273
+ if (decision === 'cancel') {
274
+ return undefined;
275
+ }
276
+ const preferredKinds = decision === 'accept'
277
+ ? ['allow_once', 'allow_always']
278
+ : decision === 'accept_for_session'
279
+ ? ['allow_always', 'allow_once']
280
+ : ['reject_once', 'reject_always'];
281
+ for (const kind of preferredKinds) {
282
+ const match = options.find((option) => option.kind === kind && typeof option.optionId === 'string');
283
+ if (match && typeof match.optionId === 'string') {
284
+ return match.optionId;
285
+ }
286
+ }
287
+ const preferredNames = decision === 'decline'
288
+ ? ['reject', 'deny', 'decline', 'no']
289
+ : ['allow', 'approve', 'yes'];
290
+ for (const option of options) {
291
+ if (typeof option.optionId !== 'string' || typeof option.name !== 'string') {
292
+ continue;
293
+ }
294
+ const name = option.name.toLowerCase();
295
+ if (preferredNames.some((token) => name.includes(token))) {
296
+ return option.optionId;
297
+ }
298
+ }
299
+ const fallback = decision === 'decline'
300
+ ? options.find((option) => typeof option.optionId === 'string')
301
+ : options.find((option) => typeof option.optionId === 'string');
302
+ return typeof fallback?.optionId === 'string' ? fallback.optionId : undefined;
303
+ }
304
+ export function createAutoModeSessionId(options = {}) {
305
+ const now = options.now ?? new Date();
306
+ const randomId = (options.randomId?.trim() || globalThis.crypto?.randomUUID?.() || fallbackRandomId()).slice(0, 8);
307
+ const stamp = now.toISOString().replace(/[:.]/g, '-');
308
+ return `auto_${stamp}_${randomId}`;
309
+ }
310
+ export function normalizeAutoModeCredentialSource(_source) {
311
+ return 'cloud';
312
+ }
313
+ export function shouldAttemptCloudCredentialProbe(_requestedSource, _localAuthStatus) {
314
+ return true;
315
+ }
316
+ export function formatAutoModeAutoFallbackMessage(localMessage, detail) {
317
+ return `${localMessage} Cloud credential fallback unavailable: ${detail}`;
318
+ }
319
+ export function resolveAutoModeCredentialSourceResolution(input) {
320
+ const requestedSource = normalizeAutoModeCredentialSource(input.requestedSource);
321
+ const cloudCredentialProbe = input.cloudCredentialProbe;
322
+ if (cloudCredentialProbe?.status === 'available') {
323
+ return {
324
+ requestedSource,
325
+ resolvedSource: 'cloud',
326
+ authStatus: { state: 'authenticated' },
327
+ };
328
+ }
329
+ return {
330
+ requestedSource,
331
+ authStatus: { state: 'unauthenticated', message: cloudCredentialProbe?.message },
332
+ error: cloudCredentialProbe?.message ?? 'Cloud credential resolution unavailable.',
333
+ };
334
+ }
335
+ export function resolveAutoModeAutoApprovalDecision(input) {
336
+ const { mode, request } = input;
337
+ if (request.kind === 'command-approval') {
338
+ if (mode === 'auto') {
339
+ return 'accept_for_session';
340
+ }
341
+ if (mode === 'smart' && isTrustedAutoModeCommand(request.command)) {
342
+ return 'accept';
343
+ }
344
+ return null;
345
+ }
346
+ if (request.kind === 'file-change-approval') {
347
+ if (mode === 'auto') {
348
+ return 'accept_for_session';
349
+ }
350
+ if (mode === 'smart') {
351
+ return 'accept';
352
+ }
353
+ return null;
354
+ }
355
+ if (request.kind === 'permissions-approval') {
356
+ if (mode === 'auto') {
357
+ return 'accept_for_session';
358
+ }
359
+ return null;
360
+ }
361
+ return null;
362
+ }
363
+ export function createFallbackAutoModeSecretaryRecommendation(input) {
364
+ if (input.mode === 'manual' || input.request.kind === 'user-input') {
365
+ return null;
366
+ }
367
+ const decision = resolveAutoModeAutoApprovalDecision({
368
+ mode: input.mode,
369
+ request: input.request,
370
+ });
371
+ const secretaryDecision = decision === 'accept_for_session' ? 'accept' : decision;
372
+ if (!secretaryDecision) {
373
+ return null;
374
+ }
375
+ return {
376
+ kind: input.request.kind,
377
+ canAutoDecide: true,
378
+ decision: secretaryDecision,
379
+ confidence: input.mode === 'auto' ? 0.7 : 0.6,
380
+ reason: 'Matched local fallback policy while AI secretary was unavailable.',
381
+ reactionWindowMs: 0,
382
+ source: 'fallback',
383
+ };
384
+ }
385
+ export function parseAutoModeSecretaryRecommendation(text, options) {
386
+ const raw = parseJsonObjectFromText(text);
387
+ if (!raw) {
388
+ return null;
389
+ }
390
+ const canAutoDecide = booleanFromUnknown(raw.canAutoDecide
391
+ ?? raw.can_auto_decide
392
+ ?? raw.autoApprove
393
+ ?? raw.auto_approve
394
+ ?? raw.canAnswer
395
+ ?? raw.can_answer);
396
+ const confidence = normalizeConfidence(raw.confidence ?? raw.confidenceScore ?? raw.confidence_score);
397
+ const reason = stringFromUnknown(raw.reason ?? raw.rationale ?? raw.explanation);
398
+ const fallbackReactionWindowMs = options.defaultReactionWindowMs ?? DEFAULT_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS;
399
+ const reactionWindowMs = confidence !== undefined
400
+ ? computeAutoModeSecretaryReactionWindowMs(confidence, fallbackReactionWindowMs)
401
+ : normalizeReactionWindowMs(raw.reactionWindowMs
402
+ ?? raw.reaction_window_ms
403
+ ?? raw.reviewWindowMs
404
+ ?? raw.review_window_ms
405
+ ?? raw.autoDecisionDelayMs
406
+ ?? raw.auto_decision_delay_ms, fallbackReactionWindowMs);
407
+ if (options.request.kind === 'user-input') {
408
+ const answers = normalizeSecretaryUserInputAnswers(options.request.questions, raw.answers ?? raw.answer ?? raw.userInputAnswers ?? raw.user_input_answers);
409
+ return {
410
+ kind: 'user-input',
411
+ canAutoDecide: canAutoDecide === true && !!answers,
412
+ ...(confidence !== undefined ? { confidence } : {}),
413
+ ...(reason ? { reason } : {}),
414
+ ...(reactionWindowMs !== undefined ? { reactionWindowMs } : {}),
415
+ ...(answers ? { answers } : {}),
416
+ source: 'model',
417
+ };
418
+ }
419
+ const decision = normalizeSecretaryApprovalDecision(raw.decision ?? raw.recommendedDecision ?? raw.recommended_decision);
420
+ if (!decision) {
421
+ return {
422
+ kind: options.request.kind,
423
+ canAutoDecide: false,
424
+ ...(confidence !== undefined ? { confidence } : {}),
425
+ ...(reason ? { reason } : {}),
426
+ ...(reactionWindowMs !== undefined ? { reactionWindowMs } : {}),
427
+ source: 'model',
428
+ };
429
+ }
430
+ return {
431
+ kind: options.request.kind,
432
+ canAutoDecide: canAutoDecide === true,
433
+ decision,
434
+ ...(confidence !== undefined ? { confidence } : {}),
435
+ ...(reason ? { reason } : {}),
436
+ ...(reactionWindowMs !== undefined ? { reactionWindowMs } : {}),
437
+ source: 'model',
438
+ };
439
+ }
440
+ export function autoModeApprovalDecisionLabel(decision) {
441
+ if (decision === 'accept') {
442
+ return 'Allow once';
443
+ }
444
+ if (decision === 'accept_for_session') {
445
+ return 'Grant';
446
+ }
447
+ if (decision === 'decline') {
448
+ return 'Deny';
449
+ }
450
+ return 'Cancel';
451
+ }
452
+ export function autoModeUserInputAnswersSummary(answers) {
453
+ return Object.entries(answers)
454
+ .map(([key, value]) => `${key}: ${value.answers.join(', ')}`)
455
+ .join('; ');
456
+ }
457
+ export function parseAutoModeGrantCoverageDecision(text) {
458
+ const raw = parseJsonObjectFromText(text);
459
+ if (!raw) {
460
+ return null;
461
+ }
462
+ const covers = booleanFromUnknown(raw.covers ?? raw.covered ?? raw.applies ?? raw.allowed);
463
+ if (covers === undefined) {
464
+ return null;
465
+ }
466
+ const confidence = normalizeConfidence(raw.confidence ?? raw.confidenceScore ?? raw.confidence_score);
467
+ const reason = stringFromUnknown(raw.reason ?? raw.rationale ?? raw.explanation);
468
+ return {
469
+ covers,
470
+ ...(confidence !== undefined ? { confidence } : {}),
471
+ ...(reason ? { reason } : {}),
472
+ source: 'model',
473
+ };
474
+ }
475
+ function isRecord(value) {
476
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
477
+ }
478
+ function stringFromUnknown(value) {
479
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
480
+ }
481
+ function booleanFromUnknown(value) {
482
+ if (typeof value === 'boolean') {
483
+ return value;
484
+ }
485
+ if (typeof value === 'string') {
486
+ const normalized = value.trim().toLowerCase();
487
+ if (['true', 'yes', 'y', '1'].includes(normalized)) {
488
+ return true;
489
+ }
490
+ if (['false', 'no', 'n', '0'].includes(normalized)) {
491
+ return false;
492
+ }
493
+ }
494
+ return undefined;
495
+ }
496
+ function normalizeConfidence(value) {
497
+ if (typeof value === 'number' && Number.isFinite(value)) {
498
+ if (value > 1 && value <= 100) {
499
+ return Math.max(0, Math.min(1, value / 100));
500
+ }
501
+ return Math.max(0, Math.min(1, value));
502
+ }
503
+ if (typeof value === 'string' && value.trim()) {
504
+ const parsed = Number(value);
505
+ return Number.isFinite(parsed) ? normalizeConfidence(parsed) : undefined;
506
+ }
507
+ return undefined;
508
+ }
509
+ function normalizeReactionWindowMs(value, fallback) {
510
+ const parsed = normalizeDurationMs(value ?? fallback, 'ms');
511
+ if (parsed === undefined) {
512
+ return undefined;
513
+ }
514
+ return Math.max(0, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, parsed));
515
+ }
516
+ export function computeAutoModeSecretaryReactionWindowMs(confidence, fallback = DEFAULT_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS) {
517
+ if (typeof confidence !== 'number' || !Number.isFinite(confidence)) {
518
+ return Math.max(0, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, fallback));
519
+ }
520
+ const normalized = Math.max(0, Math.min(1, confidence));
521
+ const window = Math.round(MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS
522
+ + (1 - normalized) * (MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS - MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS));
523
+ return Math.max(MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, window));
524
+ }
525
+ function normalizeSecretaryApprovalDecision(value) {
526
+ if (typeof value !== 'string') {
527
+ return undefined;
528
+ }
529
+ const normalized = value.trim().toLowerCase().replace(/-/g, '_');
530
+ if (['accept', 'allow', 'allow_once', 'approve', 'yes', 'accept_for_session', 'allow_always', 'grant', 'session', 'approve_for_session'].includes(normalized)) {
531
+ return 'accept';
532
+ }
533
+ if (['decline', 'deny', 'reject', 'reject_once', 'reject_always', 'no'].includes(normalized)) {
534
+ return 'decline';
535
+ }
536
+ if (['cancel', 'abort'].includes(normalized)) {
537
+ return 'cancel';
538
+ }
539
+ return undefined;
540
+ }
541
+ function parseJsonObjectFromText(text) {
542
+ const trimmed = text.trim();
543
+ if (!trimmed) {
544
+ return null;
545
+ }
546
+ for (const candidate of [
547
+ trimmed,
548
+ extractFencedJson(trimmed),
549
+ extractBracedJson(trimmed),
550
+ ]) {
551
+ if (!candidate) {
552
+ continue;
553
+ }
554
+ try {
555
+ const parsed = JSON.parse(candidate);
556
+ return recordFromUnknown(parsed);
557
+ }
558
+ catch {
559
+ // Try the next extraction shape.
560
+ }
561
+ }
562
+ return null;
563
+ }
564
+ function extractFencedJson(text) {
565
+ const match = text.match(/```(?:json)?\s*([\s\S]*?)```/iu);
566
+ return match?.[1]?.trim() || null;
567
+ }
568
+ function extractBracedJson(text) {
569
+ const start = text.indexOf('{');
570
+ const end = text.lastIndexOf('}');
571
+ return start !== -1 && end > start ? text.slice(start, end + 1) : null;
572
+ }
573
+ function normalizeSecretaryUserInputAnswers(questions, rawAnswers) {
574
+ const source = recordFromUnknown(rawAnswers);
575
+ if (!source) {
576
+ return undefined;
577
+ }
578
+ const answers = {};
579
+ for (const question of questions) {
580
+ const raw = source[question.id] ?? source[question.header] ?? source[question.question];
581
+ const normalizedAnswers = normalizeSecretaryAnswerValues(raw);
582
+ if (normalizedAnswers.length > 0) {
583
+ answers[question.id] = { answers: normalizedAnswers };
584
+ }
585
+ }
586
+ return Object.keys(answers).length > 0 ? answers : undefined;
587
+ }
588
+ function normalizeSecretaryAnswerValues(value) {
589
+ const answerRecord = recordFromUnknown(value);
590
+ if (answerRecord) {
591
+ return normalizeSecretaryAnswerValues(answerRecord.answers ?? answerRecord.value ?? answerRecord.answer);
592
+ }
593
+ const values = Array.isArray(value) ? value : [value];
594
+ return values
595
+ .map((entry) => typeof entry === 'string' ? entry.trim() : '')
596
+ .filter(Boolean);
597
+ }
598
+ function firstNonEmpty(values) {
599
+ for (const value of values) {
600
+ if (typeof value === 'string' && value.trim()) {
601
+ return value.trim();
602
+ }
603
+ }
604
+ return undefined;
605
+ }
606
+ function normalizeAutoModeAuthText(value, depth = 0) {
607
+ if (depth > 5) {
608
+ return undefined;
609
+ }
610
+ if (typeof value === 'string') {
611
+ return value;
612
+ }
613
+ if (Array.isArray(value)) {
614
+ const parts = value
615
+ .map((item) => normalizeAutoModeAuthText(item, depth + 1))
616
+ .filter((item) => typeof item === 'string' && item.trim().length > 0);
617
+ return parts.length > 0 ? parts.join(' ') : undefined;
618
+ }
619
+ if (!isRecord(value)) {
620
+ return undefined;
621
+ }
622
+ return firstNonEmpty([
623
+ normalizeAutoModeAuthText(value.message, depth + 1),
624
+ normalizeAutoModeAuthText(value.error, depth + 1),
625
+ normalizeAutoModeAuthText(value.result, depth + 1),
626
+ normalizeAutoModeAuthText(value.text, depth + 1),
627
+ normalizeAutoModeAuthText(value.reason, depth + 1),
628
+ normalizeAutoModeAuthText(value.content, depth + 1),
629
+ normalizeAutoModeAuthText(value.summary, depth + 1),
630
+ ]);
631
+ }
632
+ export function getAutoModeAuthLoginCommand(backend) {
633
+ if (backend === 'claude') {
634
+ return 'claude auth login';
635
+ }
636
+ if (backend === 'codex') {
637
+ return 'codex login';
638
+ }
639
+ return null;
640
+ }
641
+ export function formatAutoModeBackendAuthMessage(backend, detail) {
642
+ const command = getAutoModeAuthLoginCommand(backend);
643
+ const label = backend === 'claude'
644
+ ? 'Claude Code'
645
+ : backend === 'codebuddy'
646
+ ? 'CodeBuddy Code'
647
+ : 'Codex';
648
+ if (backend === 'codebuddy') {
649
+ return detail
650
+ ? `${label} is not authenticated. Open \`codebuddy\` and complete login first. Native message: ${detail}`
651
+ : `${label} is not authenticated. Open \`codebuddy\` and complete login first.`;
652
+ }
653
+ return detail
654
+ ? `${label} is not authenticated. Run \`${command}\` and try again. Native message: ${detail}`
655
+ : `${label} is not authenticated. Run \`${command}\` and try again.`;
656
+ }
657
+ export function looksLikeAutoModeAuthFailureText(text) {
658
+ return [
659
+ /\bnot logged in\b/iu,
660
+ /\bauthentication_failed\b/iu,
661
+ /\bunauthenticated\b/iu,
662
+ /\bauthentication required\b/iu,
663
+ /\bplease run \/login\b/iu,
664
+ /\bplease sign in\b/iu,
665
+ /\bsign in first\b/iu,
666
+ /\blogin required\b/iu,
667
+ /\bunauthorized\b/iu,
668
+ /\binvalid api key\b/iu,
669
+ ].some((pattern) => pattern.test(text));
670
+ }
671
+ export function parseAutoModeClaudeAuthStatus(stdout) {
672
+ const payload = parseAutoModeJsonLine(stdout.trim());
673
+ if (!payload || typeof payload.loggedIn !== 'boolean') {
674
+ return { state: 'unknown' };
675
+ }
676
+ return payload.loggedIn
677
+ ? { state: 'authenticated' }
678
+ : { state: 'unauthenticated', message: formatAutoModeBackendAuthMessage('claude') };
679
+ }
680
+ export function detectAutoModeAuthFailure(backend, line) {
681
+ const trimmed = line.trim();
682
+ if (!trimmed) {
683
+ return null;
684
+ }
685
+ const payload = parseAutoModeJsonLine(trimmed);
686
+ if (payload) {
687
+ if (backend === 'claude' && payload.error === 'authentication_failed') {
688
+ const detail = normalizeAutoModeAuthText(payload) ?? 'authentication_failed';
689
+ return { message: formatAutoModeBackendAuthMessage(backend, detail) };
690
+ }
691
+ if (backend === 'claude' && payload.loggedIn === false) {
692
+ return { message: formatAutoModeBackendAuthMessage(backend) };
693
+ }
694
+ if (payload.error) {
695
+ const detail = normalizeAutoModeAuthText(payload.error) ?? normalizeAutoModeAuthText(payload);
696
+ if (detail && looksLikeAutoModeAuthFailureText(detail)) {
697
+ return { message: formatAutoModeBackendAuthMessage(backend, detail) };
698
+ }
699
+ }
700
+ if (payload.is_error === true) {
701
+ const detail = normalizeAutoModeAuthText(payload);
702
+ if (detail && looksLikeAutoModeAuthFailureText(detail)) {
703
+ return { message: formatAutoModeBackendAuthMessage(backend, detail) };
704
+ }
705
+ }
706
+ const detail = normalizeAutoModeAuthText(payload);
707
+ if (detail && looksLikeAutoModeAuthFailureText(detail)) {
708
+ return { message: formatAutoModeBackendAuthMessage(backend, detail) };
709
+ }
710
+ }
711
+ if (!looksLikeAutoModeAuthFailureText(trimmed)) {
712
+ return null;
713
+ }
714
+ return { message: formatAutoModeBackendAuthMessage(backend, trimmed) };
715
+ }
716
+ export function parseAutoModeJsonLine(line) {
717
+ try {
718
+ const parsed = JSON.parse(line);
719
+ return isRecord(parsed) ? parsed : null;
720
+ }
721
+ catch {
722
+ return null;
723
+ }
724
+ }
725
+ export function extractAutoModeSessionIdFromJsonLine(line) {
726
+ const json = parseAutoModeJsonLine(line);
727
+ if (!json) {
728
+ return undefined;
729
+ }
730
+ return firstNonEmpty([
731
+ typeof json.session_id === 'string' ? json.session_id : undefined,
732
+ typeof json.sessionId === 'string' ? json.sessionId : undefined,
733
+ isRecord(json.message) && typeof json.message.session_id === 'string' ? json.message.session_id : undefined,
734
+ isRecord(json.message) && typeof json.message.sessionId === 'string' ? json.message.sessionId : undefined,
735
+ ]);
736
+ }
737
+ export function isTrustedAutoModeCommand(command) {
738
+ if (!command) {
739
+ return false;
740
+ }
741
+ const normalized = command.trim();
742
+ const safePatterns = [
743
+ /^pwd(?:\s|$)/,
744
+ /^ls(?:\s|$)/,
745
+ /^cat(?:\s|$)/,
746
+ /^sed(?:\s|$)/,
747
+ /^head(?:\s|$)/,
748
+ /^tail(?:\s|$)/,
749
+ /^wc(?:\s|$)/,
750
+ /^sort(?:\s|$)/,
751
+ /^uniq(?:\s|$)/,
752
+ /^find(?:\s|$)/,
753
+ /^grep(?:\s|$)/,
754
+ /^rg(?:\s|$)/,
755
+ /^git status(?:\s|$)/,
756
+ /^git diff(?:\s|$)/,
757
+ /^git log(?:\s|$)/,
758
+ ];
759
+ return safePatterns.some((pattern) => pattern.test(normalized));
760
+ }
761
+ export function normalizeCodexAppServerInteractionRequest(message) {
762
+ const method = typeof message.method === 'string' ? message.method : '';
763
+ const params = (typeof message.params === 'object' && message.params !== null
764
+ ? message.params
765
+ : {});
766
+ const approvalMetadata = extractAutoModeApprovalMetadata(message, params);
767
+ if (method === 'item/commandExecution/requestApproval') {
768
+ const command = typeof params.command === 'string' ? params.command : undefined;
769
+ const cwd = typeof params.cwd === 'string' ? params.cwd : undefined;
770
+ return {
771
+ kind: 'command-approval',
772
+ message: command || 'Codex requests command approval',
773
+ command,
774
+ cwd,
775
+ ...approvalMetadata,
776
+ raw: message,
777
+ };
778
+ }
779
+ if (method === 'item/fileChange/requestApproval') {
780
+ const reason = typeof params.reason === 'string' ? params.reason : undefined;
781
+ return {
782
+ kind: 'file-change-approval',
783
+ message: reason && reason.trim() ? reason : 'Codex requests file-change approval',
784
+ ...(reason ? { reason } : {}),
785
+ ...approvalMetadata,
786
+ raw: message,
787
+ };
788
+ }
789
+ if (method === 'item/permissions/requestApproval') {
790
+ const reason = typeof params.reason === 'string' ? params.reason : undefined;
791
+ const permissions = isRecord(params.permissions) ? params.permissions : {};
792
+ return {
793
+ kind: 'permissions-approval',
794
+ message: reason && reason.trim() ? reason : 'Codex requests additional permissions',
795
+ permissions,
796
+ ...approvalMetadata,
797
+ raw: message,
798
+ };
799
+ }
800
+ if (method === 'item/tool/requestUserInput') {
801
+ const questions = Array.isArray(params.questions)
802
+ ? params.questions
803
+ .map((question, index) => normalizeAutoModeUserInputQuestion(question, `question-${index + 1}`))
804
+ .filter((question) => question !== null)
805
+ : [];
806
+ return {
807
+ kind: 'user-input',
808
+ message: 'Codex requests structured user input',
809
+ questions,
810
+ raw: message,
811
+ };
812
+ }
813
+ if (method === 'applyPatchApproval' || method === 'execCommandApproval') {
814
+ return {
815
+ kind: 'codex-approval',
816
+ message: 'Codex requests approval',
817
+ ...approvalMetadata,
818
+ raw: message,
819
+ };
820
+ }
821
+ return null;
822
+ }
823
+ export function resolveAutoModeInteractionAutoResponse(input) {
824
+ const { mode, request } = input;
825
+ if (request.kind === 'user-input' || request.kind === 'codex-approval') {
826
+ return null;
827
+ }
828
+ const decision = resolveAutoModeAutoApprovalDecision({
829
+ mode,
830
+ request,
831
+ });
832
+ if (!decision) {
833
+ return null;
834
+ }
835
+ return buildCodexApprovalResponse(request, decision);
836
+ }
837
+ export function buildCodexApprovalResponse(request, decision) {
838
+ if (request.kind === 'permissions-approval') {
839
+ if (decision === 'accept') {
840
+ return { permissions: request.permissions, scope: 'turn' };
841
+ }
842
+ if (decision === 'accept_for_session') {
843
+ return { permissions: request.permissions, scope: 'session' };
844
+ }
845
+ return { permissions: {}, scope: 'turn' };
846
+ }
847
+ if (request.kind === 'codex-approval') {
848
+ if (decision === 'accept') {
849
+ return { decision: 'approved' };
850
+ }
851
+ if (decision === 'accept_for_session') {
852
+ return { decision: 'approved_for_session' };
853
+ }
854
+ if (decision === 'cancel') {
855
+ return { decision: 'abort' };
856
+ }
857
+ return { decision: 'denied' };
858
+ }
859
+ if (decision === 'accept') {
860
+ return { decision: 'accept' };
861
+ }
862
+ if (decision === 'accept_for_session') {
863
+ return { decision: 'acceptForSession' };
864
+ }
865
+ if (decision === 'cancel') {
866
+ return { decision: 'cancel' };
867
+ }
868
+ return { decision: 'decline' };
869
+ }
870
+ export function buildCodexUserInputResponse(answers) {
871
+ return { answers };
872
+ }
873
+ export function buildAutoModeUserInputResponse(answers) {
874
+ return buildCodexUserInputResponse(answers);
875
+ }
876
+ export function normalizeAcpInteractionRequest(message) {
877
+ const method = typeof message.method === 'string' ? message.method.toLowerCase() : '';
878
+ const params = (recordFromUnknown(message.params) ?? {});
879
+ if (method === 'session/request_permission' || Array.isArray(params.options)) {
880
+ const approvalMetadata = extractAutoModeApprovalMetadata(message, params);
881
+ const toolCall = (recordFromUnknown(params.toolCall) ?? {});
882
+ const toolKind = typeof toolCall.kind === 'string' ? toolCall.kind : '';
883
+ const command = extractAcpCommand(toolCall.rawInput);
884
+ const cwd = firstNonEmpty([
885
+ typeof toolCall.cwd === 'string' ? toolCall.cwd : undefined,
886
+ recordFromUnknown(toolCall.rawInput) && typeof recordFromUnknown(toolCall.rawInput)?.cwd === 'string'
887
+ ? recordFromUnknown(toolCall.rawInput)?.cwd
888
+ : undefined,
889
+ ]);
890
+ const messageText = firstNonEmpty([
891
+ typeof toolCall.title === 'string' ? toolCall.title : undefined,
892
+ command,
893
+ extractAutoModeJsonText(toolCall),
894
+ method || undefined,
895
+ ]) ?? 'Approval required';
896
+ if (toolKind === 'execute' || command) {
897
+ return {
898
+ kind: 'command-approval',
899
+ message: command ?? messageText,
900
+ ...(command ? { command } : {}),
901
+ ...(cwd ? { cwd } : {}),
902
+ ...approvalMetadata,
903
+ raw: message,
904
+ };
905
+ }
906
+ if (toolKind === 'edit' || toolKind === 'delete' || toolKind === 'move') {
907
+ return {
908
+ kind: 'file-change-approval',
909
+ message: messageText,
910
+ reason: messageText,
911
+ ...approvalMetadata,
912
+ raw: message,
913
+ };
914
+ }
915
+ return {
916
+ kind: 'permissions-approval',
917
+ message: messageText,
918
+ permissions: recordFromUnknown(toolCall.rawInput) ?? {},
919
+ ...approvalMetadata,
920
+ raw: message,
921
+ };
922
+ }
923
+ const looksLikeInput = method.includes('request_input')
924
+ || method.includes('requestuserinput')
925
+ || method.includes('user_input')
926
+ || Array.isArray(params.questions);
927
+ if (!looksLikeInput) {
928
+ return null;
929
+ }
930
+ const questions = Array.isArray(params.questions)
931
+ ? params.questions
932
+ .map((question, index) => normalizeAutoModeUserInputQuestion(question, `question-${index + 1}`))
933
+ .filter((question) => question !== null)
934
+ : [];
935
+ return {
936
+ kind: 'user-input',
937
+ message: firstNonEmpty([
938
+ extractAutoModeJsonText(params.message),
939
+ extractAutoModeJsonText(params.prompt),
940
+ extractAutoModeJsonText(params.question),
941
+ questions[0]?.question,
942
+ ]) ?? 'Input required',
943
+ questions,
944
+ raw: message,
945
+ };
946
+ }
947
+ export function normalizeAcpRequest(message) {
948
+ const interaction = normalizeAcpInteractionRequest(message);
949
+ if (!interaction) {
950
+ return [];
951
+ }
952
+ if (interaction.kind === 'user-input') {
953
+ return [{
954
+ type: 'input.required',
955
+ message: interaction.message,
956
+ request: interaction,
957
+ raw: message,
958
+ }];
959
+ }
960
+ const events = [{
961
+ type: 'approval.required',
962
+ message: interaction.message,
963
+ request: interaction,
964
+ raw: message,
965
+ }];
966
+ if (interaction.kind === 'command-approval' && interaction.command) {
967
+ events.push({
968
+ type: 'tool.call',
969
+ name: 'commandExecution',
970
+ arguments: {
971
+ command: interaction.command,
972
+ cwd: interaction.cwd,
973
+ },
974
+ raw: message,
975
+ });
976
+ }
977
+ return events;
978
+ }
979
+ function normalizeAcpToolCallEvent(update, raw) {
980
+ const name = firstNonEmpty([
981
+ typeof update.title === 'string' ? update.title : undefined,
982
+ typeof update.kind === 'string' ? update.kind : undefined,
983
+ typeof update.toolCallId === 'string' ? update.toolCallId : undefined,
984
+ ]);
985
+ if (!name) {
986
+ return null;
987
+ }
988
+ const args = extractAutoModeJsonArguments(update.rawInput);
989
+ return {
990
+ type: 'tool.call',
991
+ name,
992
+ ...(args ? { arguments: args } : {}),
993
+ raw,
994
+ };
995
+ }
996
+ export function normalizeAcpSessionNotification(message) {
997
+ const method = typeof message.method === 'string' ? message.method : '';
998
+ const params = (recordFromUnknown(message.params) ?? {});
999
+ if (method !== 'session/update') {
1000
+ return [];
1001
+ }
1002
+ const update = (recordFromUnknown(params.update) ?? {});
1003
+ const updateType = firstNonEmpty([
1004
+ typeof update.sessionUpdate === 'string' ? update.sessionUpdate : undefined,
1005
+ typeof update.type === 'string' ? update.type : undefined,
1006
+ ])?.toLowerCase() ?? '';
1007
+ if (updateType === 'available_commands_update' || updateType === 'usage_update') {
1008
+ return [];
1009
+ }
1010
+ if (updateType === 'agent_message_chunk') {
1011
+ const text = extractAutoModeJsonText(update.content ?? update);
1012
+ return text ? [{ type: 'assistant.delta', text, raw: message }] : [];
1013
+ }
1014
+ if (updateType === 'agent_thought_chunk') {
1015
+ return [];
1016
+ }
1017
+ if (updateType === 'tool_call' || updateType === 'tool_call_update') {
1018
+ const toolEvent = normalizeAcpToolCallEvent(update, message);
1019
+ if (toolEvent) {
1020
+ return [toolEvent];
1021
+ }
1022
+ }
1023
+ const text = extractAutoModeJsonText(update);
1024
+ if (!text) {
1025
+ return [];
1026
+ }
1027
+ return [{
1028
+ type: 'session.note',
1029
+ message: text,
1030
+ raw: message,
1031
+ }];
1032
+ }
1033
+ export function buildAcpPermissionResponse(request, decision) {
1034
+ if (decision === 'cancel') {
1035
+ return {
1036
+ outcome: { outcome: 'cancelled' },
1037
+ };
1038
+ }
1039
+ const raw = recordFromUnknown(request.raw);
1040
+ const params = recordFromUnknown(raw?.params) ?? {};
1041
+ const optionId = selectAcpPermissionOption(normalizeAcpPermissionOptions(params.options), decision);
1042
+ if (!optionId) {
1043
+ return {
1044
+ outcome: { outcome: 'cancelled' },
1045
+ };
1046
+ }
1047
+ return {
1048
+ outcome: {
1049
+ outcome: 'selected',
1050
+ optionId,
1051
+ },
1052
+ };
1053
+ }
1054
+ function maybeAutoModeToolEvent(json, lowerType) {
1055
+ const toolName = firstNonEmpty([
1056
+ typeof json.toolName === 'string' ? json.toolName : undefined,
1057
+ typeof json.name === 'string' ? json.name : undefined,
1058
+ typeof json.tool === 'string' ? json.tool : undefined,
1059
+ isRecord(json.tool) && typeof json.tool.name === 'string' ? json.tool.name : undefined,
1060
+ typeof json.command === 'string' ? json.command : undefined,
1061
+ ]);
1062
+ const looksLikeTool = lowerType.includes('tool')
1063
+ || lowerType.includes('command')
1064
+ || lowerType.includes('function_call')
1065
+ || (toolName !== undefined && !lowerType.includes('approval'));
1066
+ if (!looksLikeTool || !toolName) {
1067
+ return null;
1068
+ }
1069
+ return {
1070
+ type: 'tool.call',
1071
+ name: toolName,
1072
+ arguments: extractAutoModeJsonArguments(json.arguments ?? json.args ?? json.input),
1073
+ raw: json,
1074
+ };
1075
+ }
1076
+ function maybeAutoModeInputEvent(json, lowerType) {
1077
+ const looksLikeInput = lowerType.includes('request_user_input')
1078
+ || lowerType.includes('user_input')
1079
+ || Array.isArray(json.questions);
1080
+ if (!looksLikeInput) {
1081
+ return null;
1082
+ }
1083
+ const questions = Array.isArray(json.questions)
1084
+ ? json.questions
1085
+ .map((question, index) => normalizeAutoModeUserInputQuestion(question, `question-${index + 1}`))
1086
+ .filter((question) => question !== null)
1087
+ : [];
1088
+ const message = firstNonEmpty([
1089
+ extractAutoModeJsonText(json.message),
1090
+ extractAutoModeJsonText(json.prompt),
1091
+ extractAutoModeJsonText(json.question),
1092
+ extractAutoModeJsonText(json.description),
1093
+ ]) || 'Input required';
1094
+ const request = {
1095
+ kind: 'user-input',
1096
+ message,
1097
+ questions,
1098
+ raw: json,
1099
+ };
1100
+ return {
1101
+ type: 'input.required',
1102
+ message,
1103
+ request,
1104
+ raw: json,
1105
+ };
1106
+ }
1107
+ function maybeAutoModeApprovalEvent(json, lowerType) {
1108
+ const looksLikeApproval = lowerType.includes('approval')
1109
+ || lowerType.includes('permission')
1110
+ || isRecord(json.permissions);
1111
+ if (!looksLikeApproval) {
1112
+ return null;
1113
+ }
1114
+ const message = firstNonEmpty([
1115
+ extractAutoModeJsonText(json.message),
1116
+ extractAutoModeJsonText(json.reason),
1117
+ extractAutoModeJsonText(json.prompt),
1118
+ extractAutoModeJsonText(json.question),
1119
+ extractAutoModeJsonText(json.description),
1120
+ lowerType || undefined,
1121
+ ]);
1122
+ return {
1123
+ type: 'approval.required',
1124
+ message: message || 'Approval required',
1125
+ ...(isRecord(json.permissions)
1126
+ ? {
1127
+ request: {
1128
+ kind: 'permissions-approval',
1129
+ message: message || 'Approval required',
1130
+ permissions: json.permissions,
1131
+ ...extractAutoModeApprovalMetadata(json, json),
1132
+ raw: json,
1133
+ },
1134
+ }
1135
+ : {}),
1136
+ raw: json,
1137
+ };
1138
+ }
1139
+ function maybeAutoModeAssistantDoneEvent(json, lowerType) {
1140
+ const isDone = lowerType.includes('done')
1141
+ || lowerType.includes('completed')
1142
+ || lowerType === 'result'
1143
+ || lowerType.endsWith('.result')
1144
+ || lowerType.endsWith('.done');
1145
+ if (!isDone) {
1146
+ return null;
1147
+ }
1148
+ return {
1149
+ type: 'assistant.done',
1150
+ text: extractAutoModeJsonText(json),
1151
+ raw: json,
1152
+ };
1153
+ }
1154
+ function maybeAutoModeAssistantDeltaEvent(json, lowerType) {
1155
+ const isDelta = lowerType.includes('delta')
1156
+ || lowerType.includes('assistant')
1157
+ || lowerType.includes('message')
1158
+ || lowerType.includes('content_block')
1159
+ || lowerType.includes('text');
1160
+ if (!isDelta) {
1161
+ return null;
1162
+ }
1163
+ const text = firstNonEmpty([
1164
+ extractAutoModeJsonText(json.delta),
1165
+ extractAutoModeJsonText(json.text),
1166
+ extractAutoModeJsonText(json.message),
1167
+ extractAutoModeJsonText(json.content),
1168
+ ]);
1169
+ if (!text) {
1170
+ return null;
1171
+ }
1172
+ return {
1173
+ type: 'assistant.delta',
1174
+ text,
1175
+ raw: json,
1176
+ };
1177
+ }
1178
+ export function parseAutoModeJsonProtocolLine(line) {
1179
+ const json = parseAutoModeJsonLine(line);
1180
+ if (!json) {
1181
+ return [];
1182
+ }
1183
+ const lowerType = typeof json.type === 'string' ? json.type.toLowerCase() : '';
1184
+ const events = [];
1185
+ const inputEvent = maybeAutoModeInputEvent(json, lowerType);
1186
+ if (inputEvent) {
1187
+ events.push(inputEvent);
1188
+ }
1189
+ const approvalEvent = maybeAutoModeApprovalEvent(json, lowerType);
1190
+ if (approvalEvent) {
1191
+ events.push(approvalEvent);
1192
+ }
1193
+ const toolEvent = maybeAutoModeToolEvent(json, lowerType);
1194
+ if (toolEvent) {
1195
+ events.push(toolEvent);
1196
+ }
1197
+ const doneEvent = maybeAutoModeAssistantDoneEvent(json, lowerType);
1198
+ if (doneEvent) {
1199
+ events.push(doneEvent);
1200
+ }
1201
+ else {
1202
+ const deltaEvent = maybeAutoModeAssistantDeltaEvent(json, lowerType);
1203
+ if (deltaEvent) {
1204
+ events.push(deltaEvent);
1205
+ }
1206
+ }
1207
+ if (events.length === 0) {
1208
+ const text = extractAutoModeJsonText(json);
1209
+ if (text) {
1210
+ events.push({
1211
+ type: 'session.note',
1212
+ message: text,
1213
+ raw: json,
1214
+ });
1215
+ }
1216
+ }
1217
+ return events;
1218
+ }
1219
+ function normalizeCodexThreadItem(item, raw) {
1220
+ const type = typeof item.type === 'string' ? item.type : '';
1221
+ if (type === 'userMessage') {
1222
+ const content = Array.isArray(item.content)
1223
+ ? item.content
1224
+ .map((part) => (typeof part === 'object' && part !== null ? part : null))
1225
+ .filter((part) => part !== null)
1226
+ .map((part) => (typeof part.text === 'string' ? part.text : ''))
1227
+ .filter((text) => text.length > 0)
1228
+ .join('')
1229
+ : '';
1230
+ return content
1231
+ ? [{
1232
+ type: 'session.note',
1233
+ message: `userMessage · ${content}`,
1234
+ raw,
1235
+ }]
1236
+ : [];
1237
+ }
1238
+ if (type === 'commandExecution') {
1239
+ return [{
1240
+ type: 'tool.call',
1241
+ name: 'commandExecution',
1242
+ arguments: {
1243
+ command: item.command,
1244
+ cwd: item.cwd,
1245
+ status: item.status,
1246
+ },
1247
+ raw,
1248
+ }];
1249
+ }
1250
+ if (type === 'fileChange') {
1251
+ return [{
1252
+ type: 'tool.call',
1253
+ name: 'fileChange',
1254
+ arguments: {
1255
+ status: item.status,
1256
+ },
1257
+ raw,
1258
+ }];
1259
+ }
1260
+ if (type === 'mcpToolCall' || type === 'dynamicToolCall') {
1261
+ return [{
1262
+ type: 'tool.call',
1263
+ name: typeof item.tool === 'string' ? item.tool : type,
1264
+ arguments: (typeof item.arguments === 'object' && item.arguments !== null ? item.arguments : undefined),
1265
+ raw,
1266
+ }];
1267
+ }
1268
+ return [];
1269
+ }
1270
+ export function normalizeCodexAppServerNotification(message) {
1271
+ const method = typeof message.method === 'string' ? message.method : '';
1272
+ const params = (typeof message.params === 'object' && message.params !== null
1273
+ ? message.params
1274
+ : {});
1275
+ if (method === 'thread/started') {
1276
+ return [{
1277
+ type: 'session.note',
1278
+ message: 'Thread started',
1279
+ raw: message,
1280
+ }];
1281
+ }
1282
+ if (method === 'thread/status/changed') {
1283
+ const status = (typeof params.status === 'object' && params.status !== null ? params.status : {});
1284
+ const statusType = typeof status.type === 'string' ? status.type : 'unknown';
1285
+ return [{
1286
+ type: 'session.note',
1287
+ message: `Thread status · ${statusType}`,
1288
+ raw: message,
1289
+ }];
1290
+ }
1291
+ if (method === 'turn/started') {
1292
+ return [{
1293
+ type: 'session.note',
1294
+ message: 'Turn started',
1295
+ raw: message,
1296
+ }];
1297
+ }
1298
+ if (method === 'item/agentMessage/delta' && typeof params.delta === 'string') {
1299
+ return [{ type: 'assistant.delta', text: params.delta, raw: message }];
1300
+ }
1301
+ if (method === 'turn/completed') {
1302
+ return [{ type: 'assistant.done', raw: message }];
1303
+ }
1304
+ if ((method === 'item/commandExecution/outputDelta' || method === 'item/reasoning/textDelta' || method === 'item/reasoning/summaryTextDelta')
1305
+ && typeof params.delta === 'string') {
1306
+ return [{ type: 'session.note', message: params.delta, raw: message }];
1307
+ }
1308
+ if (method === 'item/started' || method === 'item/completed') {
1309
+ const item = (typeof params.item === 'object' && params.item !== null ? params.item : {});
1310
+ return normalizeCodexThreadItem(item, message);
1311
+ }
1312
+ if (method === 'error') {
1313
+ return [{
1314
+ type: 'session.note',
1315
+ message: extractAutoModeJsonText(params.error) || 'Codex error',
1316
+ raw: message,
1317
+ }];
1318
+ }
1319
+ return [];
1320
+ }
1321
+ export function normalizeCodexAppServerRequest(message) {
1322
+ const method = typeof message.method === 'string' ? message.method : '';
1323
+ const params = (typeof message.params === 'object' && message.params !== null
1324
+ ? message.params
1325
+ : {});
1326
+ const interaction = normalizeCodexAppServerInteractionRequest(message);
1327
+ if (interaction?.kind === 'user-input') {
1328
+ return [{
1329
+ type: 'input.required',
1330
+ message: interaction.message,
1331
+ request: interaction,
1332
+ raw: message,
1333
+ }];
1334
+ }
1335
+ if (interaction) {
1336
+ const events = [{
1337
+ type: 'approval.required',
1338
+ message: interaction.message,
1339
+ request: interaction,
1340
+ raw: message,
1341
+ }];
1342
+ if (interaction.kind === 'command-approval' && interaction.command) {
1343
+ events.push({
1344
+ type: 'tool.call',
1345
+ name: 'commandExecution',
1346
+ arguments: {
1347
+ command: interaction.command,
1348
+ cwd: interaction.cwd,
1349
+ },
1350
+ raw: message,
1351
+ });
1352
+ }
1353
+ return events;
1354
+ }
1355
+ if (method === 'item/tool/call') {
1356
+ return [{
1357
+ type: 'tool.call',
1358
+ name: typeof params.tool === 'string' ? params.tool : 'dynamicToolCall',
1359
+ arguments: (typeof params.arguments === 'object' && params.arguments !== null ? params.arguments : undefined),
1360
+ raw: message,
1361
+ }];
1362
+ }
1363
+ return [];
1364
+ }
1365
+ export function getAutoModeArchiveRelativePaths(sessionId) {
1366
+ const normalizedId = sessionId.trim();
1367
+ const sessionDir = `${AUTO_MODE_SESSIONS_DIRNAME}/${normalizedId}`;
1368
+ return {
1369
+ sessionDir,
1370
+ sessionFile: `${sessionDir}/${AUTO_MODE_SESSION_FILE_NAME}`,
1371
+ eventsFile: `${sessionDir}/${AUTO_MODE_EVENTS_FILE_NAME}`,
1372
+ };
1373
+ }
1374
+ export function buildAutoModeThreadMetadata(record) {
1375
+ return {
1376
+ kind: 'auto-mode',
1377
+ delegatedTo: 'secretary',
1378
+ sessionId: record.id,
1379
+ backend: record.backend,
1380
+ runtime: record.runtime,
1381
+ transport: record.transport,
1382
+ mode: record.mode,
1383
+ cwd: record.cwd,
1384
+ model: record.model,
1385
+ credentialSource: record.credentialSource,
1386
+ resolvedCredentialSource: record.resolvedCredentialSource,
1387
+ approvalSource: record.approvalSource,
1388
+ status: record.status,
1389
+ backendSessionId: record.backendSessionId,
1390
+ };
1391
+ }
1392
+ function pushAutoModeTranscriptMessage(messages, role, source, content, createdAt) {
1393
+ const normalized = content?.replace(/\r/g, '').trimEnd();
1394
+ if (!normalized) {
1395
+ return;
1396
+ }
1397
+ messages.push({
1398
+ role,
1399
+ source,
1400
+ content: normalized,
1401
+ createdAt,
1402
+ });
1403
+ }
1404
+ function flushAutoModeAssistantMessage(messages, state, fallbackTimestamp) {
1405
+ if (!state.assistantText.trim()) {
1406
+ state.assistantText = '';
1407
+ state.assistantTimestamp = undefined;
1408
+ return;
1409
+ }
1410
+ pushAutoModeTranscriptMessage(messages, 'assistant', 'primary-agent', state.assistantText, state.assistantTimestamp ?? fallbackTimestamp);
1411
+ state.assistantText = '';
1412
+ state.assistantTimestamp = undefined;
1413
+ }
1414
+ function appendAutoModeTranscriptEvent(messages, state, entry, event) {
1415
+ if (event.type === 'assistant.delta') {
1416
+ if (!state.assistantTimestamp) {
1417
+ state.assistantTimestamp = entry.timestamp;
1418
+ }
1419
+ state.assistantText += event.text;
1420
+ return;
1421
+ }
1422
+ if (event.type === 'assistant.done') {
1423
+ if (event.text && !state.assistantText) {
1424
+ pushAutoModeTranscriptMessage(messages, 'assistant', 'primary-agent', event.text, entry.timestamp);
1425
+ return;
1426
+ }
1427
+ flushAutoModeAssistantMessage(messages, state, entry.timestamp);
1428
+ return;
1429
+ }
1430
+ flushAutoModeAssistantMessage(messages, state, entry.timestamp);
1431
+ if (event.type === 'tool.call') {
1432
+ pushAutoModeTranscriptMessage(messages, 'system', 'tool', `[tool] ${event.name}${event.arguments ? ` ${JSON.stringify(event.arguments)}` : ''}`, entry.timestamp);
1433
+ return;
1434
+ }
1435
+ if (event.type === 'approval.required') {
1436
+ pushAutoModeTranscriptMessage(messages, 'system', 'secretary', `[approval] ${event.message}`, entry.timestamp);
1437
+ return;
1438
+ }
1439
+ if (event.type === 'input.required') {
1440
+ pushAutoModeTranscriptMessage(messages, 'system', 'secretary', `[input] ${event.message}`, entry.timestamp);
1441
+ return;
1442
+ }
1443
+ pushAutoModeTranscriptMessage(messages, 'system', 'system', `[note] ${event.message}`, entry.timestamp);
1444
+ }
1445
+ function appendAutoModeTranscriptRawEntry(messages, entry) {
1446
+ const trimmed = entry.line.trim();
1447
+ if (!trimmed) {
1448
+ return;
1449
+ }
1450
+ try {
1451
+ const parsed = JSON.parse(trimmed);
1452
+ const type = typeof parsed.type === 'string' ? parsed.type : '';
1453
+ if (type === 'user.turn' && typeof parsed.text === 'string') {
1454
+ pushAutoModeTranscriptMessage(messages, 'user', 'user', parsed.text, entry.timestamp);
1455
+ return;
1456
+ }
1457
+ if (type === 'turn.start') {
1458
+ const command = typeof parsed.command === 'string' ? parsed.command : 'unknown';
1459
+ const args = Array.isArray(parsed.args)
1460
+ ? parsed.args.filter((value) => typeof value === 'string')
1461
+ : [];
1462
+ pushAutoModeTranscriptMessage(messages, 'system', 'system', `[turn] ${[command, ...args].join(' ').trim()}`, entry.timestamp);
1463
+ return;
1464
+ }
1465
+ if (type === 'credentials.resolve') {
1466
+ const requested = typeof parsed.requestedCredentialSource === 'string' ? parsed.requestedCredentialSource : 'auto';
1467
+ const resolved = typeof parsed.resolvedCredentialSource === 'string' ? parsed.resolvedCredentialSource : requested;
1468
+ pushAutoModeTranscriptMessage(messages, 'system', 'system', `[credentials] ${requested} -> ${resolved}`, entry.timestamp);
1469
+ return;
1470
+ }
1471
+ if (type === 'process.error' && typeof parsed.message === 'string') {
1472
+ pushAutoModeTranscriptMessage(messages, 'system', 'system', `[error] ${parsed.message}`, entry.timestamp);
1473
+ return;
1474
+ }
1475
+ }
1476
+ catch {
1477
+ // Keep original line when it is not structured JSON.
1478
+ }
1479
+ pushAutoModeTranscriptMessage(messages, 'system', entry.stream === 'stderr' ? 'system' : 'primary-agent', entry.stream === 'stderr' ? `stderr> ${trimmed}` : trimmed, entry.timestamp);
1480
+ }
1481
+ export function buildAutoModeTranscriptMessages(entries) {
1482
+ const messages = [];
1483
+ const state = {
1484
+ assistantText: '',
1485
+ };
1486
+ for (const entry of entries) {
1487
+ if (entry.events.length > 0) {
1488
+ for (const event of entry.events) {
1489
+ appendAutoModeTranscriptEvent(messages, state, entry, event);
1490
+ }
1491
+ continue;
1492
+ }
1493
+ flushAutoModeAssistantMessage(messages, state, entry.timestamp);
1494
+ appendAutoModeTranscriptRawEntry(messages, entry);
1495
+ }
1496
+ flushAutoModeAssistantMessage(messages, state, new Date().toISOString());
1497
+ return messages;
1498
+ }