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