@undefineds.co/linx 0.2.19 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +23 -21
  2. package/dist/index.js +93 -142
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/ai-command.js +34 -30
  5. package/dist/lib/ai-command.js.map +1 -1
  6. package/dist/lib/auto-mode/archive.js +235 -0
  7. package/dist/lib/auto-mode/archive.js.map +1 -0
  8. package/dist/lib/{watch → auto-mode}/auth.js +6 -6
  9. package/dist/lib/auto-mode/auth.js.map +1 -0
  10. package/dist/lib/{watch → auto-mode}/codex-composer.js +1 -1
  11. package/dist/lib/auto-mode/codex-composer.js.map +1 -0
  12. package/dist/lib/auto-mode/codex-footer.js.map +1 -0
  13. package/dist/lib/auto-mode/codex-overlay.js.map +1 -0
  14. package/dist/lib/{watch → auto-mode}/codex-request-form.js +2 -2
  15. package/dist/lib/auto-mode/codex-request-form.js.map +1 -0
  16. package/dist/lib/auto-mode/codex-request-input.js.map +1 -0
  17. package/dist/lib/{watch → auto-mode}/display.js +63 -57
  18. package/dist/lib/auto-mode/display.js.map +1 -0
  19. package/dist/lib/{watch → auto-mode}/format.js +24 -16
  20. package/dist/lib/auto-mode/format.js.map +1 -0
  21. package/dist/lib/{watch → auto-mode}/hooks/claude.js +3 -2
  22. package/dist/lib/auto-mode/hooks/claude.js.map +1 -0
  23. package/dist/lib/{watch → auto-mode}/hooks/codebuddy.js +3 -2
  24. package/dist/lib/auto-mode/hooks/codebuddy.js.map +1 -0
  25. package/dist/lib/auto-mode/hooks/codex.js +18 -0
  26. package/dist/lib/auto-mode/hooks/codex.js.map +1 -0
  27. package/dist/lib/auto-mode/hooks/index.js +27 -0
  28. package/dist/lib/auto-mode/hooks/index.js.map +1 -0
  29. package/dist/lib/auto-mode/hooks/shared.js.map +1 -0
  30. package/dist/lib/auto-mode/index.js.map +1 -0
  31. package/dist/lib/auto-mode/pod-ai.js +116 -0
  32. package/dist/lib/auto-mode/pod-ai.js.map +1 -0
  33. package/dist/lib/{watch → auto-mode}/pod-approval.js +495 -325
  34. package/dist/lib/auto-mode/pod-approval.js.map +1 -0
  35. package/dist/lib/auto-mode/pod-persistence.js +412 -0
  36. package/dist/lib/auto-mode/pod-persistence.js.map +1 -0
  37. package/dist/lib/{watch → auto-mode}/runner.js +280 -193
  38. package/dist/lib/auto-mode/runner.js.map +1 -0
  39. package/dist/lib/{watch → auto-mode}/secretary.js +8 -8
  40. package/dist/lib/auto-mode/secretary.js.map +1 -0
  41. package/dist/lib/{watch → auto-mode}/types.js.map +1 -1
  42. package/dist/lib/auto-mode-command.js +114 -0
  43. package/dist/lib/auto-mode-command.js.map +1 -0
  44. package/dist/lib/codex-plugin/bridge.js +11 -12
  45. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  46. package/dist/lib/codex-plugin/codex-native-proxy.js +11 -12
  47. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  48. package/dist/lib/linx-tui-contract.js +26 -0
  49. package/dist/lib/linx-tui-contract.js.map +1 -0
  50. package/dist/lib/login-command.js +9 -3
  51. package/dist/lib/login-command.js.map +1 -1
  52. package/dist/lib/models.js +2 -2
  53. package/dist/lib/models.js.map +1 -1
  54. package/dist/lib/oidc-auth.js +83 -104
  55. package/dist/lib/oidc-auth.js.map +1 -1
  56. package/dist/lib/oidc-session-storage.js +7 -1
  57. package/dist/lib/oidc-session-storage.js.map +1 -1
  58. package/dist/lib/pi-adapter/auth.js +9 -15
  59. package/dist/lib/pi-adapter/auth.js.map +1 -1
  60. package/dist/lib/pi-adapter/branding.js +14 -44
  61. package/dist/lib/pi-adapter/branding.js.map +1 -1
  62. package/dist/lib/pi-adapter/interactive.js +19 -23
  63. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  64. package/dist/lib/pi-adapter/pod-approval.js +239 -1
  65. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  66. package/dist/lib/pi-adapter/pod-mirror.js +64 -59
  67. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  68. package/dist/lib/pi-adapter/pod-native.js +479 -0
  69. package/dist/lib/pi-adapter/pod-native.js.map +1 -0
  70. package/dist/lib/pi-adapter/pod-tools.js +76 -40
  71. package/dist/lib/pi-adapter/pod-tools.js.map +1 -1
  72. package/dist/lib/pi-adapter/runtime.js +11 -66
  73. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  74. package/dist/lib/pi-adapter/session.js +276 -33
  75. package/dist/lib/pi-adapter/session.js.map +1 -1
  76. package/dist/lib/pi-adapter/stream.js.map +1 -1
  77. package/dist/lib/pi-adapter/web-fetch.js +47 -30
  78. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  79. package/dist/lib/pod-chat-store.js +36 -38
  80. package/dist/lib/pod-chat-store.js.map +1 -1
  81. package/dist/lib/pod-data-session.js +97 -30
  82. package/dist/lib/pod-data-session.js.map +1 -1
  83. package/package.json +3 -3
  84. package/vendor/agent-runtime/dist/auto-mode.d.ts +287 -0
  85. package/vendor/agent-runtime/dist/auto-mode.js +1498 -0
  86. package/vendor/agent-runtime/dist/index.d.ts +2 -0
  87. package/vendor/agent-runtime/dist/index.js +2 -0
  88. package/vendor/agent-runtime/dist/runtime.d.ts +47 -0
  89. package/vendor/agent-runtime/dist/runtime.js +36 -0
  90. package/vendor/agent-runtime/dist/turn-controller.d.ts +4 -4
  91. package/vendor/agent-runtime/dist/turn-controller.js +7 -7
  92. package/vendor/agent-runtime/package.json +2 -0
  93. package/dist/lib/watch/archive.js +0 -110
  94. package/dist/lib/watch/archive.js.map +0 -1
  95. package/dist/lib/watch/auth.js.map +0 -1
  96. package/dist/lib/watch/codex-composer.js.map +0 -1
  97. package/dist/lib/watch/codex-footer.js.map +0 -1
  98. package/dist/lib/watch/codex-overlay.js.map +0 -1
  99. package/dist/lib/watch/codex-request-form.js.map +0 -1
  100. package/dist/lib/watch/codex-request-input.js.map +0 -1
  101. package/dist/lib/watch/display.js.map +0 -1
  102. package/dist/lib/watch/format.js.map +0 -1
  103. package/dist/lib/watch/hooks/claude.js.map +0 -1
  104. package/dist/lib/watch/hooks/codebuddy.js.map +0 -1
  105. package/dist/lib/watch/hooks/codex.js +0 -17
  106. package/dist/lib/watch/hooks/codex.js.map +0 -1
  107. package/dist/lib/watch/hooks/index.js +0 -24
  108. package/dist/lib/watch/hooks/index.js.map +0 -1
  109. package/dist/lib/watch/hooks/shared.js.map +0 -1
  110. package/dist/lib/watch/index.js.map +0 -1
  111. package/dist/lib/watch/pod-ai.js +0 -160
  112. package/dist/lib/watch/pod-ai.js.map +0 -1
  113. package/dist/lib/watch/pod-approval.js.map +0 -1
  114. package/dist/lib/watch/pod-persistence.js +0 -334
  115. package/dist/lib/watch/pod-persistence.js.map +0 -1
  116. package/dist/lib/watch/runner.js.map +0 -1
  117. package/dist/lib/watch/secretary.js.map +0 -1
  118. package/dist/watch-cli.js +0 -116
  119. package/dist/watch-cli.js.map +0 -1
  120. /package/dist/lib/{watch → auto-mode}/codex-footer.js +0 -0
  121. /package/dist/lib/{watch → auto-mode}/codex-overlay.js +0 -0
  122. /package/dist/lib/{watch → auto-mode}/codex-request-input.js +0 -0
  123. /package/dist/lib/{watch → auto-mode}/hooks/shared.js +0 -0
  124. /package/dist/lib/{watch → auto-mode}/index.js +0 -0
  125. /package/dist/lib/{watch → auto-mode}/types.js +0 -0
