@undefineds.co/linx 0.2.1

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 (100) hide show
  1. package/README.md +91 -0
  2. package/dist/index.js +578 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/lib/account-api.js +100 -0
  5. package/dist/lib/account-api.js.map +1 -0
  6. package/dist/lib/account-session.js +30 -0
  7. package/dist/lib/account-session.js.map +1 -0
  8. package/dist/lib/ai-command.js +411 -0
  9. package/dist/lib/ai-command.js.map +1 -0
  10. package/dist/lib/chat-api.js +122 -0
  11. package/dist/lib/chat-api.js.map +1 -0
  12. package/dist/lib/codex-plugin/bridge.js +109 -0
  13. package/dist/lib/codex-plugin/bridge.js.map +1 -0
  14. package/dist/lib/codex-plugin/codex-native-proxy.js +358 -0
  15. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -0
  16. package/dist/lib/codex-plugin/index.js +4 -0
  17. package/dist/lib/codex-plugin/index.js.map +1 -0
  18. package/dist/lib/codex-plugin/runner.js +38 -0
  19. package/dist/lib/codex-plugin/runner.js.map +1 -0
  20. package/dist/lib/credentials-store.js +74 -0
  21. package/dist/lib/credentials-store.js.map +1 -0
  22. package/dist/lib/default-model.js +8 -0
  23. package/dist/lib/default-model.js.map +1 -0
  24. package/dist/lib/login-command.js +51 -0
  25. package/dist/lib/login-command.js.map +1 -0
  26. package/dist/lib/models.js +6 -0
  27. package/dist/lib/models.js.map +1 -0
  28. package/dist/lib/oidc-auth.js +451 -0
  29. package/dist/lib/oidc-auth.js.map +1 -0
  30. package/dist/lib/oidc-session-storage.js +37 -0
  31. package/dist/lib/oidc-session-storage.js.map +1 -0
  32. package/dist/lib/pi-adapter/auth.js +38 -0
  33. package/dist/lib/pi-adapter/auth.js.map +1 -0
  34. package/dist/lib/pi-adapter/branding.js +239 -0
  35. package/dist/lib/pi-adapter/branding.js.map +1 -0
  36. package/dist/lib/pi-adapter/index.js +4 -0
  37. package/dist/lib/pi-adapter/index.js.map +1 -0
  38. package/dist/lib/pi-adapter/interactive.js +63 -0
  39. package/dist/lib/pi-adapter/interactive.js.map +1 -0
  40. package/dist/lib/pi-adapter/runtime.js +271 -0
  41. package/dist/lib/pi-adapter/runtime.js.map +1 -0
  42. package/dist/lib/pi-adapter/stream.js +314 -0
  43. package/dist/lib/pi-adapter/stream.js.map +1 -0
  44. package/dist/lib/pi-adapter/theme.js +84 -0
  45. package/dist/lib/pi-adapter/theme.js.map +1 -0
  46. package/dist/lib/pod-chat-store.js +200 -0
  47. package/dist/lib/pod-chat-store.js.map +1 -0
  48. package/dist/lib/profile-identity.js +116 -0
  49. package/dist/lib/profile-identity.js.map +1 -0
  50. package/dist/lib/prompt.js +82 -0
  51. package/dist/lib/prompt.js.map +1 -0
  52. package/dist/lib/runtime-target.js +12 -0
  53. package/dist/lib/runtime-target.js.map +1 -0
  54. package/dist/lib/solid-auth.js +55 -0
  55. package/dist/lib/solid-auth.js.map +1 -0
  56. package/dist/lib/thread-utils.js +12 -0
  57. package/dist/lib/thread-utils.js.map +1 -0
  58. package/dist/lib/watch/archive.js +110 -0
  59. package/dist/lib/watch/archive.js.map +1 -0
  60. package/dist/lib/watch/auth.js +56 -0
  61. package/dist/lib/watch/auth.js.map +1 -0
  62. package/dist/lib/watch/codex-composer.js +219 -0
  63. package/dist/lib/watch/codex-composer.js.map +1 -0
  64. package/dist/lib/watch/codex-footer.js +88 -0
  65. package/dist/lib/watch/codex-footer.js.map +1 -0
  66. package/dist/lib/watch/codex-overlay.js +154 -0
  67. package/dist/lib/watch/codex-overlay.js.map +1 -0
  68. package/dist/lib/watch/codex-request-form.js +341 -0
  69. package/dist/lib/watch/codex-request-form.js.map +1 -0
  70. package/dist/lib/watch/codex-request-input.js +187 -0
  71. package/dist/lib/watch/codex-request-input.js.map +1 -0
  72. package/dist/lib/watch/display.js +1514 -0
  73. package/dist/lib/watch/display.js.map +1 -0
  74. package/dist/lib/watch/format.js +161 -0
  75. package/dist/lib/watch/format.js.map +1 -0
  76. package/dist/lib/watch/hooks/claude.js +12 -0
  77. package/dist/lib/watch/hooks/claude.js.map +1 -0
  78. package/dist/lib/watch/hooks/codebuddy.js +18 -0
  79. package/dist/lib/watch/hooks/codebuddy.js.map +1 -0
  80. package/dist/lib/watch/hooks/codex.js +13 -0
  81. package/dist/lib/watch/hooks/codex.js.map +1 -0
  82. package/dist/lib/watch/hooks/index.js +24 -0
  83. package/dist/lib/watch/hooks/index.js.map +1 -0
  84. package/dist/lib/watch/hooks/shared.js +19 -0
  85. package/dist/lib/watch/hooks/shared.js.map +1 -0
  86. package/dist/lib/watch/index.js +10 -0
  87. package/dist/lib/watch/index.js.map +1 -0
  88. package/dist/lib/watch/pod-ai.js +168 -0
  89. package/dist/lib/watch/pod-ai.js.map +1 -0
  90. package/dist/lib/watch/pod-approval.js +578 -0
  91. package/dist/lib/watch/pod-approval.js.map +1 -0
  92. package/dist/lib/watch/pod-persistence.js +226 -0
  93. package/dist/lib/watch/pod-persistence.js.map +1 -0
  94. package/dist/lib/watch/runner.js +1124 -0
  95. package/dist/lib/watch/runner.js.map +1 -0
  96. package/dist/lib/watch/types.js +2 -0
  97. package/dist/lib/watch/types.js.map +1 -0
  98. package/dist/watch-cli.js +142 -0
  99. package/dist/watch-cli.js.map +1 -0
  100. package/package.json +38 -0