@@ -1,28 +1,30 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { setTimeout as delay } from 'node:timers/promises';
3
- import { buildAcpPermissionResponse, buildWatchUserInputResponse, MAX_WATCH_SECRETARY_REACTION_WINDOW_MS, MIN_WATCH_SECRETARY_REACTION_WINDOW_MS, normalizeAcpInteractionRequest, normalizeAcpRequest, normalizeAcpSessionNotification, normalizeWatchCredentialSource, parseWatchJsonLine, resolveWatchCredentialSourceResolution, shouldAttemptCloudCredentialProbe, watchApprovalDecisionLabel, watchUserInputAnswersSummary, } 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, resolveExistingRemoteWatchGrant, resolveRemoteWatchApproval, waitForRemoteWatchApproval, } from './pod-approval.js';
10
- import { persistWatchConversationToPod } from './pod-persistence.js';
3
+ import { buildAcpPermissionResponse, buildAutoModeUserInputResponse, MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, normalizeAcpInteractionRequest, normalizeAcpRequest, normalizeAcpSessionNotification, parseAutoModeJsonLine, autoModeApprovalDecisionLabel, autoModeUserInputAnswersSummary, } from '../../../vendor/agent-runtime/dist/auto-mode.js';
4
+ import { adoptAutoModeSessionId, appendAutoModeEvent, createAutoModeSession, finishAutoModeSession, loadAutoModeEvents, loadAutoModeSession, listAutoModeSessions, writeAutoModeSession, } from './archive.js';
5
+ import { detectAutoModeAuthFailure, preflightAutoModeAuth } from './auth.js';
6
+ import { createAutoModeDisplay } from './display.js';
7
+ import { formatAutoModeSessionSummary } from './format.js';
8
+ import { describeAutoModeMode, getAutoModeHook, listAutoModeHooks } from './hooks/index.js';
9
+ import { createRemoteAutoModeApproval, isRemoteApprovalAbortError, resolveExistingRemoteAutoModeGrant, resolveRemoteAutoModeApproval, waitForRemoteAutoModeApproval, } from './pod-approval.js';
10
+ import { persistAutoModeConversationToPod } from './pod-persistence.js';
11
11
  import { loadPodBackendCredential, podCredentialMissingMessage } from './pod-ai.js';
12
- import { resolveWatchSecretaryRecommendation } from './secretary.js';
12
+ import { resolveAutoModeSecretaryRecommendation } from './secretary.js';
13
13
  import { promptText } from '../prompt.js';