@@ -0,0 +1,1124 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+ import { buildAcpPermissionResponse, buildWatchUserInputResponse, normalizeAcpInteractionRequest, normalizeAcpRequest, normalizeAcpSessionNotification, normalizeWatchCredentialSource, parseWatchJsonLine, resolveWatchAutoApprovalDecision, resolveWatchCredentialSourceResolution, shouldAttemptCloudCredentialProbe, } from '@undefineds.co/models/watch';
4
+ import { appendWatchEvent, createWatchSession, finishWatchSession, loadWatchEvents, loadWatchSession, listWatchSessions, writeWatchSession, } from './archive.js';
5
+ import { detectWatchAuthFailure, preflightWatchAuth } from './auth.js';
6
+ import { createWatchDisplay } from './display.js';
7
+ import { formatWatchSessionSummary } from './format.js';
8
+ import { describeWatchMode, getWatchHook, listWatchHooks } from './hooks/index.js';
9
+ import { createRemoteWatchApproval, isRemoteApprovalAbortError, resolveRemoteWatchApproval, waitForRemoteWatchApproval, } from './pod-approval.js';
10
+ import { persistWatchConversationToPod } from './pod-persistence.js';
11
+ import { loadPodBackendCredential, podCredentialMissingMessage } from './pod-ai.js';
12
+ import { promptText } from '../prompt.js';
13
+ export const watchRuntime = {
14
+ promptText,
15
+ preflightWatchAuth,
16
+ loadPodBackendCredential,
17
+ createRemoteWatchApproval,
18
+ waitForRemoteWatchApproval,
19
+ resolveRemoteWatchApproval,
20
+ persistWatchConversationToPod,
21
+ };
22
+ function createLineSplitter(stream, onLine) {
23
+ let buffer = '';
24
+ return {
25
+ push(chunk) {
26
+ buffer += chunk;
27
+ let newlineIndex = buffer.indexOf('\n');
28
+ while (newlineIndex !== -1) {
29
+ const line = buffer.slice(0, newlineIndex).replace(/\r$/, '');
30
+ buffer = buffer.slice(newlineIndex + 1);
31
+ onLine(line, stream);
32
+ newlineIndex = buffer.indexOf('\n');
33
+ }
34
+ if (buffer.length > 16_384) {
35
+ onLine(buffer, stream);
36
+ buffer = '';
37
+ }
38
+ },
39
+ flush() {
40
+ if (!buffer) {
41
+ return;
42
+ }
43
+ onLine(buffer.replace(/\r$/, ''), stream);
44
+ buffer = '';
45
+ },
46
+ };
47
+ }
48
+ function appendEntry(record, stream, line, events) {
49
+ const entry = {
50
+ timestamp: new Date().toISOString(),
51
+ stream,
52
+ line,
53
+ events,
54
+ };
55
+ appendWatchEvent(record, entry);
56
+ }
57
+ function appendSessionNote(record, message, raw) {
58
+ appendEntry(record, 'system', JSON.stringify({
59
+ type: 'session.note',
60
+ message,
61
+ }), [{
62
+ type: 'session.note',
63
+ message,
64
+ raw,
65
+ }]);
66
+ }
67
+ function appendAndDisplaySessionNote(record, display, message, tone = 'note', raw) {
68
+ appendSessionNote(record, message, raw);
69
+ display.showActivity(message, tone);
70
+ }
71
+ async function promptApproval(display, message, allowSessionOption = true, signal) {
72
+ while (true) {
73
+ display.setPhase('approval', message);
74
+ const answer = (await display.chooseOption('Approval required', [`[approval] ${message}`], allowSessionOption
75
+ ? [
76
+ { label: 'Yes', value: 'y', shortcuts: ['y'] },
77
+ { label: 'Session', value: 's', shortcuts: ['s'] },
78
+ { label: 'No', value: 'n', shortcuts: ['n'] },
79
+ { label: 'Cancel', value: 'c', shortcuts: ['c'] },
80
+ ]
81
+ : [
82
+ { label: 'Yes', value: 'y', shortcuts: ['y'] },
83
+ { label: 'No', value: 'n', shortcuts: ['n'] },
84
+ { label: 'Cancel', value: 'c', shortcuts: ['c'] },
85
+ ], signal)).trim().toLowerCase();
86
+ if (answer === 'y' || answer === 'yes') {
87
+ display.setPhase('running', 'Continuing turn');
88
+ return 'accept';
89
+ }
90
+ if (allowSessionOption && (answer === 's' || answer === 'session')) {
91
+ display.setPhase('running', 'Continuing turn');
92
+ return 'accept_for_session';
93
+ }
94
+ if (answer === 'n' || answer === 'no') {
95
+ display.setPhase('running', 'Continuing turn');
96
+ return 'decline';
97
+ }
98
+ if (answer === 'c' || answer === 'cancel') {
99
+ display.setPhase('running', 'Continuing turn');
100
+ return 'cancel';
101
+ }
102
+ }
103
+ }
104
+ async function promptAuthContinue(display, lines) {
105
+ while (true) {
106
+ display.setPhase('question', 'Authentication required');
107
+ const answer = (await display.chooseOption('Authentication required', lines, [
108
+ { label: 'Continue', value: 'continue', shortcuts: ['c', 'y'] },
109
+ { label: 'Cancel', value: 'cancel', shortcuts: ['n', 'x'] },
110
+ ])).trim().toLowerCase();
111
+ if (answer === 'continue' || answer === 'c' || answer === 'y' || answer === 'yes') {
112
+ display.setPhase('running', 'Continuing turn');
113
+ return true;
114
+ }
115
+ if (answer === 'cancel' || answer === 'n' || answer === 'x' || answer === 'no') {
116
+ display.setPhase('running', 'Continuing turn');
117
+ return false;
118
+ }
119
+ }
120
+ }
121
+ function approvalPromptMessage(request) {
122
+ if (request.kind === 'command-approval') {
123
+ return request.command ? `Approve command: ${request.command}` : 'Approve command execution';
124
+ }
125
+ if (request.kind === 'file-change-approval') {
126
+ return request.reason && request.reason.trim() ? request.reason : 'Approve file changes';
127
+ }
128
+ if (request.kind === 'permissions-approval') {
129
+ return request.message || 'Approve additional permissions';
130
+ }
131
+ return request.message || 'Approval required';
132
+ }
133
+ function appendUserTurn(record, text) {
134
+ appendEntry(record, 'system', JSON.stringify({ type: 'user.turn', text }), []);
135
+ }
136
+ function appendTurnStart(record, command, args) {
137
+ appendEntry(record, 'system', JSON.stringify({ type: 'turn.start', command, args }), []);
138
+ }
139
+ function requestedCredentialSource(options) {
140
+ return normalizeWatchCredentialSource(options.credentialSource);
141
+ }
142
+ function requestedApprovalSource(options) {
143
+ return options.approvalSource ?? 'hybrid';
144
+ }
145
+ function requestedRuntime(options) {
146
+ return options.runtime ?? 'local';
147
+ }
148
+ function normalizeBackendCommandEnv(backend, env) {
149
+ if (!env) {
150
+ return undefined;
151
+ }
152
+ const next = { ...env };
153
+ if (backend === 'codex' && next.OPENAI_API_KEY && !next.CODEX_API_KEY) {
154
+ next.CODEX_API_KEY = next.OPENAI_API_KEY;
155
+ }
156
+ return next;
157
+ }
158
+ function mergeCommandEnv(backend, commandEnv, planEnv) {
159
+ const normalizedCommandEnv = normalizeBackendCommandEnv(backend, commandEnv);
160
+ if (!normalizedCommandEnv && !planEnv) {
161
+ return undefined;
162
+ }
163
+ return {
164
+ ...(normalizedCommandEnv ?? {}),
165
+ ...(planEnv ?? {}),
166
+ };
167
+ }
168
+ function syncRecordFromOptions(record, options, plan) {
169
+ return {
170
+ backend: options.backend,
171
+ runtime: requestedRuntime(options),
172
+ mode: options.mode,
173
+ cwd: options.cwd,
174
+ model: options.model,
175
+ prompt: options.prompt,
176
+ passthroughArgs: [...options.passthroughArgs],
177
+ credentialSource: requestedCredentialSource(options),
178
+ resolvedCredentialSource: options.resolvedCredentialSource,
179
+ approvalSource: requestedApprovalSource(options),
180
+ command: plan.command,
181
+ args: [...plan.args],
182
+ transport: options.transport ?? 'acp',
183
+ };
184
+ }
185
+ function withResolvedSource(options, resolvedCredentialSource, commandEnv) {
186
+ return {
187
+ ...options,
188
+ transport: options.transport ?? 'acp',
189
+ credentialSource: requestedCredentialSource(options),
190
+ approvalSource: requestedApprovalSource(options),
191
+ resolvedCredentialSource,
192
+ commandEnv,
193
+ };
194
+ }
195
+ async function probeCloudCredentialSource(backend, runtime) {
196
+ try {
197
+ const podCredential = await runtime.loadPodBackendCredential(backend);
198
+ if (!podCredential) {
199
+ return {
200
+ probe: {
201
+ status: 'unavailable',
202
+ message: podCredentialMissingMessage(backend),
203
+ },
204
+ };
205
+ }
206
+ return {
207
+ probe: { status: 'available' },
208
+ commandEnv: normalizeBackendCommandEnv(backend, { ...podCredential.env }),
209
+ };
210
+ }
211
+ catch (error) {
212
+ return {
213
+ probe: {
214
+ status: 'error',
215
+ message: error instanceof Error ? error.message : String(error),
216
+ },
217
+ };
218
+ }
219
+ }
220
+ export async function resolveWatchRunOptions(options, runtime = watchRuntime) {
221
+ const source = requestedCredentialSource(options);
222
+ if (source === 'cloud') {
223
+ const { probe, commandEnv } = await probeCloudCredentialSource(options.backend, runtime);
224
+ const resolution = resolveWatchCredentialSourceResolution({
225
+ requestedSource: source,
226
+ localAuthStatus: { state: 'unknown' },
227
+ cloudCredentialProbe: probe,
228
+ });
229
+ if (resolution.error) {
230
+ throw new Error(resolution.error);
231
+ }
232
+ return {
233
+ options: withResolvedSource(options, resolution.resolvedSource ?? 'cloud', commandEnv),
234
+ authPreflight: resolution.authStatus,
235
+ };
236
+ }
237
+ const localOptions = withResolvedSource(options, 'local');
238
+ const authPreflight = await runtime.preflightWatchAuth(options.backend);
239
+ let cloudCredentialProbe;
240
+ let commandEnv;
241
+ if (shouldAttemptCloudCredentialProbe(source, authPreflight)) {
242
+ const cloudResult = await probeCloudCredentialSource(options.backend, runtime);
243
+ cloudCredentialProbe = cloudResult.probe;
244
+ commandEnv = cloudResult.commandEnv;
245
+ }
246
+ const resolution = resolveWatchCredentialSourceResolution({
247
+ requestedSource: source,
248
+ localAuthStatus: authPreflight,
249
+ cloudCredentialProbe,
250
+ defaultLocalMessage: `${options.backend} is not authenticated`,
251
+ });
252
+ if (resolution.error) {
253
+ throw new Error(resolution.error);
254
+ }
255
+ return {
256
+ options: resolution.resolvedSource === 'cloud'
257
+ ? withResolvedSource(options, 'cloud', commandEnv)
258
+ : localOptions,
259
+ authPreflight: resolution.authStatus,
260
+ };
261
+ }
262
+ class BaseSession {
263
+ record;
264
+ display;
265
+ child = null;
266
+ activeExitPromise = null;
267
+ activeExitResolve = null;
268
+ closed = false;
269
+ lastExit = null;
270
+ constructor(record, prompt) {
271
+ this.record = record;
272
+ this.display = createWatchDisplay(record, prompt);
273
+ }
274
+ spawnProcess(command, args, cwd, env) {
275
+ this.activeExitPromise = new Promise((resolve) => {
276
+ this.activeExitResolve = resolve;
277
+ });
278
+ const child = spawn(command, args, {
279
+ cwd,
280
+ env: { ...process.env, ...env },
281
+ stdio: ['pipe', 'pipe', 'pipe'],
282
+ });
283
+ child.stdout.setEncoding('utf-8');
284
+ child.stderr.setEncoding('utf-8');
285
+ this.child = child;
286
+ child.on('error', (error) => {
287
+ appendEntry(this.record, 'system', JSON.stringify({ type: 'process.error', message: error.message }), [
288
+ { type: 'session.note', message: error.message, raw: error.message },
289
+ ]);
290
+ this.onProcessFailure(new Error(error.message));
291
+ });
292
+ child.on('exit', (code, signal) => {
293
+ this.lastExit = { code, signal };
294
+ this.activeExitResolve?.({ code, signal });
295
+ this.activeExitResolve = null;
296
+ if (this.child === child) {
297
+ this.child = null;
298
+ }
299
+ this.onProcessExit(code, signal);
300
+ });
301
+ return child;
302
+ }
303
+ async finalizeAndClose(status, error) {
304
+ const exitState = await this.waitForActiveExit();
305
+ const next = finishWatchSession(this.record, {
306
+ status,
307
+ exitCode: exitState.code,
308
+ signal: exitState.signal,
309
+ error,
310
+ });
311
+ this.display.finish(status, next, error);
312
+ return next;
313
+ }
314
+ recordParsedLine(stream, line, events) {
315
+ appendEntry(this.record, stream, line, events);
316
+ if (events.length > 0) {
317
+ this.display.renderEvents(events);
318
+ return;
319
+ }
320
+ this.display.renderRawLine(stream, line);
321
+ }
322
+ updateRecord(updates) {
323
+ Object.assign(this.record, updates);
324
+ writeWatchSession(this.record);
325
+ this.display.updateRecord(this.record);
326
+ }
327
+ waitForActiveExit() {
328
+ if (this.lastExit) {
329
+ return Promise.resolve(this.lastExit);
330
+ }
331
+ if (this.activeExitPromise) {
332
+ return this.activeExitPromise;
333
+ }
334
+ return Promise.resolve({ code: null, signal: null });
335
+ }
336
+ async close() {
337
+ this.closed = true;
338
+ const child = this.child;
339
+ if (!child) {
340
+ return;
341
+ }
342
+ child.stdin.end();
343
+ const settled = await Promise.race([
344
+ this.waitForActiveExit().then(() => true),
345
+ delay(1500).then(() => false),
346
+ ]);
347
+ if (!settled) {
348
+ child.kill('SIGTERM');
349
+ await this.waitForActiveExit();
350
+ }
351
+ }
352
+ }
353
+ class AcpSession extends BaseSession {
354
+ hook;
355
+ options;
356
+ requestId = 1;
357
+ pendingRequests = new Map();
358
+ turnState = null;
359
+ sessionId = null;
360
+ authFailureMessage = null;
361
+ turnSettleTimer = null;
362
+ activeAgentRequests = 0;
363
+ constructor(options, hook) {
364
+ const plan = hook.buildSpawnPlan(options);
365
+ super(createWatchSession({ ...options, transport: options.transport ?? 'acp' }, plan), watchRuntime.promptText);
366
+ this.hook = hook;
367
+ this.options = options;
368
+ }
369
+ async start() {
370
+ const plan = this.hook.buildSpawnPlan(this.options);
371
+ const child = this.spawnProcess(plan.command, plan.args, this.record.cwd, mergeCommandEnv(this.options.backend, this.options.commandEnv, plan.env));
372
+ const stdoutSplitter = createLineSplitter('stdout', this.handleLine.bind(this));
373
+ const stderrSplitter = createLineSplitter('stderr', this.handleLine.bind(this));
374
+ child.stdout.on('data', (chunk) => stdoutSplitter.push(chunk));
375
+ child.stderr.on('data', (chunk) => stderrSplitter.push(chunk));
376
+ child.on('exit', () => {
377
+ stdoutSplitter.flush();
378
+ stderrSplitter.flush();
379
+ });
380
+ await this.sendRequest('initialize', {
381
+ protocolVersion: 1,
382
+ clientCapabilities: {},
383
+ clientInfo: {
384
+ name: 'linx-cli',
385
+ version: '0.1.0',
386
+ },
387
+ });
388
+ const newSession = await this.sendRequest('session/new', {
389
+ cwd: this.options.cwd,
390
+ mcpServers: [],
391
+ });
392
+ const sessionId = typeof newSession.sessionId === 'string'
393
+ ? newSession.sessionId
394
+ : typeof newSession.session?.id === 'string'
395
+ ? newSession.session.id
396
+ : null;
397
+ if (!sessionId) {
398
+ throw new Error(`ACP backend ${this.options.backend} did not return a session id`);
399
+ }
400
+ this.sessionId = sessionId;
401
+ this.updateRecord({
402
+ backendSessionId: sessionId,
403
+ error: undefined,
404
+ });
405
+ if (this.options.model) {
406
+ await this.trySetModel(this.options.model);
407
+ }
408
+ }
409
+ applyResolvedOptions(options) {
410
+ this.options = options;
411
+ const plan = this.hook.buildSpawnPlan(options);
412
+ this.updateRecord(syncRecordFromOptions(this.record, options, plan));
413
+ }
414
+ async setModel(model) {
415
+ const normalized = model.trim();
416
+ if (!normalized) {
417
+ throw new Error('Model id cannot be empty');
418
+ }
419
+ await this.trySetModel(normalized, true);
420
+ this.options = {
421
+ ...this.options,
422
+ model: normalized,
423
+ };
424
+ this.updateRecord({
425
+ model: normalized,
426
+ });
427
+ }
428
+ async sendTurn(text) {
429
+ if (!this.sessionId) {
430
+ throw new Error('ACP session is not initialized');
431
+ }
432
+ if (this.turnState) {
433
+ throw new Error('A watch turn is already in progress');
434
+ }
435
+ appendUserTurn(this.record, text);
436
+ appendTurnStart(this.record, this.record.command, this.record.args);
437
+ this.authFailureMessage = null;
438
+ const completion = new Promise((resolve, reject) => {
439
+ this.turnState = {
440
+ resolve,
441
+ reject,
442
+ responseReceived: false,
443
+ };
444
+ });
445
+ void completion.catch(() => { });
446
+ try {
447
+ const response = await this.sendRequest('session/prompt', {
448
+ sessionId: this.sessionId,
449
+ prompt: [{ type: 'text', text }],
450
+ });
451
+ const turnState = this.turnState;
452
+ if (turnState === null) {
453
+ return;
454
+ }
455
+ ;
456
+ turnState.responseReceived = true;
457
+ this.recordParsedLine('system', JSON.stringify({
458
+ type: 'turn.stop',
459
+ stopReason: typeof response.stopReason === 'string' ? response.stopReason : undefined,
460
+ }), [{
461
+ type: 'assistant.done',
462
+ raw: {
463
+ stopReason: typeof response.stopReason === 'string' ? response.stopReason : undefined,
464
+ },
465
+ }]);
466
+ this.scheduleTurnSettle();
467
+ }
468
+ catch (error) {
469
+ this.turnState = null;
470
+ this.clearTurnSettleTimer();
471
+ throw error;
472
+ }
473
+ await completion;
474
+ }
475
+ onProcessExit(code, signal) {
476
+ this.clearTurnSettleTimer();
477
+ const errorMessage = this.authFailureMessage ?? `ACP backend exited (${code ?? signal ?? 'null'})`;
478
+ for (const pending of this.pendingRequests.values()) {
479
+ pending.reject(new Error(errorMessage));
480
+ }
481
+ this.pendingRequests.clear();
482
+ if (this.turnState) {
483
+ const reject = this.turnState.reject;
484
+ this.turnState = null;
485
+ reject(new Error(this.authFailureMessage ?? `ACP backend exited during turn (${code ?? signal ?? 'null'})`));
486
+ }
487
+ }
488
+ onProcessFailure(error) {
489
+ this.clearTurnSettleTimer();
490
+ for (const pending of this.pendingRequests.values()) {
491
+ pending.reject(error);
492
+ }
493
+ this.pendingRequests.clear();
494
+ if (this.turnState) {
495
+ const reject = this.turnState.reject;
496
+ this.turnState = null;
497
+ reject(error);
498
+ }
499
+ }
500
+ clearTurnSettleTimer() {
501
+ if (!this.turnSettleTimer) {
502
+ return;
503
+ }
504
+ clearTimeout(this.turnSettleTimer);
505
+ this.turnSettleTimer = null;
506
+ }
507
+ scheduleTurnSettle() {
508
+ if (!this.turnState || !this.turnState.responseReceived || this.activeAgentRequests > 0) {
509
+ return;
510
+ }
511
+ this.clearTurnSettleTimer();
512
+ this.turnSettleTimer = setTimeout(() => {
513
+ if (!this.turnState || !this.turnState.responseReceived || this.activeAgentRequests > 0) {
514
+ return;
515
+ }
516
+ const turnState = this.turnState;
517
+ this.turnState = null;
518
+ this.turnSettleTimer = null;
519
+ if (this.authFailureMessage) {
520
+ turnState.reject(new Error(this.authFailureMessage));
521
+ return;
522
+ }
523
+ turnState.resolve();
524
+ }, 75);
525
+ }
526
+ markTurnActivity() {
527
+ if (!this.turnState?.responseReceived) {
528
+ return;
529
+ }
530
+ this.scheduleTurnSettle();
531
+ }
532
+ handleLine(line, stream) {
533
+ const authFailure = detectWatchAuthFailure(this.record.backend, line);
534
+ if (authFailure) {
535
+ this.authFailureMessage = authFailure.message;
536
+ }
537
+ if (stream === 'stderr') {
538
+ this.recordParsedLine(stream, line, []);
539
+ this.markTurnActivity();
540
+ return;
541
+ }
542
+ const message = parseWatchJsonLine(line);
543
+ if (!message) {
544
+ this.recordParsedLine(stream, line, []);
545
+ this.markTurnActivity();
546
+ return;
547
+ }
548
+ if (typeof message.method === 'string' && typeof message.id !== 'undefined') {
549
+ const method = message.method;
550
+ const params = (typeof message.params === 'object' && message.params !== null ? message.params : {});
551
+ const events = method === 'auth/request'
552
+ ? [{
553
+ type: 'session.note',
554
+ message: [
555
+ typeof params.message === 'string' ? params.message : 'Authentication required',
556
+ typeof params.url === 'string' ? `Open ${params.url}` : '',
557
+ ].filter(Boolean).join(' · '),
558
+ raw: message,
559
+ }]
560
+ : normalizeAcpRequest(message);
561
+ if (events.length > 0) {
562
+ this.recordParsedLine(stream, line, events);
563
+ }
564
+ else {
565
+ appendEntry(this.record, stream, line, []);
566
+ }
567
+ void this.handleAgentRequest(message);
568
+ this.markTurnActivity();
569
+ return;
570
+ }
571
+ if (typeof message.method === 'string') {
572
+ const events = normalizeAcpSessionNotification(message);
573
+ if (events.length > 0) {
574
+ this.recordParsedLine(stream, line, events);
575
+ }
576
+ else {
577
+ appendEntry(this.record, stream, line, []);
578
+ }
579
+ this.markTurnActivity();
580
+ return;
581
+ }
582
+ if (typeof message.id !== 'undefined') {
583
+ appendEntry(this.record, stream, line, []);
584
+ this.handleResponse(message);
585
+ this.markTurnActivity();
586
+ return;
587
+ }
588
+ this.recordParsedLine(stream, line, []);
589
+ this.markTurnActivity();
590
+ }
591
+ handleResponse(message) {
592
+ const id = typeof message.id === 'number' ? message.id : Number(message.id);
593
+ const pending = this.pendingRequests.get(id);
594
+ if (!pending) {
595
+ return;
596
+ }
597
+ this.pendingRequests.delete(id);
598
+ if ('error' in message && message.error) {
599
+ const authFailure = detectWatchAuthFailure(this.record.backend, JSON.stringify(message));
600
+ if (authFailure) {
601
+ this.authFailureMessage = authFailure.message;
602
+ pending.reject(new Error(authFailure.message));
603
+ return;
604
+ }
605
+ const error = message.error;
606
+ const detail = typeof error.message === 'string'
607
+ ? error.message
608
+ : JSON.stringify(error);
609
+ pending.reject(new Error(detail));
610
+ return;
611
+ }
612
+ pending.resolve(message.result);
613
+ }
614
+ async handleAgentRequest(message) {
615
+ const id = typeof message.id === 'number' ? message.id : Number(message.id);
616
+ const method = typeof message.method === 'string' ? message.method : '';
617
+ const params = (typeof message.params === 'object' && message.params !== null ? message.params : {});
618
+ this.activeAgentRequests += 1;
619
+ this.clearTurnSettleTimer();
620
+ try {
621
+ if (method === 'auth/request') {
622
+ const lines = [
623
+ `[note] ${typeof params.message === 'string' ? params.message : 'Authentication required'}`,
624
+ ...(typeof params.url === 'string' ? [`[note] ${params.url}`] : []),
625
+ ];
626
+ const shouldContinue = await promptAuthContinue(this.display, lines);
627
+ if (!shouldContinue) {
628
+ this.authFailureMessage = 'Authentication request cancelled by user';
629
+ }
630
+ this.sendResponse(id, {});
631
+ return;
632
+ }
633
+ const interaction = normalizeAcpInteractionRequest(message);
634
+ if (!interaction) {
635
+ this.sendError(id, -32601, `Unsupported ACP client request: ${method}`);
636
+ return;
637
+ }
638
+ if (interaction.kind === 'user-input') {
639
+ const result = await this.resolveToolUserInput(interaction.questions);
640
+ this.sendResponse(id, result);
641
+ return;
642
+ }
643
+ const autoDecision = resolveWatchAutoApprovalDecision({
644
+ mode: this.options.mode,
645
+ request: interaction,
646
+ });
647
+ const decision = autoDecision ?? await this.resolveApproval(interaction);
648
+ this.sendResponse(id, buildAcpPermissionResponse(interaction, decision));
649
+ }
650
+ catch (error) {
651
+ const messageText = error instanceof Error ? error.message : String(error);
652
+ this.sendError(id, -32000, messageText);
653
+ }
654
+ finally {
655
+ this.activeAgentRequests = Math.max(0, this.activeAgentRequests - 1);
656
+ this.scheduleTurnSettle();
657
+ }
658
+ }
659
+ async resolveToolUserInput(questions) {
660
+ this.display.setPhase('question', questions[0]?.header ?? 'Input required');
661
+ const answers = await this.display.chooseQuestions(questions);
662
+ this.display.setPhase('running', 'Continuing turn');
663
+ return buildWatchUserInputResponse(answers);
664
+ }
665
+ async resolveApproval(interaction) {
666
+ const source = this.options.approvalSource ?? 'hybrid';
667
+ if (source === 'local') {
668
+ return promptApproval(this.display, approvalPromptMessage(interaction), true);
669
+ }
670
+ if (source === 'remote') {
671
+ return this.resolveRemoteOnlyApproval(interaction);
672
+ }
673
+ return this.resolveHybridApproval(interaction);
674
+ }
675
+ async resolveRemoteOnlyApproval(interaction) {
676
+ const promptMessage = approvalPromptMessage(interaction);
677
+ appendSessionNote(this.record, `Waiting for remote approval | ${promptMessage}`);
678
+ this.display.setPhase('approval', `${promptMessage} · remote`);
679
+ const remote = await watchRuntime.createRemoteWatchApproval({
680
+ record: this.record,
681
+ request: interaction,
682
+ });
683
+ const decision = await watchRuntime.waitForRemoteWatchApproval({
684
+ approvalId: remote.id,
685
+ });
686
+ appendSessionNote(this.record, `Remote approval resolved | ${decision}`);
687
+ this.display.setPhase('running', 'Continuing turn');
688
+ return decision;
689
+ }
690
+ async resolveHybridApproval(interaction) {
691
+ const promptMessage = approvalPromptMessage(interaction);
692
+ let remoteApproval = null;
693
+ try {
694
+ remoteApproval = await watchRuntime.createRemoteWatchApproval({
695
+ record: this.record,
696
+ request: interaction,
697
+ });
698
+ appendSessionNote(this.record, `Remote approval opened | ${remoteApproval.id}`);
699
+ }
700
+ catch (error) {
701
+ appendSessionNote(this.record, `Remote approval unavailable | ${error instanceof Error ? error.message : String(error)}`);
702
+ return promptApproval(this.display, promptMessage, true);
703
+ }
704
+ const localAbort = new AbortController();
705
+ const remoteAbort = new AbortController();
706
+ const localDecisionPromise = promptApproval(this.display, promptMessage, true, localAbort.signal)
707
+ .then((decision) => ({ source: 'local', decision }));
708
+ const remoteDecisionPromise = watchRuntime.waitForRemoteWatchApproval({
709
+ approvalId: remoteApproval.id,
710
+ signal: remoteAbort.signal,
711
+ }).then((decision) => ({ source: 'remote', decision }));
712
+ void localDecisionPromise.catch(() => undefined);
713
+ void remoteDecisionPromise.catch(() => undefined);
714
+ try {
715
+ const winner = await Promise.race([localDecisionPromise, remoteDecisionPromise]);
716
+ if (winner.source === 'local') {
717
+ remoteAbort.abort();
718
+ appendSessionNote(this.record, `Local approval resolved | ${winner.decision}`);
719
+ void watchRuntime.resolveRemoteWatchApproval({
720
+ approvalId: remoteApproval.id,
721
+ decision: winner.decision,
722
+ note: 'resolved from active local watch session',
723
+ }).catch(() => undefined);
724
+ this.display.setPhase('running', 'Continuing turn');
725
+ return winner.decision;
726
+ }
727
+ localAbort.abort();
728
+ appendSessionNote(this.record, `Remote approval resolved | ${winner.decision}`);
729
+ this.display.setPhase('running', 'Continuing turn');
730
+ return winner.decision;
731
+ }
732
+ catch (error) {
733
+ if (isRemoteApprovalAbortError(error)) {
734
+ throw error;
735
+ }
736
+ remoteAbort.abort();
737
+ localAbort.abort();
738
+ throw error;
739
+ }
740
+ }
741
+ sendRequest(method, params) {
742
+ const id = this.requestId++;
743
+ const child = this.child;
744
+ if (!child) {
745
+ throw new Error('ACP backend is not started');
746
+ }
747
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
748
+ return new Promise((resolve, reject) => {
749
+ this.pendingRequests.set(id, { resolve, reject, method });
750
+ });
751
+ }
752
+ sendResponse(id, result) {
753
+ const child = this.child;
754
+ if (!child) {
755
+ throw new Error('ACP backend is not started');
756
+ }
757
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
758
+ }
759
+ sendError(id, code, message) {
760
+ const child = this.child;
761
+ if (!child) {
762
+ throw new Error('ACP backend is not started');
763
+ }
764
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } })}\n`);
765
+ }
766
+ async trySetModel(model, throwOnFailure = false) {
767
+ if (!this.sessionId) {
768
+ return false;
769
+ }
770
+ try {
771
+ await this.sendRequest('session/set_model', {
772
+ sessionId: this.sessionId,
773
+ modelId: model,
774
+ });
775
+ return true;
776
+ }
777
+ catch (error) {
778
+ const reason = error instanceof Error ? error.message : String(error);
779
+ appendEntry(this.record, 'system', JSON.stringify({
780
+ type: 'session.set_model.skipped',
781
+ model,
782
+ reason,
783
+ }), []);
784
+ if (throwOnFailure) {
785
+ throw new Error(reason);
786
+ }
787
+ return false;
788
+ }
789
+ }
790
+ }
791
+ function buildConversationSession(options) {
792
+ const hook = getWatchHook(options.backend);
793
+ return new AcpSession(options, hook);
794
+ }
795
+ async function handleWatchShellCommand(args) {
796
+ const { input, session, display, queueState, backend, record } = args;
797
+ if (input === '/exit' || input === '/quit') {
798
+ return 'exit';
799
+ }
800
+ if (input === '/help') {
801
+ display.showHelp();
802
+ return 'handled';
803
+ }
804
+ if (input === '/session') {
805
+ appendAndDisplaySessionNote(record, display, [
806
+ `session=${record.id}`,
807
+ `backend=${record.backend}`,
808
+ `runtime=${record.runtime}`,
809
+ `source=${record.resolvedCredentialSource ?? record.credentialSource}`,
810
+ `model=${record.model ?? 'default'}`,
811
+ `cwd=${record.cwd}`,
812
+ ].join(' | '));
813
+ return 'handled';
814
+ }
815
+ if (input === '/queue') {
816
+ appendAndDisplaySessionNote(record, display, `queue | steer=${queueState.steeringCount} | follow-up=${queueState.followUpCount}`);
817
+ return 'handled';
818
+ }
819
+ if (input === '/sessions') {
820
+ const summaries = listWatchSessions().slice(0, 5).map(formatWatchSessionSummary);
821
+ if (summaries.length === 0) {
822
+ appendAndDisplaySessionNote(record, display, 'No archived watch sessions found');
823
+ return 'handled';
824
+ }
825
+ for (const summary of summaries) {
826
+ appendAndDisplaySessionNote(record, display, summary);
827
+ }
828
+ return 'handled';
829
+ }
830
+ if (input === '/new') {
831
+ appendAndDisplaySessionNote(record, display, 'Use `linx watch run` to start a fresh watch session');
832
+ return 'handled';
833
+ }
834
+ if (input === '/debug' || input === '/debug on') {
835
+ display.setDebugMode(true);
836
+ appendSessionNote(record, 'Debug protocol view enabled', { debug: true });
837
+ return 'handled';
838
+ }
839
+ if (input === '/debug off') {
840
+ display.setDebugMode(false);
841
+ appendSessionNote(record, 'Debug protocol view disabled', { debug: false });
842
+ return 'handled';
843
+ }
844
+ if (input.startsWith('/model ')) {
845
+ const model = input.slice('/model '.length).trim();
846
+ if (!model) {
847
+ appendAndDisplaySessionNote(record, display, 'Usage: /model <modelId>', 'error');
848
+ return 'handled';
849
+ }
850
+ try {
851
+ await session.setModel(model);
852
+ appendAndDisplaySessionNote(record, display, `Model set to ${model}`, 'success', { backend, model });
853
+ }
854
+ catch (error) {
855
+ const reason = error instanceof Error ? error.message : String(error);
856
+ appendAndDisplaySessionNote(record, display, `Model switch failed | ${reason}`, 'error', { backend, model, reason });
857
+ }
858
+ return 'handled';
859
+ }
860
+ return 'pass';
861
+ }
862
+ export const __testHandleWatchShellCommand = handleWatchShellCommand;
863
+ export async function runWatch(options) {
864
+ const previousPlainEnv = process.env.LINX_WATCH_PLAIN;
865
+ if (options.plain) {
866
+ process.env.LINX_WATCH_PLAIN = '1';
867
+ }
868
+ let fatalError = null;
869
+ let session = null;
870
+ const requestedOptions = {
871
+ ...options,
872
+ runtime: requestedRuntime(options),
873
+ transport: 'acp',
874
+ credentialSource: requestedCredentialSource(options),
875
+ approvalSource: requestedApprovalSource(options),
876
+ };
877
+ try {
878
+ session = buildConversationSession(requestedOptions);
879
+ const activeSession = session;
880
+ activeSession.display.start();
881
+ activeSession.display.showHelp();
882
+ activeSession.display.setPhase('starting', `Preparing ${requestedOptions.backend}`);
883
+ activeSession.display.updateQueue({
884
+ steeringCount: 0,
885
+ followUpCount: 0,
886
+ });
887
+ let resolvedRun;
888
+ try {
889
+ resolvedRun = await resolveWatchRunOptions(requestedOptions);
890
+ }
891
+ catch (error) {
892
+ const message = error instanceof Error ? error.message : String(error);
893
+ appendEntry(activeSession.record, 'system', JSON.stringify({
894
+ type: 'credentials.resolve',
895
+ backend: requestedOptions.backend,
896
+ requestedCredentialSource: requestedOptions.credentialSource,
897
+ error: message,
898
+ }), []);
899
+ throw error;
900
+ }
901
+ activeSession.applyResolvedOptions(resolvedRun.options);
902
+ appendEntry(activeSession.record, 'system', JSON.stringify({
903
+ type: 'credentials.resolve',
904
+ backend: resolvedRun.options.backend,
905
+ requestedCredentialSource: resolvedRun.options.credentialSource,
906
+ resolvedCredentialSource: resolvedRun.options.resolvedCredentialSource,
907
+ }), []);
908
+ const authPreflight = resolvedRun.authPreflight;
909
+ if (authPreflight.state === 'unauthenticated') {
910
+ const message = authPreflight.message ?? `${resolvedRun.options.backend} is not authenticated`;
911
+ appendEntry(activeSession.record, 'system', JSON.stringify({
912
+ type: 'auth.preflight',
913
+ backend: resolvedRun.options.backend,
914
+ state: authPreflight.state,
915
+ resolvedCredentialSource: resolvedRun.options.resolvedCredentialSource,
916
+ }), [
917
+ { type: 'session.note', message, raw: { backend: resolvedRun.options.backend, state: authPreflight.state } },
918
+ ]);
919
+ throw new Error(message);
920
+ }
921
+ await activeSession.start();
922
+ const steeringQueue = [];
923
+ const followUpQueue = [];
924
+ let stopRequested = false;
925
+ let activeTurn = null;
926
+ let wakeResolver = null;
927
+ const resolveWake = () => {
928
+ if (!wakeResolver) {
929
+ return;
930
+ }
931
+ const resolve = wakeResolver;
932
+ wakeResolver = null;
933
+ resolve();
934
+ };
935
+ const waitForWake = async () => {
936
+ if (fatalError || (stopRequested && activeTurn === null)) {
937
+ return;
938
+ }
939
+ await new Promise((resolve) => {
940
+ wakeResolver = resolve;
941
+ });
942
+ };
943
+ const updateQueueState = () => {
944
+ activeSession.display.updateQueue({
945
+ steeringCount: steeringQueue.length,
946
+ followUpCount: followUpQueue.length,
947
+ });
948
+ };
949
+ const clearQueuedSubmissions = () => {
950
+ if (steeringQueue.length === 0 && followUpQueue.length === 0) {
951
+ return;
952
+ }
953
+ steeringQueue.length = 0;
954
+ followUpQueue.length = 0;
955
+ updateQueueState();
956
+ };
957
+ const restoreQueuedSubmission = () => {
958
+ const restored = steeringQueue.pop() ?? followUpQueue.pop() ?? null;
959
+ updateQueueState();
960
+ return restored;
961
+ };
962
+ const inputController = {
963
+ restoreQueuedSubmission,
964
+ };
965
+ session.display.bindInputController(inputController);
966
+ const enqueueSubmission = (submission) => {
967
+ if (submission.mode === 'follow-up') {
968
+ followUpQueue.push(submission);
969
+ }
970
+ else {
971
+ steeringQueue.push(submission);
972
+ }
973
+ updateQueueState();
974
+ appendSessionNote(activeSession.record, submission.mode === 'follow-up'
975
+ ? `Queued follow-up message (${followUpQueue.length} total)`
976
+ : `Queued steering message (${steeringQueue.length} total)`, { text: submission.text, mode: submission.mode });
977
+ resolveWake();
978
+ };
979
+ const dequeueSubmission = () => {
980
+ const next = steeringQueue.shift() ?? followUpQueue.shift() ?? null;
981
+ updateQueueState();
982
+ return next;
983
+ };
984
+ const runTurn = (text) => {
985
+ const trimmed = text.trim();
986
+ if (!trimmed) {
987
+ return;
988
+ }
989
+ activeSession.display.showUserTurn(trimmed);
990
+ activeSession.display.setPhase('running', `Running ${resolvedRun.options.backend}`);
991
+ activeTurn = activeSession.sendTurn(trimmed)
992
+ .catch((error) => {
993
+ fatalError = error instanceof Error ? error : new Error(String(error));
994
+ })
995
+ .finally(() => {
996
+ activeTurn = null;
997
+ if (fatalError) {
998
+ stopRequested = true;
999
+ clearQueuedSubmissions();
1000
+ resolveWake();
1001
+ return;
1002
+ }
1003
+ const next = dequeueSubmission();
1004
+ if (next) {
1005
+ runTurn(next.text);
1006
+ return;
1007
+ }
1008
+ if (stopRequested) {
1009
+ resolveWake();
1010
+ return;
1011
+ }
1012
+ activeSession.display.setPhase('ready', 'Waiting for input');
1013
+ resolveWake();
1014
+ });
1015
+ };
1016
+ const dispatchSubmission = async (submission) => {
1017
+ const trimmed = submission.text.trim();
1018
+ if (!trimmed) {
1019
+ return;
1020
+ }
1021
+ const shellCommand = await handleWatchShellCommand({
1022
+ input: trimmed,
1023
+ session: activeSession,
1024
+ display: activeSession.display,
1025
+ queueState: {
1026
+ steeringCount: steeringQueue.length,
1027
+ followUpCount: followUpQueue.length,
1028
+ },
1029
+ backend: resolvedRun.options.backend,
1030
+ record: activeSession.record,
1031
+ });
1032
+ if (shellCommand === 'handled') {
1033
+ activeSession.display.setPhase(activeTurn ? 'running' : 'ready', activeTurn ? `Running ${resolvedRun.options.backend}` : 'Waiting for input');
1034
+ resolveWake();
1035
+ return;
1036
+ }
1037
+ if (shellCommand === 'exit') {
1038
+ stopRequested = true;
1039
+ resolveWake();
1040
+ return;
1041
+ }
1042
+ if (activeTurn) {
1043
+ enqueueSubmission({
1044
+ text: trimmed,
1045
+ mode: submission.mode,
1046
+ });
1047
+ return;
1048
+ }
1049
+ runTurn(trimmed);
1050
+ };
1051
+ let inputLoop = null;
1052
+ if (resolvedRun.options.prompt) {
1053
+ await dispatchSubmission({
1054
+ text: resolvedRun.options.prompt,
1055
+ mode: 'send',
1056
+ });
1057
+ stopRequested = true;
1058
+ }
1059
+ else {
1060
+ inputLoop = (async () => {
1061
+ activeSession.display.setPhase('ready', 'Waiting for input');
1062
+ while (!fatalError && !stopRequested) {
1063
+ const submission = await activeSession.display.promptInput('you> ');
1064
+ await dispatchSubmission(submission);
1065
+ }
1066
+ })().catch((error) => {
1067
+ fatalError = error instanceof Error ? error : new Error(String(error));
1068
+ stopRequested = true;
1069
+ clearQueuedSubmissions();
1070
+ resolveWake();
1071
+ });
1072
+ }
1073
+ while (!fatalError && (!stopRequested || activeTurn !== null || steeringQueue.length > 0 || followUpQueue.length > 0)) {
1074
+ await waitForWake();
1075
+ }
1076
+ void inputLoop;
1077
+ if (fatalError) {
1078
+ throw fatalError;
1079
+ }
1080
+ return 0;
1081
+ }
1082
+ catch (error) {
1083
+ fatalError = error instanceof Error ? error : new Error(String(error));
1084
+ throw fatalError;
1085
+ }
1086
+ finally {
1087
+ if (session) {
1088
+ await session.close();
1089
+ const finalRecord = await session.finalizeAndClose(fatalError ? 'failed' : 'completed', fatalError?.message);
1090
+ await watchRuntime.persistWatchConversationToPod(finalRecord).catch(() => undefined);
1091
+ }
1092
+ if (options.plain) {
1093
+ if (previousPlainEnv === undefined) {
1094
+ delete process.env.LINX_WATCH_PLAIN;
1095
+ }
1096
+ else {
1097
+ process.env.LINX_WATCH_PLAIN = previousPlainEnv;
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ export function listArchivedWatchSessions() {
1103
+ return listWatchSessions();
1104
+ }
1105
+ export function loadArchivedWatchSession(id) {
1106
+ return loadWatchSession(id);
1107
+ }
1108
+ export function loadArchivedWatchEvents(id) {
1109
+ return loadWatchEvents(id);
1110
+ }
1111
+ export { formatWatchSessionSummary };
1112
+ export function listSupportedWatchBackends() {
1113
+ return listWatchHooks().map((hook) => ({
1114
+ backend: hook.id,
1115
+ label: hook.label,
1116
+ description: hook.description,
1117
+ modes: {
1118
+ manual: describeWatchMode('manual'),
1119
+ smart: describeWatchMode('smart'),
1120
+ auto: describeWatchMode('auto'),
1121
+ },
1122
+ }));
1123
+ }
1124
+ //# sourceMappingURL=runner.js.map