14
- const WATCH_SECRETARY_COUNTDOWN_BAR_WIDTH = 10;
15
- const WATCH_SECRETARY_COUNTDOWN_TICK_MS = 250;
16
- export const watchRuntime = {
14
+ import { runLinxLoginCommand, runLinxLogoutCommand } from '../login-command.js';
15
+ import { clearDefaultPodDataSession } from '../pod-data-session.js';
16
+ const AUTO_MODE_SECRETARY_COUNTDOWN_BAR_WIDTH = 10;
17
+ const AUTO_MODE_SECRETARY_COUNTDOWN_TICK_MS = 250;
18
+ export const autoModeRuntime = {
17
19
  promptText,
18
- preflightWatchAuth,
20
+ preflightAutoModeAuth,
19
21
  loadPodBackendCredential,
20
- createRemoteWatchApproval,
21
- resolveExistingRemoteWatchGrant,
22
- waitForRemoteWatchApproval,
23
- resolveRemoteWatchApproval,
24
- persistWatchConversationToPod,
25
- resolveWatchSecretaryRecommendation,
22
+ createRemoteAutoModeApproval,
23
+ resolveExistingRemoteAutoModeGrant,
24
+ waitForRemoteAutoModeApproval,
25
+ resolveRemoteAutoModeApproval,
26
+ persistAutoModeConversationToPod,
27
+ resolveAutoModeSecretaryRecommendation,
26
28
  };
27
29
  function createLineSplitter(stream, onLine) {
28
30
  let buffer = '';
@@ -57,7 +59,7 @@ function appendEntry(record, stream, line, events) {
57
59
  line,
58
60
  events,
59
61
  };
60
- appendWatchEvent(record, entry);
62
+ appendAutoModeEvent(record, entry);
61
63
  }
62
64
  function appendSessionNote(record, message, raw) {
63
65
  appendEntry(record, 'system', JSON.stringify({
@@ -98,7 +100,7 @@ async function promptApproval(display, message, allowSessionOption = true, signa
98
100
  function approvalPromptLines(message, recommendation) {
99
101
  const lines = [`[approval] ${message}`];
100
102
  if (recommendation?.decision) {
101
- const label = watchApprovalDecisionLabel(recommendation.decision);
103
+ const label = autoModeApprovalDecisionLabel(recommendation.decision);
102
104
  const confidence = typeof recommendation.confidence === 'number'
103
105
  ? ` · confidence ${Math.round(recommendation.confidence * 100)}%`
104
106
  : '';
@@ -108,7 +110,7 @@ function approvalPromptLines(message, recommendation) {
108
110
  lines.push(`[secretary] ${recommendation.reason}`);
109
111
  }
110
112
  if (recommendation?.canAutoDecide && recommendation.decision && (recommendation.reactionWindowMs ?? 0) > 0) {
111
- lines.push(`[secretary] auto-selects ${watchApprovalDecisionLabel(recommendation.decision)} after ${formatReactionWindow(recommendation.reactionWindowMs)}`);
113
+ lines.push(`[secretary] auto-selects ${autoModeApprovalDecisionLabel(recommendation.decision)} after ${formatReactionWindow(recommendation.reactionWindowMs)}`);
112
114
  }
113
115
  return lines;
114
116
  }
@@ -118,9 +120,9 @@ function resolveSecretaryReactionWindowMs(recommendation) {
118
120
  }
119
121
  const reactionWindowMs = recommendation.reactionWindowMs ?? 0;
120
122
  if (recommendation.source === 'fallback') {
121
- return Math.max(0, Math.min(MAX_WATCH_SECRETARY_REACTION_WINDOW_MS, reactionWindowMs));
123
+ return Math.max(0, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, reactionWindowMs));
122
124
  }
123
- return Math.max(MIN_WATCH_SECRETARY_REACTION_WINDOW_MS, Math.min(MAX_WATCH_SECRETARY_REACTION_WINDOW_MS, reactionWindowMs > 0 ? reactionWindowMs : MIN_WATCH_SECRETARY_REACTION_WINDOW_MS));
125
+ return Math.max(MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, reactionWindowMs > 0 ? reactionWindowMs : MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS));
124
126
  }
125
127
  export const __testResolveSecretaryReactionWindowMs = resolveSecretaryReactionWindowMs;
126
128
  function approvalPromptOptions(allowSessionOption, recommendedDecision) {
@@ -158,7 +160,7 @@ async function promptApprovalWithRecommendation(display, message, recommendation
158
160
  : undefined,
159
161
  onAuto: () => {
160
162
  onAuto?.();
161
- display.showActivity(`AI secretary selected ${watchApprovalDecisionLabel(recommendation.decision)} | ${recommendation.reason ?? 'auto decision'}`, 'success');
163
+ display.showActivity(`AI secretary selected ${autoModeApprovalDecisionLabel(recommendation.decision)} | ${recommendation.reason ?? 'auto decision'}`, 'success');
162
164
  },
163
165
  });
164
166
  }
@@ -181,11 +183,11 @@ async function promptWithAutoDefault(options) {
181
183
  const progressTimer = options.onProgress
182
184
  ? setInterval(() => {
183
185
  const remainingMs = Math.max(0, options.reactionWindowMs - Math.max(0, Date.now() - startedAt));
184
- options.onProgress?.(formatWatchSecretaryCountdownDetail(remainingMs, options.reactionWindowMs));
185
- }, WATCH_SECRETARY_COUNTDOWN_TICK_MS)
186
+ options.onProgress?.(formatAutoModeSecretaryCountdownDetail(remainingMs, options.reactionWindowMs));
187
+ }, AUTO_MODE_SECRETARY_COUNTDOWN_TICK_MS)
186
188
  : null;
187
189
  if (options.onProgress) {
188
- options.onProgress(formatWatchSecretaryCountdownDetail(options.reactionWindowMs, options.reactionWindowMs));
190
+ options.onProgress(formatAutoModeSecretaryCountdownDetail(options.reactionWindowMs, options.reactionWindowMs));
189
191
  }
190
192
  void promptPromise.catch(() => undefined);
191
193
  try {
@@ -206,30 +208,74 @@ function formatReactionWindow(ms) {
206
208
  const seconds = Math.max(0, Math.ceil((ms ?? 0) / 1000));
207
209
  return `${seconds}s`;
208
210
  }
209
- export function formatWatchSecretaryCountdownDetail(remainingMs, durationMs) {
211
+ export function formatAutoModeSecretaryCountdownDetail(remainingMs, durationMs) {
210
212
  const totalMs = Math.max(1, durationMs);
211
213
  const clampedRemainingMs = Math.max(0, Math.min(totalMs, remainingMs));
212
- const filled = Math.max(0, Math.min(WATCH_SECRETARY_COUNTDOWN_BAR_WIDTH, Math.ceil((clampedRemainingMs / totalMs) * WATCH_SECRETARY_COUNTDOWN_BAR_WIDTH)));
213
- const bar = `${'#'.repeat(filled)}${'-'.repeat(WATCH_SECRETARY_COUNTDOWN_BAR_WIDTH - filled)}`;
214
+ const filled = Math.max(0, Math.min(AUTO_MODE_SECRETARY_COUNTDOWN_BAR_WIDTH, Math.ceil((clampedRemainingMs / totalMs) * AUTO_MODE_SECRETARY_COUNTDOWN_BAR_WIDTH)));
215
+ const bar = `${'#'.repeat(filled)}${'-'.repeat(AUTO_MODE_SECRETARY_COUNTDOWN_BAR_WIDTH - filled)}`;
214
216
  return `auto [${bar}] ${formatReactionWindow(clampedRemainingMs)}`;
215
217
  }
216
- async function promptAuthContinue(display, lines) {
218
+ async function promptLinxCloudAuth(display, lines, reason = 'manual') {
217
219
  while (true) {
218
- display.setPhase('question', 'Authentication required');
219
- const answer = (await display.chooseOption('Authentication required', lines, [
220
- { label: 'Continue', value: 'continue', shortcuts: ['c', 'y'] },
221
- { label: 'Cancel', value: 'cancel', shortcuts: ['n', 'x'] },
220
+ display.setPhase('question', reason === 'expired' ? 'LinX Cloud login expired' : 'LinX Cloud login required');
221
+ const answer = (await display.chooseOption(reason === 'expired' ? 'LinX Cloud login expired' : 'LinX Cloud login required', lines, [
222
+ { label: 'Authorize in browser', value: 'browser', description: 'refresh the LinX Cloud Solid session', shortcuts: ['b', '1'] },
223
+ { label: 'Logout', value: 'logout', description: 'clear the current LinX Cloud session', shortcuts: ['l', '2'] },
224
+ { label: 'Exit', value: 'exit', description: 'leave auto-mode', shortcuts: ['x', '3'] },
222
225
  ])).trim().toLowerCase();
223
- if (answer === 'continue' || answer === 'c' || answer === 'y' || answer === 'yes') {
224
- display.setPhase('running', 'Continuing turn');
225
- return true;
226
+ if (answer === 'browser' || answer === 'b' || answer === '1') {
227
+ await runBackendLinxLogin(display);
228
+ return 'retry';
226
229
  }
227
- if (answer === 'cancel' || answer === 'n' || answer === 'x' || answer === 'no') {
228
- display.setPhase('running', 'Continuing turn');
229
- return false;
230
+ if (answer === 'logout' || answer === 'l' || answer === '2') {
231
+ runBackendLinxLogout(display);
232
+ continue;
233
+ }
234
+ if (answer === 'exit' || answer === 'x' || answer === '3' || answer === 'cancel') {
235
+ display.setPhase('running', 'Authentication cancelled');
236
+ return 'cancel';
230
237
  }
231
238
  }
232
239
  }
240
+ async function runBackendLinxLogin(display) {
241
+ display.showActivity('Opening LinX Cloud login in your browser...');
242
+ await runLinxLoginCommand({}, {
243
+ promptText: autoModeRuntime.promptText,
244
+ write(chunk) {
245
+ for (const line of chunk.split(/\r?\n/u)) {
246
+ const trimmed = line.trim();
247
+ if (trimmed) {
248
+ display.showActivity(trimmed);
249
+ }
250
+ }
251
+ },
252
+ });
253
+ clearDefaultPodDataSession();
254
+ display.showActivity('LinX Cloud login refreshed.', 'success');
255
+ }
256
+ function runBackendLinxLogout(display) {
257
+ runLinxLogoutCommand({
258
+ write(chunk) {
259
+ for (const line of chunk.split(/\r?\n/u)) {
260
+ const trimmed = line.trim();
261
+ if (trimmed) {
262
+ display.showActivity(trimmed);
263
+ }
264
+ }
265
+ },
266
+ });
267
+ clearDefaultPodDataSession();
268
+ display.showActivity('Use /login or choose browser authorization to sign in again.', 'note');
269
+ }
270
+ function isRecoverableLinxCloudAuthError(message) {
271
+ const normalized = message.toLowerCase();
272
+ return normalized.includes('linx login')
273
+ || normalized.includes('linx cloud login expired')
274
+ || normalized.includes('linx cloud credential source is not connected')
275
+ || normalized.includes('failed to restore oidc access token')
276
+ || normalized.includes('invalid solid token')
277
+ || normalized.includes('unauthorized');
278
+ }
233
279
  function approvalPromptMessage(request) {
234
280
  if (request.kind === 'command-approval') {
235
281
  return request.command ? `Approve command: ${request.command}` : 'Approve command execution';
@@ -248,15 +294,18 @@ function appendUserTurn(record, text) {
248
294
  function appendTurnStart(record, command, args) {
249
295
  appendEntry(record, 'system', JSON.stringify({ type: 'turn.start', command, args }), []);
250
296
  }
251
- function requestedCredentialSource(options) {
252
- return normalizeWatchCredentialSource(options.credentialSource);
297
+ function requestedCredentialSource(_options) {
298
+ return 'cloud';
253
299
  }
254
- function requestedApprovalSource(options) {
255
- return options.approvalSource ?? 'hybrid';
300
+ function requestedApprovalSource() {
301
+ return 'hybrid';
256
302
  }
257
303
  function requestedRuntime(options) {
258
304
  return options.runtime ?? 'local';
259
305
  }
306
+ function requestedAutoModeMode(options) {
307
+ return options.autoModeEnabled ? 'auto' : options.mode;
308
+ }
260
309
  function normalizeBackendCommandEnv(backend, env) {
261
310
  if (!env) {
262
311
  return undefined;
@@ -281,25 +330,38 @@ function syncRecordFromOptions(record, options, plan) {
281
330
  return {
282
331
  backend: options.backend,
283
332
  runtime: requestedRuntime(options),
284
- mode: options.mode,
333
+ mode: requestedAutoModeMode(options),
285
334
  cwd: options.cwd,
286
335
  model: options.model,
287
336
  prompt: options.prompt,
288
337
  passthroughArgs: [...options.passthroughArgs],
289
338
  credentialSource: requestedCredentialSource(options),
290
339
  resolvedCredentialSource: options.resolvedCredentialSource,
291
- approvalSource: requestedApprovalSource(options),
340
+ approvalSource: requestedApprovalSource(),
292
341
  command: plan.command,
293
342
  args: [...plan.args],
294
343
  transport: options.transport ?? 'acp',
295
344
  };
296
345
  }
346
+ function extractAcpSessionId(response) {
347
+ if (typeof response.sessionId === 'string' && response.sessionId.trim()) {
348
+ return response.sessionId;
349
+ }
350
+ const nestedSession = response.session;
351
+ if (typeof nestedSession === 'object'
352
+ && nestedSession !== null
353
+ && typeof nestedSession.id === 'string'
354
+ && nestedSession.id.trim()) {
355
+ return nestedSession.id;
356
+ }
357
+ return null;
358
+ }
297
359
  function withResolvedSource(options, resolvedCredentialSource, commandEnv) {
298
360
  return {
299
361
  ...options,
362
+ mode: requestedAutoModeMode(options),
300
363
  transport: options.transport ?? 'acp',
301
364
  credentialSource: requestedCredentialSource(options),
302
- approvalSource: requestedApprovalSource(options),
303
365
  resolvedCredentialSource,
304
366
  commandEnv,
305
367
  };
@@ -308,67 +370,21 @@ async function probeCloudCredentialSource(backend, runtime) {
308
370
  try {
309
371
  const podCredential = await runtime.loadPodBackendCredential(backend);
310
372
  if (!podCredential) {
311
- return {
312
- probe: {
313
- status: 'unavailable',
314
- message: podCredentialMissingMessage(backend),
315
- },
316
- };
373
+ throw new Error(podCredentialMissingMessage(backend));
317
374
  }
318
375
  return {
319
- probe: { status: 'available' },
320
376
  commandEnv: normalizeBackendCommandEnv(backend, { ...podCredential.env }),
321
377
  };
322
378
  }
323
379
  catch (error) {
324
- return {
325
- probe: {
326
- status: 'error',
327
- message: error instanceof Error ? error.message : String(error),
328
- },
329
- };
380
+ throw error instanceof Error ? error : new Error(String(error));
330
381
  }
331
382
  }
332
- export async function resolveWatchRunOptions(options, runtime = watchRuntime) {
333
- const source = requestedCredentialSource(options);
334
- if (source === 'cloud') {
335
- const { probe, commandEnv } = await probeCloudCredentialSource(options.backend, runtime);
336
- const resolution = resolveWatchCredentialSourceResolution({
337
- requestedSource: source,
338
- localAuthStatus: { state: 'unknown' },
339
- cloudCredentialProbe: probe,
340
- });
341
- if (resolution.error) {
342
- throw new Error(resolution.error);
343
- }
344
- return {
345
- options: withResolvedSource(options, resolution.resolvedSource ?? 'cloud', commandEnv),
346
- authPreflight: resolution.authStatus,
347
- };
348
- }
349
- const localOptions = withResolvedSource(options, 'local');
350
- const authPreflight = await runtime.preflightWatchAuth(options.backend);
351
- let cloudCredentialProbe;
352
- let commandEnv;
353
- if (shouldAttemptCloudCredentialProbe(source, authPreflight)) {
354
- const cloudResult = await probeCloudCredentialSource(options.backend, runtime);
355
- cloudCredentialProbe = cloudResult.probe;
356
- commandEnv = cloudResult.commandEnv;
357
- }
358
- const resolution = resolveWatchCredentialSourceResolution({
359
- requestedSource: source,
360
- localAuthStatus: authPreflight,
361
- cloudCredentialProbe,
362
- defaultLocalMessage: `${options.backend} is not authenticated`,
363
- });
364
- if (resolution.error) {
365
- throw new Error(resolution.error);
366
- }
383
+ export async function resolveAutoRunOptions(options, runtime = autoModeRuntime) {
384
+ const { commandEnv } = await probeCloudCredentialSource(options.backend, runtime);
367
385
  return {
368
- options: resolution.resolvedSource === 'cloud'
369
- ? withResolvedSource(options, 'cloud', commandEnv)
370
- : localOptions,
371
- authPreflight: resolution.authStatus,
386
+ options: withResolvedSource(options, 'cloud', commandEnv),
387
+ authPreflight: { state: 'authenticated' },
372
388
  };
373
389
  }
374
390
  class BaseSession {
@@ -381,7 +397,7 @@ class BaseSession {
381
397
  lastExit = null;
382
398
  constructor(record, prompt) {
383
399
  this.record = record;
384
- this.display = createWatchDisplay(record, prompt);
400
+ this.display = createAutoModeDisplay(record, prompt);
385
401
  }
386
402
  spawnProcess(command, args, cwd, env) {
387
403
  this.activeExitPromise = new Promise((resolve) => {
@@ -414,7 +430,7 @@ class BaseSession {
414
430
  }
415
431
  async finalizeAndClose(status, error) {
416
432
  const exitState = await this.waitForActiveExit();
417
- const next = finishWatchSession(this.record, {
433
+ const next = finishAutoModeSession(this.record, {
418
434
  status,
419
435
  exitCode: exitState.code,
420
436
  signal: exitState.signal,
@@ -433,7 +449,11 @@ class BaseSession {
433
449
  }
434
450
  updateRecord(updates) {
435
451
  Object.assign(this.record, updates);
436
- writeWatchSession(this.record);
452
+ writeAutoModeSession(this.record);
453
+ this.display.updateRecord(this.record);
454
+ }
455
+ adoptSessionId(sessionId) {
456
+ adoptAutoModeSessionId(this.record, sessionId);
437
457
  this.display.updateRecord(this.record);
438
458
  }
439
459
  waitForActiveExit() {
@@ -474,7 +494,7 @@ class AcpSession extends BaseSession {
474
494
  activeAgentRequests = 0;
475
495
  constructor(options, hook) {
476
496
  const plan = hook.buildSpawnPlan(options);
477
- super(createWatchSession({ ...options, transport: options.transport ?? 'acp' }, plan), watchRuntime.promptText);
497
+ super(createAutoModeSession({ ...options, transport: options.transport ?? 'acp' }, plan), autoModeRuntime.promptText);
478
498
  this.hook = hook;
479
499
  this.options = options;
480
500
  }
@@ -497,19 +517,22 @@ class AcpSession extends BaseSession {
497
517
  version: '0.1.0',
498
518
  },
499
519
  });
500
- const newSession = await this.sendRequest('session/new', {
501
- cwd: this.options.cwd,
502
- mcpServers: [],
503
- });
504
- const sessionId = typeof newSession.sessionId === 'string'
505
- ? newSession.sessionId
506
- : typeof newSession.session?.id === 'string'
507
- ? newSession.session.id
508
- : null;
520
+ const sessionResponse = this.options.resumeSessionId
521
+ ? await this.sendRequest('session/resume', {
522
+ cwd: this.options.cwd,
523
+ mcpServers: [],
524
+ sessionId: this.options.resumeSessionId,
525
+ })
526
+ : await this.sendRequest('session/new', {
527
+ cwd: this.options.cwd,
528
+ mcpServers: [],
529
+ });
530
+ const sessionId = extractAcpSessionId(sessionResponse) ?? this.options.resumeSessionId ?? null;
509
531
  if (!sessionId) {
510
532
  throw new Error(`ACP backend ${this.options.backend} did not return a session id`);
511
533
  }
512
534
  this.sessionId = sessionId;
535
+ this.adoptSessionId(sessionId);
513
536
  this.updateRecord({
514
537
  backendSessionId: sessionId,
515
538
  error: undefined,
@@ -552,7 +575,7 @@ class AcpSession extends BaseSession {
552
575
  throw new Error('ACP session is not initialized');
553
576
  }
554
577
  if (this.turnState) {
555
- throw new Error('A watch turn is already in progress');
578
+ throw new Error('An auto-mode turn is already in progress');
556
579
  }
557
580
  appendUserTurn(this.record, text);
558
581
  appendTurnStart(this.record, this.record.command, this.record.args);
@@ -652,7 +675,7 @@ class AcpSession extends BaseSession {
652
675
  this.scheduleTurnSettle();
653
676
  }
654
677
  handleLine(line, stream) {
655
- const authFailure = detectWatchAuthFailure(this.record.backend, line);
678
+ const authFailure = detectAutoModeAuthFailure(this.record.backend, line);
656
679
  if (authFailure) {
657
680
  this.authFailureMessage = authFailure.message;
658
681
  }
@@ -661,7 +684,7 @@ class AcpSession extends BaseSession {
661
684
  this.markTurnActivity();
662
685
  return;
663
686
  }
664
- const message = parseWatchJsonLine(line);
687
+ const message = parseAutoModeJsonLine(line);
665
688
  if (!message) {
666
689
  this.recordParsedLine(stream, line, []);
667
690
  this.markTurnActivity();
@@ -718,7 +741,7 @@ class AcpSession extends BaseSession {
718
741
  }
719
742
  this.pendingRequests.delete(id);
720
743
  if ('error' in message && message.error) {
721
- const authFailure = detectWatchAuthFailure(this.record.backend, JSON.stringify(message));
744
+ const authFailure = detectAutoModeAuthFailure(this.record.backend, JSON.stringify(message));
722
745
  if (authFailure) {
723
746
  this.authFailureMessage = authFailure.message;
724
747
  pending.reject(new Error(authFailure.message));
@@ -744,9 +767,10 @@ class AcpSession extends BaseSession {
744
767
  const lines = [
745
768
  `[note] ${typeof params.message === 'string' ? params.message : 'Authentication required'}`,
746
769
  ...(typeof params.url === 'string' ? [`[note] ${params.url}`] : []),
770
+ '[note] Backend credentials are resolved from LinX Cloud Pod settings, not local backend login.',
747
771
  ];
748
- const shouldContinue = await promptAuthContinue(this.display, lines);
749
- if (!shouldContinue) {
772
+ const authAction = await promptLinxCloudAuth(this.display, lines, 'expired');
773
+ if (authAction !== 'retry') {
750
774
  this.authFailureMessage = 'Authentication request cancelled by user';
751
775
  }
752
776
  this.sendResponse(id, {});
@@ -778,13 +802,13 @@ class AcpSession extends BaseSession {
778
802
  const recommendation = await this.resolveSecretaryRecommendation(interaction);
779
803
  const answers = await this.resolveToolUserInputAnswers(interaction.questions, recommendation?.kind === 'user-input' ? recommendation : null);
780
804
  this.display.setPhase('running', 'Continuing turn');
781
- return buildWatchUserInputResponse(answers);
805
+ return buildAutoModeUserInputResponse(answers);
782
806
  }
783
807
  async resolveToolUserInputAnswers(questions, recommendation) {
784
808
  this.display.setPhase('question', questions[0]?.header ?? 'Input required');
785
809
  if (!recommendation?.answers || !recommendation.canAutoDecide) {
786
810
  if (recommendation?.answers) {
787
- this.display.showActivity(`AI secretary suggests: ${watchUserInputAnswersSummary(recommendation.answers)}`);
811
+ this.display.showActivity(`AI secretary suggests: ${autoModeUserInputAnswersSummary(recommendation.answers)}`);
788
812
  }
789
813
  return this.display.chooseQuestions(questions);
790
814
  }
@@ -796,7 +820,7 @@ class AcpSession extends BaseSession {
796
820
  const useAiAnswer = await promptWithAutoDefault({
797
821
  fallback: (signal) => this.display.chooseOption('Input required', [
798
822
  `[input] ${questions[0]?.question ?? 'Input required'}`,
799
- `[secretary] suggests ${watchUserInputAnswersSummary(recommendation.answers)}`,
823
+ `[secretary] suggests ${autoModeUserInputAnswersSummary(recommendation.answers)}`,
800
824
  ...(recommendation.reason ? [`[secretary] ${recommendation.reason}`] : []),
801
825
  ...(displayRecommendation.reactionWindowMs ? [`[secretary] auto-uses this answer after ${formatReactionWindow(displayRecommendation.reactionWindowMs)}`] : []),
802
826
  ], [
@@ -808,7 +832,7 @@ class AcpSession extends BaseSession {
808
832
  onProgress: reactionWindowMs > 0
809
833
  ? (detail) => this.display.setPhase('question', detail)
810
834
  : undefined,
811
- onAuto: () => this.display.showActivity(`AI secretary answered input | ${watchUserInputAnswersSummary(recommendation.answers)}`, 'success'),
835
+ onAuto: () => this.display.showActivity(`AI secretary answered input | ${autoModeUserInputAnswersSummary(recommendation.answers)}`, 'success'),
812
836
  });
813
837
  if (useAiAnswer === 'use' || useAiAnswer === 'u' || useAiAnswer === 'y' || useAiAnswer === 'yes') {
814
838
  return recommendation.answers;
@@ -816,30 +840,21 @@ class AcpSession extends BaseSession {
816
840
  return this.display.chooseQuestions(questions);
817
841
  }
818
842
  async resolveApproval(interaction) {
819
- const source = this.options.approvalSource ?? 'hybrid';
820
- if (source !== 'local') {
821
- const granted = await watchRuntime.resolveExistingRemoteWatchGrant({
822
- record: this.record,
823
- request: interaction,
824
- }).catch(() => null);
825
- if (granted) {
826
- appendSessionNote(this.record, `Existing grant covered approval | ${watchApprovalDecisionLabel(granted)}`);
827
- this.display.showActivity(`Existing grant covered approval | ${watchApprovalDecisionLabel(granted)}`, 'success');
828
- this.display.setPhase('running', 'Continuing turn');
829
- return granted;
830
- }
843
+ const granted = await autoModeRuntime.resolveExistingRemoteAutoModeGrant({
844
+ record: this.record,
845
+ request: interaction,
846
+ }).catch(() => null);
847
+ if (granted) {
848
+ appendSessionNote(this.record, `Existing grant covered approval | ${autoModeApprovalDecisionLabel(granted)}`);
849
+ this.display.showActivity(`Existing grant covered approval | ${autoModeApprovalDecisionLabel(granted)}`, 'success');
850
+ this.display.setPhase('running', 'Continuing turn');
851
+ return granted;
831
852
  }
832
853
  const recommendation = await this.resolveSecretaryRecommendation(interaction);
833
- if (source === 'local') {
834
- return promptApprovalWithRecommendation(this.display, approvalPromptMessage(interaction), recommendation?.kind === interaction.kind ? recommendation : null);
835
- }
836
- if (source === 'remote') {
837
- return this.resolveRemoteOnlyApproval(interaction, recommendation?.kind === interaction.kind ? recommendation : null);
838
- }
839
854
  return this.resolveHybridApproval(interaction, recommendation?.kind === interaction.kind ? recommendation : null);
840
855
  }
841
856
  async resolveSecretaryRecommendation(interaction) {
842
- return watchRuntime.resolveWatchSecretaryRecommendation({
857
+ return autoModeRuntime.resolveAutoModeSecretaryRecommendation({
843
858
  mode: this.options.mode,
844
859
  record: this.record,
845
860
  request: interaction,
@@ -849,11 +864,11 @@ class AcpSession extends BaseSession {
849
864
  const promptMessage = approvalPromptMessage(interaction);
850
865
  appendSessionNote(this.record, `Waiting for remote approval | ${promptMessage}`);
851
866
  this.display.setPhase('approval', `${promptMessage} · remote`);
852
- const remote = await watchRuntime.createRemoteWatchApproval({
867
+ const remote = await autoModeRuntime.createRemoteAutoModeApproval({
853
868
  record: this.record,
854
869
  request: interaction,
855
870
  });
856
- const remoteDecisionPromise = watchRuntime.waitForRemoteWatchApproval({
871
+ const remoteDecisionPromise = autoModeRuntime.waitForRemoteAutoModeApproval({
857
872
  approvalId: remote.id,
858
873
  approvalUri: remote.approvalUri,
859
874
  });
@@ -868,8 +883,8 @@ class AcpSession extends BaseSession {
868
883
  const decision = await Promise.race([
869
884
  remoteDecisionPromise,
870
885
  delay(reactionWindowMs).then(async () => {
871
- this.display.showActivity(`AI secretary selected ${watchApprovalDecisionLabel(recommendation.decision)} | ${recommendation.reason ?? 'auto decision'}`, 'success');
872
- await watchRuntime.resolveRemoteWatchApproval({
886
+ this.display.showActivity(`AI secretary selected ${autoModeApprovalDecisionLabel(recommendation.decision)} | ${recommendation.reason ?? 'auto decision'}`, 'success');
887
+ await autoModeRuntime.resolveRemoteAutoModeApproval({
873
888
  approvalId: remote.id,
874
889
  approvalUri: remote.approvalUri,
875
890
  decision: recommendation.decision,
@@ -887,7 +902,7 @@ class AcpSession extends BaseSession {
887
902
  const promptMessage = approvalPromptMessage(interaction);
888
903
  let remoteApproval = null;
889
904
  try {
890
- remoteApproval = await watchRuntime.createRemoteWatchApproval({
905
+ remoteApproval = await autoModeRuntime.createRemoteAutoModeApproval({
891
906
  record: this.record,
892
907
  request: interaction,
893
908
  });
@@ -895,7 +910,10 @@ class AcpSession extends BaseSession {
895
910
  }
896
911
  catch (error) {
897
912
  appendSessionNote(this.record, `Remote approval unavailable | ${error instanceof Error ? error.message : String(error)}`);
898
- return promptApprovalWithRecommendation(this.display, promptMessage, recommendation);
913
+ const decision = await promptApprovalWithRecommendation(this.display, promptMessage, recommendation);
914
+ appendSessionNote(this.record, `Local approval resolved | ${decision}`);
915
+ this.display.setPhase('running', 'Continuing turn');
916
+ return decision;
899
917
  }
900
918
  const localAbort = new AbortController();
901
919
  const remoteAbort = new AbortController();
@@ -904,7 +922,7 @@ class AcpSession extends BaseSession {
904
922
  secretaryAutoResolved = true;
905
923
  })
906
924
  .then((decision) => ({ source: 'local', decision }));
907
- const remoteDecisionPromise = watchRuntime.waitForRemoteWatchApproval({
925
+ const remoteDecisionPromise = autoModeRuntime.waitForRemoteAutoModeApproval({
908
926
  approvalId: remoteApproval.id,
909
927
  approvalUri: remoteApproval.approvalUri,
910
928
  signal: remoteAbort.signal,
@@ -916,14 +934,14 @@ class AcpSession extends BaseSession {
916
934
  if (winner.source === 'local') {
917
935
  remoteAbort.abort();
918
936
  appendSessionNote(this.record, `Local approval resolved | ${winner.decision}`);
919
- void watchRuntime.resolveRemoteWatchApproval({
937
+ void autoModeRuntime.resolveRemoteAutoModeApproval({
920
938
  approvalId: remoteApproval.id,
921
939
  approvalUri: remoteApproval.approvalUri,
922
940
  decision: winner.decision,
923
941
  decisionRole: secretaryAutoResolved ? 'secretary' : 'human',
924
942
  note: secretaryAutoResolved
925
943
  ? (recommendation?.reason ?? 'resolved by AI secretary')
926
- : 'resolved from active local watch session',
944
+ : 'resolved from active local auto-mode session',
927
945
  }).catch(() => undefined);
928
946
  this.display.setPhase('running', 'Continuing turn');
929
947
  return winner.decision;
@@ -993,37 +1011,71 @@ class AcpSession extends BaseSession {
993
1011
  }
994
1012
  }
995
1013
  function buildConversationSession(options) {
996
- const hook = getWatchHook(options.backend);
1014
+ const hook = getAutoModeHook(options.backend);
997
1015
  return new AcpSession(options, hook);
998
1016
  }
999
- async function handleWatchShellCommand(args) {
1017
+ async function handleAutoModeShellCommand(args) {
1000
1018
  const { input, session, display, queueState, backend, record } = args;
1001
1019
  if (input === '/exit' || input === '/quit') {
1002
1020
  return 'exit';
1003
1021
  }
1004
- if (input === '/help') {
1022
+ if (input === '/help' || input === '/hotkeys' || input === '/keymap') {
1005
1023
  display.showHelp();
1006
1024
  return 'handled';
1007
1025
  }
1026
+ if (input === '/login') {
1027
+ try {
1028
+ await runBackendLinxLogin(display);
1029
+ appendSessionNote(record, 'LinX Cloud login refreshed from auto-mode');
1030
+ }
1031
+ catch (error) {
1032
+ const message = error instanceof Error ? error.message : String(error);
1033
+ appendAndDisplaySessionNote(record, display, `LinX Cloud login failed | ${message}`, 'error', { error: message });
1034
+ }
1035
+ return 'handled';
1036
+ }
1037
+ if (input === '/logout') {
1038
+ runBackendLinxLogout(display);
1039
+ appendSessionNote(record, 'LinX Cloud logout requested from auto-mode');
1040
+ return 'handled';
1041
+ }
1008
1042
  if (input === '/session') {
1009
1043
  appendAndDisplaySessionNote(record, display, [
1010
- `session=${record.id}`,
1044
+ `session=${record.backendSessionId ?? record.id}`,
1011
1045
  `backend=${record.backend}`,
1012
1046
  `runtime=${record.runtime}`,
1013
- `source=${record.resolvedCredentialSource ?? record.credentialSource}`,
1047
+ 'credentials=pod',
1014
1048
  `model=${record.model ?? 'default'}`,
1015
1049
  `cwd=${record.cwd}`,
1016
1050
  ].join(' | '));
1017
1051
  return 'handled';
1018
1052
  }
1053
+ if (input === '/manual' || input === '/smart' || input === '/auto') {
1054
+ const mode = input.slice(1);
1055
+ session.applyResolvedOptions({
1056
+ backend: record.backend,
1057
+ mode,
1058
+ cwd: record.cwd,
1059
+ plain: false,
1060
+ model: record.model,
1061
+ prompt: record.prompt,
1062
+ passthroughArgs: record.passthroughArgs,
1063
+ runtime: record.runtime,
1064
+ transport: record.transport,
1065
+ credentialSource: record.credentialSource,
1066
+ resolvedCredentialSource: record.resolvedCredentialSource,
1067
+ });
1068
+ appendAndDisplaySessionNote(record, display, `Approval mode set to ${mode}`, 'success', { mode });
1069
+ return 'handled';
1070
+ }
1019
1071
  if (input === '/queue') {
1020
1072
  appendAndDisplaySessionNote(record, display, `queue | steer=${queueState.steeringCount} | follow-up=${queueState.followUpCount}`);
1021
1073
  return 'handled';
1022
1074
  }
1023
1075
  if (input === '/sessions') {
1024
- const summaries = listWatchSessions().slice(0, 5).map(formatWatchSessionSummary);
1076
+ const summaries = listAutoModeSessions().slice(0, 5).map(formatAutoModeSessionSummary);
1025
1077
  if (summaries.length === 0) {
1026
- appendAndDisplaySessionNote(record, display, 'No archived watch sessions found');
1078
+ appendAndDisplaySessionNote(record, display, 'No archived auto-mode sessions found');
1027
1079
  return 'handled';
1028
1080
  }
1029
1081
  for (const summary of summaries) {
@@ -1032,7 +1084,7 @@ async function handleWatchShellCommand(args) {
1032
1084
  return 'handled';
1033
1085
  }
1034
1086
  if (input === '/new') {
1035
- appendAndDisplaySessionNote(record, display, 'Use `linx watch run` to start a fresh watch session');
1087
+ appendAndDisplaySessionNote(record, display, 'Use `linx --backend <backend>` to start a fresh auto-mode session');
1036
1088
  return 'handled';
1037
1089
  }
1038
1090
  if (input === '/debug' || input === '/debug on') {
@@ -1063,11 +1115,11 @@ async function handleWatchShellCommand(args) {
1063
1115
  }
1064
1116
  return 'pass';
1065
1117
  }
1066
- export const __testHandleWatchShellCommand = handleWatchShellCommand;
1067
- export async function runWatch(options) {
1068
- const previousPlainEnv = process.env.LINX_WATCH_PLAIN;
1118
+ export const __testHandleAutoModeShellCommand = handleAutoModeShellCommand;
1119
+ export async function runAutoMode(options) {
1120
+ const previousPlainEnv = process.env.LINX_BACKEND_PLAIN;
1069
1121
  if (options.plain) {
1070
- process.env.LINX_WATCH_PLAIN = '1';
1122
+ process.env.LINX_BACKEND_PLAIN = '1';
1071
1123
  }
1072
1124
  let fatalError = null;
1073
1125
  let session = null;
@@ -1075,8 +1127,8 @@ export async function runWatch(options) {
1075
1127
  ...options,
1076
1128
  runtime: requestedRuntime(options),
1077
1129
  transport: 'acp',
1130
+ mode: requestedAutoModeMode(options),
1078
1131
  credentialSource: requestedCredentialSource(options),
1079
- approvalSource: requestedApprovalSource(options),
1080
1132
  };
1081
1133
  try {
1082
1134
  session = buildConversationSession(requestedOptions);
@@ -1089,18 +1141,31 @@ export async function runWatch(options) {
1089
1141
  followUpCount: 0,
1090
1142
  });
1091
1143
  let resolvedRun;
1092
- try {
1093
- resolvedRun = await resolveWatchRunOptions(requestedOptions);
1094
- }
1095
- catch (error) {
1096
- const message = error instanceof Error ? error.message : String(error);
1097
- appendEntry(activeSession.record, 'system', JSON.stringify({
1098
- type: 'credentials.resolve',
1099
- backend: requestedOptions.backend,
1100
- requestedCredentialSource: requestedOptions.credentialSource,
1101
- error: message,
1102
- }), []);
1103
- throw error;
1144
+ for (let attempt = 0;; attempt += 1) {
1145
+ try {
1146
+ resolvedRun = await resolveAutoRunOptions(requestedOptions);
1147
+ break;
1148
+ }
1149
+ catch (error) {
1150
+ const message = error instanceof Error ? error.message : String(error);
1151
+ appendEntry(activeSession.record, 'system', JSON.stringify({
1152
+ type: 'credentials.resolve',
1153
+ backend: requestedOptions.backend,
1154
+ requestedCredentialSource: requestedOptions.credentialSource,
1155
+ error: message,
1156
+ }), []);
1157
+ if (attempt >= 2 || !isRecoverableLinxCloudAuthError(message)) {
1158
+ throw error;
1159
+ }
1160
+ const authAction = await promptLinxCloudAuth(activeSession.display, [
1161
+ message,
1162
+ `Backend ${requestedOptions.backend} reads provider credentials from your LinX Cloud Pod.`,
1163
+ 'Re-authorize LinX Cloud, then LinX will retry backend startup.',
1164
+ ], 'startup');
1165
+ if (authAction !== 'retry') {
1166
+ throw error;
1167
+ }
1168
+ }
1104
1169
  }
1105
1170
  activeSession.applyResolvedOptions(resolvedRun.options);
1106
1171
  appendEntry(activeSession.record, 'system', JSON.stringify({
@@ -1222,7 +1287,7 @@ export async function runWatch(options) {
1222
1287
  if (!trimmed) {
1223
1288
  return;
1224
1289
  }
1225
- const shellCommand = await handleWatchShellCommand({
1290
+ const shellCommand = await handleAutoModeShellCommand({
1226
1291
  input: trimmed,
1227
1292
  session: activeSession,
1228
1293
  display: activeSession.display,
@@ -1291,38 +1356,60 @@ export async function runWatch(options) {
1291
1356
  if (session) {
1292
1357
  await session.close();
1293
1358
  const finalRecord = await session.finalizeAndClose(fatalError ? 'failed' : 'completed', fatalError?.message);
1294
- await watchRuntime.persistWatchConversationToPod(finalRecord).catch(() => undefined);
1359
+ await autoModeRuntime.persistAutoModeConversationToPod(finalRecord).catch((error) => {
1360
+ const message = error instanceof Error ? error.message : String(error);
1361
+ appendSessionNote(finalRecord, `Pod sync failed | ${message}`, { error: message });
1362
+ process.emitWarning(`LinX auto-mode Pod sync failed: ${message}`);
1363
+ });
1295
1364
  }
1296
1365
  if (options.plain) {
1297
1366
  if (previousPlainEnv === undefined) {
1298
- delete process.env.LINX_WATCH_PLAIN;
1367
+ delete process.env.LINX_BACKEND_PLAIN;
1299
1368
  }
1300
1369
  else {
1301
- process.env.LINX_WATCH_PLAIN = previousPlainEnv;
1370
+ process.env.LINX_BACKEND_PLAIN = previousPlainEnv;
1302
1371
  }
1303
1372
  }
1304
1373
  }
1305
1374
  }
1306
- export function listArchivedWatchSessions() {
1307
- return listWatchSessions();
1375
+ export function listArchivedAutoModeSessions() {
1376
+ return listAutoModeSessions();
1377
+ }
1378
+ export function loadArchivedAutoModeSession(id) {
1379
+ return loadAutoModeSession(id);
1308
1380
  }
1309
- export function loadArchivedWatchSession(id) {
1310
- return loadWatchSession(id);
1381
+ export function loadArchivedAutoModeEvents(id) {
1382
+ return loadAutoModeEvents(id);
1311
1383
  }
1312
- export function loadArchivedWatchEvents(id) {
1313
- return loadWatchEvents(id);
1384
+ export function resumeAutoModeSession(record, options = {}) {
1385
+ const sessionId = record.backendSessionId?.trim() || record.id;
1386
+ return runAutoMode({
1387
+ backend: record.backend,
1388
+ mode: record.mode,
1389
+ autoModeEnabled: record.mode === 'auto',
1390
+ resumeSessionId: sessionId,
1391
+ cwd: options.cwd || record.cwd,
1392
+ plain: Boolean(options.plain),
1393
+ model: options.model || record.model,
1394
+ prompt: options.prompt,
1395
+ passthroughArgs: [...record.passthroughArgs],
1396
+ runtime: record.runtime,
1397
+ transport: record.transport ?? 'acp',
1398
+ credentialSource: record.credentialSource,
1399
+ resolvedCredentialSource: record.resolvedCredentialSource,
1400
+ });
1314
1401
  }
1315
- export { formatWatchSessionSummary };
1316
- export function listSupportedWatchBackends() {
1317
- return listWatchHooks().map((hook) => ({
1402
+ export { formatAutoModeSessionSummary };
1403
+ export function listSupportedAutoModeBackends() {
1404
+ return listAutoModeHooks().map((hook) => ({
1318
1405
  backend: hook.id,
1319
1406
  label: hook.label,
1320
1407
  description: hook.description,
1321
1408
  capabilities: hook.capabilities,
1322
1409
  modes: {
1323
- manual: describeWatchMode('manual'),
1324
- smart: describeWatchMode('smart'),
1325
- auto: describeWatchMode('auto'),
1410
+ manual: describeAutoModeMode('manual'),
1411
+ smart: describeAutoModeMode('smart'),
1412
+ auto: describeAutoModeMode('auto'),
1326
1413
  },
1327
1414
  }));
1328
1415
  }