@undefineds.co/linx 0.3.4 → 0.3.7

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 (172) hide show
  1. package/README.md +58 -23
  2. package/dist/generated/version.js +1 -1
  3. package/dist/generated/version.js.map +1 -1
  4. package/dist/index.js +334 -162
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/account-session.js +4 -8
  7. package/dist/lib/account-session.js.map +1 -1
  8. package/dist/lib/ai-command.js +228 -178
  9. package/dist/lib/ai-command.js.map +1 -1
  10. package/dist/lib/auto-mode/archive.js +38 -7
  11. package/dist/lib/auto-mode/archive.js.map +1 -1
  12. package/dist/lib/auto-mode/auth.js.map +1 -1
  13. package/dist/lib/auto-mode/display.js +71 -45
  14. package/dist/lib/auto-mode/display.js.map +1 -1
  15. package/dist/lib/auto-mode/format.js +9 -7
  16. package/dist/lib/auto-mode/format.js.map +1 -1
  17. package/dist/lib/auto-mode/hooks/claude.js +12 -2
  18. package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
  19. package/dist/lib/auto-mode/hooks/codex.js +17 -7
  20. package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
  21. package/dist/lib/auto-mode/hooks/index.js +28 -8
  22. package/dist/lib/auto-mode/hooks/index.js.map +1 -1
  23. package/dist/lib/auto-mode/pod-ai.js +20 -37
  24. package/dist/lib/auto-mode/pod-ai.js.map +1 -1
  25. package/dist/lib/auto-mode/pod-approval.js +124 -195
  26. package/dist/lib/auto-mode/pod-approval.js.map +1 -1
  27. package/dist/lib/auto-mode/pod-persistence.js +169 -90
  28. package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
  29. package/dist/lib/auto-mode/runner.js +683 -81
  30. package/dist/lib/auto-mode/runner.js.map +1 -1
  31. package/dist/lib/auto-mode/secretary.js +186 -41
  32. package/dist/lib/auto-mode/secretary.js.map +1 -1
  33. package/dist/lib/auto-mode-command.js +32 -32
  34. package/dist/lib/auto-mode-command.js.map +1 -1
  35. package/dist/lib/chat-api.js +242 -50
  36. package/dist/lib/chat-api.js.map +1 -1
  37. package/dist/lib/codex-plugin/bridge.js +164 -17
  38. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  39. package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
  40. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  41. package/dist/lib/credentials-store.js +33 -42
  42. package/dist/lib/credentials-store.js.map +1 -1
  43. package/dist/lib/linx-cloud-errors.js +61 -0
  44. package/dist/lib/linx-cloud-errors.js.map +1 -0
  45. package/dist/lib/linx-tui-contract.js +8 -5
  46. package/dist/lib/linx-tui-contract.js.map +1 -1
  47. package/dist/lib/login-command.js +9 -2
  48. package/dist/lib/login-command.js.map +1 -1
  49. package/dist/lib/models.js +3 -20
  50. package/dist/lib/models.js.map +1 -1
  51. package/dist/lib/oidc-auth.js +143 -17
  52. package/dist/lib/oidc-auth.js.map +1 -1
  53. package/dist/lib/oidc-session-storage.js +2 -6
  54. package/dist/lib/oidc-session-storage.js.map +1 -1
  55. package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
  56. package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
  57. package/dist/lib/pi-adapter/backend-command.js +2 -0
  58. package/dist/lib/pi-adapter/backend-command.js.map +1 -0
  59. package/dist/lib/pi-adapter/backend-credentials.js +80 -0
  60. package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
  61. package/dist/lib/pi-adapter/branding.js +246 -108
  62. package/dist/lib/pi-adapter/branding.js.map +1 -1
  63. package/dist/lib/pi-adapter/control-state.js +72 -0
  64. package/dist/lib/pi-adapter/control-state.js.map +1 -0
  65. package/dist/lib/pi-adapter/interactive.js +2634 -30
  66. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  67. package/dist/lib/pi-adapter/pod-approval.js +382 -210
  68. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  69. package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
  70. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
  71. package/dist/lib/pi-adapter/pod-mirror.js +531 -64
  72. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  73. package/dist/lib/pi-adapter/pod-native.js +81 -85
  74. package/dist/lib/pi-adapter/pod-native.js.map +1 -1
  75. package/dist/lib/pi-adapter/pod-status-output.js +54 -0
  76. package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
  77. package/dist/lib/pi-adapter/runtime.js +458 -228
  78. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  79. package/dist/lib/pi-adapter/session-control.js +509 -0
  80. package/dist/lib/pi-adapter/session-control.js.map +1 -0
  81. package/dist/lib/pi-adapter/session.js +35 -22
  82. package/dist/lib/pi-adapter/session.js.map +1 -1
  83. package/dist/lib/pi-adapter/stream.js +89 -32
  84. package/dist/lib/pi-adapter/stream.js.map +1 -1
  85. package/dist/lib/pi-adapter/sync-recovery.js +89 -0
  86. package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
  87. package/dist/lib/pi-adapter/web-fetch.js +13 -14
  88. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  89. package/dist/lib/pod-chat-store.js +254 -78
  90. package/dist/lib/pod-chat-store.js.map +1 -1
  91. package/dist/lib/pod-data-session.js +156 -35
  92. package/dist/lib/pod-data-session.js.map +1 -1
  93. package/dist/lib/solid-auth-store.js +27 -0
  94. package/dist/lib/solid-auth-store.js.map +1 -0
  95. package/dist/lib/solid-auth.js +2 -4
  96. package/dist/lib/solid-auth.js.map +1 -1
  97. package/dist/lib/solid-client-credentials-login.js +100 -0
  98. package/dist/lib/solid-client-credentials-login.js.map +1 -0
  99. package/dist/lib/solid-local-store.js +31 -0
  100. package/dist/lib/solid-local-store.js.map +1 -0
  101. package/dist/lib/symphony/archive.js +328 -18
  102. package/dist/lib/symphony/archive.js.map +1 -1
  103. package/dist/lib/symphony/pod-projection.js +2222 -0
  104. package/dist/lib/symphony/pod-projection.js.map +1 -0
  105. package/dist/lib/symphony-command.js +602 -178
  106. package/dist/lib/symphony-command.js.map +1 -1
  107. package/dist/lib/sync-checkpoint-store.js +74 -0
  108. package/dist/lib/sync-checkpoint-store.js.map +1 -0
  109. package/dist/skills/symphony/SKILL.md +665 -0
  110. package/package.json +15 -9
  111. package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
  112. package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
  113. package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
  114. package/vendor/agent-runtime/dist/auto-mode.js +288 -31
  115. package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
  116. package/vendor/agent-runtime/dist/control-plane.js +79 -0
  117. package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
  118. package/vendor/agent-runtime/dist/file-sync.js +314 -0
  119. package/vendor/agent-runtime/dist/index.d.ts +7 -0
  120. package/vendor/agent-runtime/dist/index.js +7 -0
  121. package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
  122. package/vendor/agent-runtime/dist/reconciler.js +361 -0
  123. package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
  124. package/vendor/agent-runtime/dist/symphony.js +362 -57
  125. package/vendor/agent-runtime/dist/sync.d.ts +271 -0
  126. package/vendor/agent-runtime/dist/sync.js +550 -0
  127. package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
  128. package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
  129. package/vendor/agent-runtime/dist/turn-controller.js +2 -2
  130. package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
  131. package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
  132. package/vendor/agent-runtime/package.json +8 -1
  133. package/vendor/pi-web-access/CHANGELOG.md +387 -0
  134. package/vendor/pi-web-access/LICENSE +21 -0
  135. package/vendor/pi-web-access/README.md +352 -0
  136. package/vendor/pi-web-access/activity.ts +101 -0
  137. package/vendor/pi-web-access/banner.png +0 -0
  138. package/vendor/pi-web-access/chrome-cookies.ts +322 -0
  139. package/vendor/pi-web-access/code-search.ts +107 -0
  140. package/vendor/pi-web-access/curator-page.ts +3359 -0
  141. package/vendor/pi-web-access/curator-server.ts +605 -0
  142. package/vendor/pi-web-access/exa.ts +520 -0
  143. package/vendor/pi-web-access/extract.ts +641 -0
  144. package/vendor/pi-web-access/gemini-api.ts +112 -0
  145. package/vendor/pi-web-access/gemini-search.ts +361 -0
  146. package/vendor/pi-web-access/gemini-url-context.ts +126 -0
  147. package/vendor/pi-web-access/gemini-web-config.ts +52 -0
  148. package/vendor/pi-web-access/gemini-web.ts +396 -0
  149. package/vendor/pi-web-access/github-api.ts +196 -0
  150. package/vendor/pi-web-access/github-extract.ts +634 -0
  151. package/vendor/pi-web-access/index.ts +2346 -0
  152. package/vendor/pi-web-access/package.json +45 -0
  153. package/vendor/pi-web-access/pdf-extract.ts +192 -0
  154. package/vendor/pi-web-access/perplexity.ts +195 -0
  155. package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  156. package/vendor/pi-web-access/rsc-extract.ts +338 -0
  157. package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
  158. package/vendor/pi-web-access/storage.ts +72 -0
  159. package/vendor/pi-web-access/summary-review.ts +276 -0
  160. package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
  161. package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
  162. package/vendor/pi-web-access/utils.ts +44 -0
  163. package/vendor/pi-web-access/video-extract.ts +378 -0
  164. package/vendor/pi-web-access/youtube-extract.ts +310 -0
  165. package/dist/lib/pi-adapter/auth.js +0 -68
  166. package/dist/lib/pi-adapter/auth.js.map +0 -1
  167. package/dist/lib/pi-adapter/pod-tools.js +0 -140
  168. package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
  169. package/dist/skills/drizzle-solid/SKILL.md +0 -340
  170. package/dist/skills/pod-storage/SKILL.md +0 -100
  171. package/dist/skills/solid-modeling/SKILL.md +0 -274
  172. package/dist/skills/xpod-componentsjs/SKILL.md +0 -284
@@ -1,19 +1,27 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { setTimeout as delay } from 'node:timers/promises';
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';
3
+ import { buildAcpPermissionResponse, buildAutoModeUserInputResponse, MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, normalizeAcpInteractionRequest, normalizeAcpRequest, normalizeAcpSessionNotification, parseAutoModeJsonLine, autoModeApprovalDecisionLabel, autoModeUserInputAnswersSummary, resolveAutoModeCommandRoute, shouldMaterializeAutoModeGrant, } from '../../../vendor/agent-runtime/dist/auto-mode.js';
4
+ import { adoptAutoModeSessionId, appendAutoModeEvent, createAutoModeSession, finishAutoModeSession, hasPendingAutoModeSync, loadAutoModeEvents, loadAutoModeSession, listAutoModeSessionsWithPendingSync, listAutoModeSessions, writeAutoModeSyncCheckpoint, writeAutoModeSession, } from './archive.js';
5
5
  import { detectAutoModeAuthFailure, preflightAutoModeAuth } from './auth.js';
6
6
  import { createAutoModeDisplay } from './display.js';
7
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';
8
+ import { describeAutoControl, getAutoModeHook, linxNativeBackend, listAutoModeHooks } from './hooks/index.js';
9
+ import { createRemoteAutoModeApproval, isRemoteApprovalAbortError, materializeRemoteAutoModeGrant, resolveExistingRemoteAutoModeGrant, resolveRemoteAutoModeApproval, waitForRemoteAutoModeApproval, } from './pod-approval.js';
10
10
  import { persistAutoModeConversationToPod } from './pod-persistence.js';
11
11
  import { loadPodBackendCredential, podCredentialMissingMessage } from './pod-ai.js';
12
12
  import { resolveAutoModeSecretaryRecommendation } from './secretary.js';
13
13
  import { promptText } from '../prompt.js';
14
14
  import { runLinxLoginCommand, runLinxLogoutCommand } from '../login-command.js';
15
- import { clearDefaultPodDataSession } from '../pod-data-session.js';
15
+ import { clearDefaultPodDataSession, createPodDataSession } from '../pod-data-session.js';
16
+ import { parseSolidClientCredentials, persistSolidClientCredentialsLogin } from '../solid-client-credentials-login.js';
16
17
  import { connectAiProviderCredential } from '../ai-command.js';
18
+ import { saveAccountSession } from '../account-session.js';
19
+ import { clearCredentials, loadCredentials, saveCredentials } from '../credentials-store.js';
20
+ import { resolveAccountBaseUrl } from '../account-api.js';
21
+ import { createRemoteCompletionResult } from '../chat-api.js';
22
+ import { resolveRuntimeTarget } from '../runtime-target.js';
23
+ import { runThreadReconcilerCycle } from '../../../vendor/agent-runtime/dist/index.js';
24
+ import { createLinxSyncCheckpoint } from '../../../vendor/agent-runtime/dist/sync.js';
17
25
  const AUTO_MODE_SECRETARY_COUNTDOWN_BAR_WIDTH = 10;
18
26
  const AUTO_MODE_SECRETARY_COUNTDOWN_TICK_MS = 250;
19
27
  const POD_PERSISTENCE_TIMEOUT_MS = 5_000;
@@ -26,8 +34,18 @@ export const autoModeRuntime = {
26
34
  resolveExistingRemoteAutoModeGrant,
27
35
  waitForRemoteAutoModeApproval,
28
36
  resolveRemoteAutoModeApproval,
37
+ materializeRemoteAutoModeGrant,
29
38
  persistAutoModeConversationToPod,
30
39
  resolveAutoModeSecretaryRecommendation,
40
+ createPodDataSession,
41
+ clearDefaultPodDataSession,
42
+ loadCredentials,
43
+ saveCredentials,
44
+ clearCredentials,
45
+ saveAccountSession,
46
+ resolveAccountBaseUrl,
47
+ persistSolidClientCredentialsLogin,
48
+ createRemoteCompletionResult,
31
49
  };
32
50
  function createLineSplitter(stream, onLine) {
33
51
  let buffer = '';
@@ -72,6 +90,39 @@ function withTimeout(promise, ms, message, onTimeout) {
72
90
  });
73
91
  });
74
92
  }
93
+ function createAbortError(message = 'The operation was aborted.') {
94
+ const error = new Error(message);
95
+ error.name = 'AbortError';
96
+ return error;
97
+ }
98
+ function isAbortError(error) {
99
+ return error instanceof Error && (error.name === 'AbortError' || error.message.toLowerCase().includes('aborted'));
100
+ }
101
+ function throwIfAborted(signal) {
102
+ if (!signal?.aborted) {
103
+ return;
104
+ }
105
+ const reason = signal.reason;
106
+ if (reason instanceof Error) {
107
+ throw reason;
108
+ }
109
+ throw createAbortError(typeof reason === 'string' && reason.trim() ? reason : undefined);
110
+ }
111
+ function withAbortSignal(promise, signal) {
112
+ if (!signal) {
113
+ return promise;
114
+ }
115
+ throwIfAborted(signal);
116
+ return new Promise((resolve, reject) => {
117
+ const onAbort = () => reject(signal.reason instanceof Error
118
+ ? signal.reason
119
+ : createAbortError(typeof signal.reason === 'string' && signal.reason.trim() ? signal.reason : undefined));
120
+ signal.addEventListener('abort', onAbort, { once: true });
121
+ promise
122
+ .then(resolve, reject)
123
+ .finally(() => signal.removeEventListener('abort', onAbort));
124
+ });
125
+ }
75
126
  function appendEntry(record, stream, line, events) {
76
127
  const entry = {
77
128
  timestamp: new Date().toISOString(),
@@ -91,6 +142,32 @@ function appendSessionNote(record, message, raw) {
91
142
  raw,
92
143
  }]);
93
144
  }
145
+ function writeFailedPodSyncCheckpoint(record, message) {
146
+ const now = new Date().toISOString();
147
+ const result = {
148
+ source: 'auto-mode-archive',
149
+ target: 'pod',
150
+ direction: 'local-to-core',
151
+ plane: 'projection',
152
+ authority: 'core',
153
+ attempted: 0,
154
+ applied: 0,
155
+ skipped: 0,
156
+ failed: 1,
157
+ failures: [{
158
+ operationId: 'auto-mode.persist-to-pod',
159
+ message,
160
+ failedAt: now,
161
+ }],
162
+ startedAt: now,
163
+ completedAt: now,
164
+ status: 'failed',
165
+ };
166
+ writeAutoModeSyncCheckpoint(record, createLinxSyncCheckpoint('auto-mode-archive:pod:projection', result, {
167
+ sessionId: record.id,
168
+ backend: record.backend,
169
+ }));
170
+ }
94
171
  function appendAndDisplaySessionNote(record, display, message, tone = 'note', raw) {
95
172
  appendSessionNote(record, message, raw);
96
173
  display.showActivity(message, tone);
@@ -240,15 +317,18 @@ async function promptLinxCloudAuth(display, lines, reason = 'manual') {
240
317
  display.setPhase('question', reason === 'expired' ? 'LinX Cloud login expired' : 'LinX Cloud login required');
241
318
  const answer = (await display.chooseOption(reason === 'expired' ? 'LinX Cloud login expired' : 'LinX Cloud login required', lines, [
242
319
  { label: 'Authorize in browser', value: 'browser', description: 'refresh the LinX Cloud Solid session', shortcuts: ['b', '1'] },
243
- { label: 'Logout', value: 'logout', description: 'clear the current LinX Cloud session', shortcuts: ['l', '2'] },
244
- { label: 'Exit', value: 'exit', description: 'leave auto-mode', shortcuts: ['x', '3'] },
320
+ { label: 'Enter Solid client credentials', value: 'client-credentials', description: 'use LinX Cloud client credentials', shortcuts: ['k', '2'] },
321
+ { label: 'Exit', value: 'exit', description: 'leave this session', shortcuts: ['x', '3'] },
245
322
  ])).trim().toLowerCase();
246
323
  if (answer === 'browser' || answer === 'b' || answer === '1') {
247
324
  await runBackendLinxLogin(display);
248
325
  return 'retry';
249
326
  }
250
- if (answer === 'logout' || answer === 'l' || answer === '2') {
251
- runBackendLinxLogout(display);
327
+ if (answer === 'client-credentials' || answer === 'k' || answer === '2') {
328
+ const saved = await promptBackendSolidClientCredentials(display);
329
+ if (saved) {
330
+ return 'retry';
331
+ }
252
332
  continue;
253
333
  }
254
334
  if (answer === 'exit' || answer === 'x' || answer === '3' || answer === 'cancel') {
@@ -270,7 +350,7 @@ async function runBackendLinxLogin(display) {
270
350
  }
271
351
  },
272
352
  });
273
- clearDefaultPodDataSession();
353
+ autoModeRuntime.clearDefaultPodDataSession();
274
354
  display.showActivity('LinX Cloud login refreshed.', 'success');
275
355
  }
276
356
  function runBackendLinxLogout(display) {
@@ -284,9 +364,35 @@ function runBackendLinxLogout(display) {
284
364
  }
285
365
  },
286
366
  });
287
- clearDefaultPodDataSession();
367
+ autoModeRuntime.clearDefaultPodDataSession();
288
368
  display.showActivity('Use /login or choose browser authorization to sign in again.', 'note');
289
369
  }
370
+ async function promptBackendSolidClientCredentials(display) {
371
+ display.setPhase('question', 'Solid client credentials required');
372
+ const credentialsText = await display.promptSecret({
373
+ header: 'Solid client credentials',
374
+ question: 'Paste Solid client credentials in client_id:client_secret format.',
375
+ note: 'Input is hidden and saved locally as LinX Cloud client credentials.',
376
+ });
377
+ const parsed = parseSolidClientCredentials(credentialsText);
378
+ if (!parsed) {
379
+ display.showActivity('Solid client credentials entry cancelled or invalid. Expected client_id:client_secret.', 'error');
380
+ return false;
381
+ }
382
+ autoModeRuntime.clearDefaultPodDataSession();
383
+ try {
384
+ await autoModeRuntime.persistSolidClientCredentialsLogin(credentialsText, autoModeRuntime);
385
+ autoModeRuntime.clearDefaultPodDataSession();
386
+ display.showActivity('Solid client credentials saved. Retrying backend startup.', 'success');
387
+ return true;
388
+ }
389
+ catch (error) {
390
+ autoModeRuntime.clearDefaultPodDataSession();
391
+ const message = error instanceof Error ? error.message : String(error);
392
+ display.showActivity(`Solid client credentials rejected: ${message}`, 'error');
393
+ return false;
394
+ }
395
+ }
290
396
  function isRecoverableLinxCloudAuthError(message) {
291
397
  const normalized = message.toLowerCase();
292
398
  return normalized.includes('linx login')
@@ -300,7 +406,7 @@ function backendProviderLabel(backend) {
300
406
  if (backend === 'claude')
301
407
  return 'Anthropic';
302
408
  if (backend === 'codex')
303
- return 'OpenAI/Codex';
409
+ return 'Codex';
304
410
  return 'CodeBuddy';
305
411
  }
306
412
  function backendProviderId(backend) {
@@ -313,10 +419,24 @@ function backendProviderId(backend) {
313
419
  function isMissingProviderCredentialError(backend, message) {
314
420
  return message === podCredentialMissingMessage(backend);
315
421
  }
316
- async function promptBackendProviderKey(display, backend) {
422
+ async function promptBackendProviderCredential(display, backend, reason = 'missing') {
317
423
  const provider = backendProviderId(backend);
318
424
  const label = backendProviderLabel(backend);
319
- display.setPhase('question', `${label} API key required`);
425
+ display.showActivity(`AI Secretary detected ${reason} ${label} credentials.`, 'note');
426
+ let providerId = provider;
427
+ if (backend === 'codex') {
428
+ display.setPhase('question', `${label} ${reason} provider`);
429
+ const enteredProviderId = await display.chooseQuestion({
430
+ header: `${label} provider id`,
431
+ question: 'Enter the provider id for this Codex-compatible API gateway.',
432
+ options: [],
433
+ questionIndex: 0,
434
+ questionCount: 1,
435
+ unansweredCount: 1,
436
+ });
437
+ providerId = enteredProviderId.trim() || provider;
438
+ }
439
+ display.setPhase('question', `${label} ${reason} API key`);
320
440
  const apiKey = await display.promptSecret({
321
441
  header: `${label} key`,
322
442
  question: `Enter the ${label} API key to save in your LinX Pod AI settings.`,
@@ -326,8 +446,9 @@ async function promptBackendProviderKey(display, backend) {
326
446
  return 'cancel';
327
447
  }
328
448
  const result = await autoModeRuntime.connectAiProviderCredential({
329
- provider,
449
+ provider: providerId,
330
450
  apiKey,
451
+ ...(backend === 'codex' ? { supportsBackend: 'codex', rotationPolicy: 'round_robin' } : {}),
331
452
  });
332
453
  display.showActivity(`Saved ${result.providerId} credential to LinX Pod AI settings.`, 'success');
333
454
  return 'saved';
@@ -344,33 +465,71 @@ function approvalPromptMessage(request) {
344
465
  }
345
466
  return request.message || 'Approval required';
346
467
  }
468
+ async function materializeAutoModeGrantIfNeeded(input) {
469
+ if (!shouldMaterializeAutoModeGrant(input.decision)) {
470
+ return;
471
+ }
472
+ await autoModeRuntime.materializeRemoteAutoModeGrant({
473
+ approvalId: input.approvalId,
474
+ approvalUri: input.approvalUri,
475
+ decisionRole: input.decisionRole,
476
+ }).catch(() => undefined);
477
+ }
347
478
  function appendUserTurn(record, text) {
348
479
  appendEntry(record, 'system', JSON.stringify({ type: 'user.turn', text }), []);
349
480
  }
350
481
  function appendTurnStart(record, command, args) {
351
482
  appendEntry(record, 'system', JSON.stringify({ type: 'turn.start', command, args }), []);
352
483
  }
353
- function requestedCredentialSource(_options) {
354
- return 'cloud';
484
+ function createAcpInteractionThreadEvent(record, interaction) {
485
+ return {
486
+ type: interaction.kind === 'user-input' ? 'input.required' : 'approval.required',
487
+ thread: record.backendSessionId ?? record.id,
488
+ chat: record.backendSessionId ?? record.id,
489
+ actor: {
490
+ id: record.backendSessionId ?? record.backend,
491
+ role: 'runtime',
492
+ },
493
+ data: {
494
+ requestKind: interaction.kind,
495
+ backend: record.backend,
496
+ runtimeSession: record.backendSessionId,
497
+ businessSession: record.id,
498
+ },
499
+ };
500
+ }
501
+ function requestedCredentialSource(options) {
502
+ return options.credentialSource === 'local' ? 'local' : 'cloud';
355
503
  }
356
- function requestedApprovalSource() {
504
+ function defaultApprovalStrategy() {
357
505
  return 'hybrid';
358
506
  }
507
+ function resolveApprovalStrategy(options) {
508
+ return options.approvalStrategy ?? defaultApprovalStrategy();
509
+ }
359
510
  function requestedRuntime(options) {
360
511
  return options.runtime ?? 'local';
361
512
  }
513
+ function requestedAutoEnabled(options) {
514
+ return options.autoEnabled === true;
515
+ }
362
516
  function requestedAutoModeMode(options) {
363
- return options.autoModeEnabled ? 'auto' : options.mode;
517
+ if (options.mode === 'auto' || options.mode === 'off') {
518
+ return options.mode;
519
+ }
520
+ return requestedAutoEnabled(options) ? 'auto' : 'off';
521
+ }
522
+ function isAutoModeWorkerBackend(backend) {
523
+ return backend === 'linx' || backend === 'codex' || backend === 'claude' || backend === 'codebuddy';
524
+ }
525
+ function isAcpAutoModeWorkerBackend(backend) {
526
+ return backend === 'codex' || backend === 'claude' || backend === 'codebuddy';
364
527
  }
365
528
  function normalizeBackendCommandEnv(backend, env) {
366
529
  if (!env) {
367
530
  return undefined;
368
531
  }
369
- const next = { ...env };
370
- if (backend === 'codex' && next.OPENAI_API_KEY && !next.CODEX_API_KEY) {
371
- next.CODEX_API_KEY = next.OPENAI_API_KEY;
372
- }
373
- return next;
532
+ return { ...env };
374
533
  }
375
534
  function mergeCommandEnv(backend, commandEnv, planEnv) {
376
535
  const normalizedCommandEnv = normalizeBackendCommandEnv(backend, commandEnv);
@@ -387,6 +546,7 @@ function syncRecordFromOptions(record, options, plan) {
387
546
  backend: options.backend,
388
547
  runtime: requestedRuntime(options),
389
548
  mode: requestedAutoModeMode(options),
549
+ autoEnabled: requestedAutoEnabled(options),
390
550
  goalMode: options.goalMode || undefined,
391
551
  cwd: options.cwd,
392
552
  model: options.model,
@@ -394,10 +554,11 @@ function syncRecordFromOptions(record, options, plan) {
394
554
  passthroughArgs: [...options.passthroughArgs],
395
555
  credentialSource: requestedCredentialSource(options),
396
556
  resolvedCredentialSource: options.resolvedCredentialSource,
397
- approvalSource: requestedApprovalSource(),
557
+ approvalSource: record.approvalSource ?? defaultApprovalStrategy(),
398
558
  command: plan.command,
399
559
  args: [...plan.args],
400
- transport: options.transport ?? 'acp',
560
+ transport: options.transport ?? (options.backend === 'linx' ? 'native' : 'acp'),
561
+ metadata: options.metadata ? { ...options.metadata } : undefined,
401
562
  };
402
563
  }
403
564
  function extractAcpSessionId(response) {
@@ -417,10 +578,12 @@ function withResolvedSource(options, resolvedCredentialSource, commandEnv) {
417
578
  return {
418
579
  ...options,
419
580
  mode: requestedAutoModeMode(options),
420
- transport: options.transport ?? 'acp',
581
+ transport: options.transport ?? (options.backend === 'linx' ? 'native' : 'acp'),
421
582
  credentialSource: requestedCredentialSource(options),
422
583
  resolvedCredentialSource,
423
- commandEnv,
584
+ commandEnv: mergeCommandEnv(options.backend, commandEnv, options.commandEnv),
585
+ autoEnabled: requestedAutoEnabled(options),
586
+ approvalStrategy: resolveApprovalStrategy(options),
424
587
  };
425
588
  }
426
589
  async function probeCloudCredentialSource(backend, runtime) {
@@ -438,6 +601,23 @@ async function probeCloudCredentialSource(backend, runtime) {
438
601
  }
439
602
  }
440
603
  export async function resolveAutoRunOptions(options, runtime = autoModeRuntime) {
604
+ if (options.backend === 'linx') {
605
+ const session = await runtime.createPodDataSession();
606
+ if (!session) {
607
+ throw new Error('No LinX cloud login found. Run `linx login` first.');
608
+ }
609
+ await session.close().catch(() => undefined);
610
+ return {
611
+ options: withResolvedSource(options, 'cloud'),
612
+ authPreflight: { state: 'authenticated' },
613
+ };
614
+ }
615
+ if (requestedCredentialSource(options) === 'local') {
616
+ return {
617
+ options: withResolvedSource(options, 'local'),
618
+ authPreflight: await runtime.preflightAutoModeAuth(options.backend),
619
+ };
620
+ }
441
621
  const { commandEnv } = await probeCloudCredentialSource(options.backend, runtime);
442
622
  return {
443
623
  options: withResolvedSource(options, 'cloud', commandEnv),
@@ -454,9 +634,9 @@ class BaseSession {
454
634
  activeCloseResolve = null;
455
635
  closed = false;
456
636
  lastExit = null;
457
- constructor(record, prompt) {
637
+ constructor(record, prompt, options = {}) {
458
638
  this.record = record;
459
- this.display = createAutoModeDisplay(record, prompt);
639
+ this.display = createAutoModeDisplay(record, prompt, { quiet: options.quiet });
460
640
  }
461
641
  spawnProcess(command, args, cwd, env) {
462
642
  this.activeExitPromise = new Promise((resolve) => {
@@ -540,6 +720,17 @@ class BaseSession {
540
720
  }
541
721
  return Promise.resolve(this.lastExit ?? { code: null, signal: null });
542
722
  }
723
+ async abort() {
724
+ this.closed = true;
725
+ const child = this.child;
726
+ if (!child) {
727
+ return;
728
+ }
729
+ if (child.exitCode === null && child.signalCode === null) {
730
+ child.kill('SIGTERM');
731
+ }
732
+ await this.waitForActiveClose();
733
+ }
543
734
  async close() {
544
735
  this.closed = true;
545
736
  const child = this.child;
@@ -573,7 +764,9 @@ class AcpSession extends BaseSession {
573
764
  activeAgentRequests = 0;
574
765
  constructor(options, hook) {
575
766
  const plan = hook.buildSpawnPlan(options);
576
- super(createAutoModeSession({ ...options, transport: options.transport ?? 'acp' }, plan), autoModeRuntime.promptText);
767
+ super(createAutoModeSession({ ...options, transport: options.transport ?? 'acp' }, plan), autoModeRuntime.promptText, {
768
+ quiet: options.quiet === true,
769
+ });
577
770
  this.hook = hook;
578
771
  this.options = options;
579
772
  }
@@ -860,13 +1053,7 @@ class AcpSession extends BaseSession {
860
1053
  this.sendError(id, -32601, `Unsupported ACP client request: ${method}`);
861
1054
  return;
862
1055
  }
863
- if (interaction.kind === 'user-input') {
864
- const result = await this.resolveToolUserInput(interaction);
865
- this.sendResponse(id, result);
866
- return;
867
- }
868
- const decision = await this.resolveApproval(interaction);
869
- this.sendResponse(id, buildAcpPermissionResponse(interaction, decision));
1056
+ this.sendResponse(id, await this.resolveInteraction(interaction));
870
1057
  }
871
1058
  catch (error) {
872
1059
  const messageText = error instanceof Error ? error.message : String(error);
@@ -877,6 +1064,68 @@ class AcpSession extends BaseSession {
877
1064
  this.scheduleTurnSettle();
878
1065
  }
879
1066
  }
1067
+ async resolveInteraction(interaction) {
1068
+ let result;
1069
+ let wakeError;
1070
+ const autoEnabled = this.record.autoEnabled === true;
1071
+ const cycle = await runThreadReconcilerCycle({
1072
+ policy: {
1073
+ kind: autoEnabled ? 'auto' : 'direct',
1074
+ secretaryAgent: '__secretary__',
1075
+ },
1076
+ handleWakeJob: async ({ decisionSummary, record }) => {
1077
+ try {
1078
+ result = await this.resolveInteractionDirect(interaction);
1079
+ return {
1080
+ requestKind: interaction.kind,
1081
+ responseKind: interaction.kind === 'user-input' ? 'user-input' : 'approval',
1082
+ reconciler: decisionSummary.id,
1083
+ wakeJob: record.key,
1084
+ };
1085
+ }
1086
+ catch (error) {
1087
+ wakeError = error;
1088
+ throw error;
1089
+ }
1090
+ },
1091
+ event: createAcpInteractionThreadEvent(this.record, interaction),
1092
+ dispatchOptions: {
1093
+ randomId: `acp-${interaction.kind}-${Date.now()}`,
1094
+ },
1095
+ onDispatched: (dispatch) => {
1096
+ appendSessionNote(this.record, `Thread Reconciler dispatched ${interaction.kind}`, {
1097
+ requestKind: interaction.kind,
1098
+ reconciler: dispatch.summary,
1099
+ scheduler: {
1100
+ wakeRecords: dispatch.wakeRecordSummaries,
1101
+ },
1102
+ });
1103
+ if (dispatch.summary.wakeJobs.length === 0) {
1104
+ result = this.resolveInteractionDirect(interaction);
1105
+ }
1106
+ },
1107
+ });
1108
+ result = await result;
1109
+ appendSessionNote(this.record, `Thread Reconciler resolved ${interaction.kind}`, {
1110
+ requestKind: interaction.kind,
1111
+ reconciler: cycle.summary,
1112
+ scheduler: cycle.schedulerSummary,
1113
+ });
1114
+ if (cycle.schedulerSummary.failed.length > 0) {
1115
+ throw wakeError ?? new Error(String(cycle.schedulerSummary.failed[0]?.error ?? 'AI Secretary interaction wake job failed'));
1116
+ }
1117
+ if (result === undefined) {
1118
+ throw new Error('AI Secretary was not awakened for ACP interaction.');
1119
+ }
1120
+ return result;
1121
+ }
1122
+ async resolveInteractionDirect(interaction) {
1123
+ if (interaction.kind === 'user-input') {
1124
+ return this.resolveToolUserInput(interaction);
1125
+ }
1126
+ const decision = await this.resolveApproval(interaction);
1127
+ return buildAcpPermissionResponse(interaction, decision);
1128
+ }
880
1129
  async resolveToolUserInput(interaction) {
881
1130
  const recommendation = await this.resolveSecretaryRecommendation(interaction);
882
1131
  const answers = await this.resolveToolUserInputAnswers(interaction.questions, recommendation?.kind === 'user-input' ? recommendation : null);
@@ -919,6 +1168,18 @@ class AcpSession extends BaseSession {
919
1168
  return this.display.chooseQuestions(questions);
920
1169
  }
921
1170
  async resolveApproval(interaction) {
1171
+ const recommendation = await this.resolveSecretaryRecommendation(interaction);
1172
+ const approvalRecommendation = recommendation?.kind === interaction.kind
1173
+ ? recommendation
1174
+ : null;
1175
+ const approvalStrategy = resolveApprovalStrategy(this.options);
1176
+ if (approvalStrategy === 'local') {
1177
+ const promptMessage = approvalPromptMessage(interaction);
1178
+ const decision = await promptApprovalWithRecommendation(this.display, promptMessage, approvalRecommendation);
1179
+ appendSessionNote(this.record, `Local approval resolved | ${decision}`);
1180
+ this.display.setPhase('running', 'Continuing turn');
1181
+ return decision;
1182
+ }
922
1183
  const granted = await autoModeRuntime.resolveExistingRemoteAutoModeGrant({
923
1184
  record: this.record,
924
1185
  request: interaction,
@@ -929,12 +1190,14 @@ class AcpSession extends BaseSession {
929
1190
  this.display.setPhase('running', 'Continuing turn');
930
1191
  return granted;
931
1192
  }
932
- const recommendation = await this.resolveSecretaryRecommendation(interaction);
933
- return this.resolveHybridApproval(interaction, recommendation?.kind === interaction.kind ? recommendation : null);
1193
+ if (approvalStrategy === 'remote') {
1194
+ return this.resolveRemoteOnlyApproval(interaction, approvalRecommendation);
1195
+ }
1196
+ return this.resolveHybridApproval(interaction, approvalRecommendation);
934
1197
  }
935
1198
  async resolveSecretaryRecommendation(interaction) {
936
1199
  return autoModeRuntime.resolveAutoModeSecretaryRecommendation({
937
- mode: this.options.mode,
1200
+ mode: requestedAutoModeMode(this.options),
938
1201
  record: this.record,
939
1202
  request: interaction,
940
1203
  }).catch(() => null);
@@ -954,6 +1217,11 @@ class AcpSession extends BaseSession {
954
1217
  void remoteDecisionPromise.catch(() => undefined);
955
1218
  if (!recommendation?.canAutoDecide || !recommendation.decision) {
956
1219
  const decision = await remoteDecisionPromise;
1220
+ await materializeAutoModeGrantIfNeeded({
1221
+ approvalId: remote.id,
1222
+ approvalUri: remote.approvalUri,
1223
+ decision,
1224
+ });
957
1225
  appendSessionNote(this.record, `Remote approval resolved | ${decision}`);
958
1226
  this.display.setPhase('running', 'Continuing turn');
959
1227
  return decision;
@@ -973,6 +1241,12 @@ class AcpSession extends BaseSession {
973
1241
  return recommendation.decision;
974
1242
  }),
975
1243
  ]);
1244
+ await materializeAutoModeGrantIfNeeded({
1245
+ approvalId: remote.id,
1246
+ approvalUri: remote.approvalUri,
1247
+ decision,
1248
+ decisionRole: recommendation?.canAutoDecide ? 'secretary' : undefined,
1249
+ });
976
1250
  appendSessionNote(this.record, `Remote approval resolved | ${decision}`);
977
1251
  this.display.setPhase('running', 'Continuing turn');
978
1252
  return decision;
@@ -1021,11 +1295,23 @@ class AcpSession extends BaseSession {
1021
1295
  note: secretaryAutoResolved
1022
1296
  ? (recommendation?.reason ?? 'resolved by AI secretary')
1023
1297
  : 'resolved from active local auto-mode session',
1024
- }).catch(() => undefined);
1298
+ })
1299
+ .then(() => materializeAutoModeGrantIfNeeded({
1300
+ approvalId: remoteApproval.id,
1301
+ approvalUri: remoteApproval.approvalUri,
1302
+ decision: winner.decision,
1303
+ decisionRole: secretaryAutoResolved ? 'secretary' : 'human',
1304
+ }))
1305
+ .catch(() => undefined);
1025
1306
  this.display.setPhase('running', 'Continuing turn');
1026
1307
  return winner.decision;
1027
1308
  }
1028
1309
  localAbort.abort();
1310
+ await materializeAutoModeGrantIfNeeded({
1311
+ approvalId: remoteApproval.id,
1312
+ approvalUri: remoteApproval.approvalUri,
1313
+ decision: winner.decision,
1314
+ });
1029
1315
  appendSessionNote(this.record, `Remote approval resolved | ${winner.decision}`);
1030
1316
  this.display.setPhase('running', 'Continuing turn');
1031
1317
  return winner.decision;
@@ -1089,7 +1375,124 @@ class AcpSession extends BaseSession {
1089
1375
  }
1090
1376
  }
1091
1377
  }
1378
+ class LinxNativeSession extends BaseSession {
1379
+ options;
1380
+ podSession = null;
1381
+ constructor(options) {
1382
+ super(createAutoModeSession({ ...options, transport: 'native' }, buildLinxNativeSpawnPlan(options)), autoModeRuntime.promptText, {
1383
+ quiet: options.quiet === true,
1384
+ });
1385
+ this.options = options;
1386
+ }
1387
+ async start() {
1388
+ const podSession = await autoModeRuntime.createPodDataSession();
1389
+ if (!podSession) {
1390
+ throw new Error('No LinX cloud login found. Run `linx login` first.');
1391
+ }
1392
+ this.podSession = podSession;
1393
+ this.updateRecord({
1394
+ backendSessionId: this.record.id,
1395
+ transport: 'native',
1396
+ resolvedCredentialSource: 'cloud',
1397
+ error: undefined,
1398
+ });
1399
+ appendSessionNote(this.record, `LinX native worker connected as ${podSession.webId}`);
1400
+ }
1401
+ applyResolvedOptions(options) {
1402
+ this.options = {
1403
+ ...options,
1404
+ transport: 'native',
1405
+ };
1406
+ this.updateRecord(syncRecordFromOptions(this.record, this.options, buildLinxNativeSpawnPlan(this.options)));
1407
+ }
1408
+ async setModel(model) {
1409
+ const normalized = model.trim();
1410
+ if (!normalized) {
1411
+ throw new Error('Model id cannot be empty');
1412
+ }
1413
+ this.options = {
1414
+ ...this.options,
1415
+ model: normalized,
1416
+ };
1417
+ this.updateRecord({
1418
+ model: normalized,
1419
+ });
1420
+ appendSessionNote(this.record, `LinX native worker model set to ${normalized}`);
1421
+ }
1422
+ async sendTurn(text) {
1423
+ const podSession = this.podSession;
1424
+ if (!podSession) {
1425
+ throw new Error('LinX native session is not initialized');
1426
+ }
1427
+ appendUserTurn(this.record, text);
1428
+ appendTurnStart(this.record, this.record.command, this.record.args);
1429
+ const result = await autoModeRuntime.createRemoteCompletionResult({
1430
+ runtimeUrl: resolveRuntimeTarget({ issuerUrl: podSession.credentials.url }).runtimeUrl,
1431
+ authSession: podSession,
1432
+ model: this.options.model,
1433
+ messages: [{ role: 'user', content: text }],
1434
+ signal: this.options.signal,
1435
+ });
1436
+ this.recordLinxCompletion(result);
1437
+ }
1438
+ onProcessExit() { }
1439
+ onProcessFailure() { }
1440
+ async abort() {
1441
+ await this.close();
1442
+ }
1443
+ async close() {
1444
+ const session = this.podSession;
1445
+ this.podSession = null;
1446
+ await session?.close().catch(() => undefined);
1447
+ await super.close();
1448
+ }
1449
+ recordLinxCompletion(result) {
1450
+ const content = result.content.trim();
1451
+ const toolEvents = result.toolCalls.map((toolCall) => ({
1452
+ type: 'tool.call',
1453
+ name: toolCall.function.name,
1454
+ arguments: parseLinxToolCallArguments(toolCall.function.arguments),
1455
+ raw: toolCall,
1456
+ }));
1457
+ const events = [
1458
+ ...toolEvents,
1459
+ ...(content ? [{ type: 'assistant.delta', text: content, raw: result }] : []),
1460
+ { type: 'assistant.done', ...(content ? { text: content } : {}), raw: result },
1461
+ ];
1462
+ this.recordParsedLine('stdout', JSON.stringify({
1463
+ type: 'assistant.message',
1464
+ content,
1465
+ ...(result.reasoningContent ? { reasoningContent: result.reasoningContent } : {}),
1466
+ ...(result.finishReason ? { finishReason: result.finishReason } : {}),
1467
+ ...(result.usage ? { usage: result.usage } : {}),
1468
+ }), events);
1469
+ }
1470
+ }
1471
+ function buildLinxNativeSpawnPlan(_options) {
1472
+ return {
1473
+ command: 'linx-cloud',
1474
+ args: ['chat/completions'],
1475
+ };
1476
+ }
1477
+ function parseLinxToolCallArguments(value) {
1478
+ const trimmed = value.trim();
1479
+ if (!trimmed) {
1480
+ return undefined;
1481
+ }
1482
+ try {
1483
+ const parsed = JSON.parse(trimmed);
1484
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
1485
+ ? parsed
1486
+ : { value: parsed };
1487
+ }
1488
+ catch {
1489
+ return { raw: value };
1490
+ }
1491
+ }
1092
1492
  function buildConversationSession(options) {
1493
+ if (options.backend === 'linx') {
1494
+ return new LinxNativeSession(options);
1495
+ }
1093
1496
  const hook = getAutoModeHook(options.backend);
1094
1497
  return new AcpSession(options, hook);
1095
1498
  }
@@ -1129,24 +1532,14 @@ async function handleAutoModeShellCommand(args) {
1129
1532
  ].join(' | '));
1130
1533
  return 'handled';
1131
1534
  }
1132
- if (input === '/manual' || input === '/smart' || input === '/auto') {
1133
- const mode = input.slice(1);
1134
- session.applyResolvedOptions({
1135
- backend: record.backend,
1136
- mode,
1137
- cwd: record.cwd,
1138
- plain: false,
1139
- model: record.model,
1140
- prompt: record.prompt,
1141
- passthroughArgs: record.passthroughArgs,
1142
- goalMode: record.goalMode,
1143
- runtime: record.runtime,
1144
- transport: record.transport,
1145
- credentialSource: record.credentialSource,
1146
- resolvedCredentialSource: record.resolvedCredentialSource,
1535
+ const autoModeRoute = resolveAutoModeCommandRoute(input);
1536
+ if (autoModeRoute) {
1537
+ return handleAutoModeCommandRoute({
1538
+ route: autoModeRoute,
1539
+ session,
1540
+ display,
1541
+ record,
1147
1542
  });
1148
- appendAndDisplaySessionNote(record, display, `Approval mode set to ${mode}`, 'success', { mode });
1149
- return 'handled';
1150
1543
  }
1151
1544
  if (input === '/queue') {
1152
1545
  appendAndDisplaySessionNote(record, display, `queue | steer=${queueState.steeringCount} | follow-up=${queueState.followUpCount}`);
@@ -1198,7 +1591,97 @@ async function handleAutoModeShellCommand(args) {
1198
1591
  }
1199
1592
  return 'pass';
1200
1593
  }
1594
+ function handleAutoModeCommandRoute(args) {
1595
+ const { route, session, display, record } = args;
1596
+ if (route.kind === 'control-command') {
1597
+ return handleAutoModeControlCommand({ route, session, display, record });
1598
+ }
1599
+ return handleAutoModePeerCommand({ route, session, display, record });
1600
+ }
1601
+ function handleAutoModeControlCommand(args) {
1602
+ const { route, session, display, record } = args;
1603
+ const auto = route.auto;
1604
+ if (!auto || auto.action === 'status') {
1605
+ const enabled = record.autoEnabled === true;
1606
+ appendAndDisplaySessionNote(record, display, `Auto is ${enabled ? 'on' : 'off'}. Use /auto on or /auto off.`);
1607
+ return 'handled';
1608
+ }
1609
+ if (!isAutoModeWorkerBackend(record.backend)) {
1610
+ throw new Error(`Auto control commands cannot run backend ${record.backend}`);
1611
+ }
1612
+ applyAutoModeAutoEnabled(session, record, auto.enabled);
1613
+ appendAndDisplaySessionNote(record, display, `Auto ${auto.enabled ? 'on' : 'off'}: ${auto.enabled ? 'Secretary drives the session and asks when blocked' : 'user drives the session directly'}.`, 'success', { autoEnabled: auto.enabled });
1614
+ if (auto.initialInput) {
1615
+ const projectedRoute = resolveAutoModeCommandRoute(auto.initialInput);
1616
+ if (projectedRoute) {
1617
+ return handleAutoModeCommandRoute({
1618
+ route: projectedRoute,
1619
+ session,
1620
+ display,
1621
+ record,
1622
+ });
1623
+ }
1624
+ return { kind: 'send', text: auto.initialInput };
1625
+ }
1626
+ return 'handled';
1627
+ }
1628
+ function handleAutoModePeerCommand(args) {
1629
+ const { route, session, display, record } = args;
1630
+ const goalMirror = route.secretaryBehavior?.goalMode;
1631
+ if (goalMirror !== undefined) {
1632
+ applyAutoModeGoalMode(session, record, goalMirror);
1633
+ appendAndDisplaySessionNote(record, display, `Goal command routed to current chat peer; local supervision mirror is ${goalMirror ? 'active' : 'paused'}.`, 'success', { goalMode: goalMirror, peerCommand: route.text });
1634
+ }
1635
+ else {
1636
+ appendAndDisplaySessionNote(record, display, 'Goal command routed to current chat peer.', 'note', { peerCommand: route.text });
1637
+ }
1638
+ return { kind: 'send', text: route.text };
1639
+ }
1640
+ function applyAutoModeAutoEnabled(session, record, enabled) {
1641
+ session.applyResolvedOptions({
1642
+ backend: record.backend,
1643
+ autoEnabled: enabled,
1644
+ mode: enabled ? 'auto' : 'off',
1645
+ cwd: record.cwd,
1646
+ plain: false,
1647
+ model: record.model,
1648
+ prompt: record.prompt,
1649
+ passthroughArgs: record.passthroughArgs,
1650
+ goalMode: record.goalMode,
1651
+ runtime: record.runtime,
1652
+ transport: record.transport,
1653
+ credentialSource: record.credentialSource,
1654
+ resolvedCredentialSource: record.resolvedCredentialSource,
1655
+ approvalStrategy: resolveApprovalStrategy({ approvalStrategy: record.approvalSource }),
1656
+ });
1657
+ record.autoEnabled = enabled;
1658
+ record.mode = enabled ? 'auto' : 'off';
1659
+ }
1660
+ function applyAutoModeGoalMode(session, record, enabled) {
1661
+ if (!isAutoModeWorkerBackend(record.backend)) {
1662
+ throw new Error(`Goal peer commands cannot run backend ${record.backend}`);
1663
+ }
1664
+ session.applyResolvedOptions({
1665
+ backend: record.backend,
1666
+ autoEnabled: record.autoEnabled === true,
1667
+ mode: record.mode,
1668
+ cwd: record.cwd,
1669
+ plain: false,
1670
+ model: record.model,
1671
+ prompt: record.prompt,
1672
+ passthroughArgs: record.passthroughArgs,
1673
+ goalMode: enabled,
1674
+ runtime: record.runtime,
1675
+ transport: record.transport,
1676
+ credentialSource: record.credentialSource,
1677
+ resolvedCredentialSource: record.resolvedCredentialSource,
1678
+ approvalStrategy: resolveApprovalStrategy({ approvalStrategy: record.approvalSource }),
1679
+ });
1680
+ record.goalMode = enabled || undefined;
1681
+ }
1201
1682
  export const __testHandleAutoModeShellCommand = handleAutoModeShellCommand;
1683
+ export const __testPromptLinxCloudAuth = promptLinxCloudAuth;
1684
+ export const __testParseSolidClientCredentials = parseSolidClientCredentials;
1202
1685
  export async function runAutoMode(options) {
1203
1686
  const previousPlainEnv = process.env.LINX_BACKEND_PLAIN;
1204
1687
  if (options.plain) {
@@ -1206,18 +1689,39 @@ export async function runAutoMode(options) {
1206
1689
  }
1207
1690
  let fatalError = null;
1208
1691
  let session = null;
1692
+ let abortCleanup = null;
1209
1693
  const requestedOptions = {
1210
1694
  ...options,
1211
1695
  runtime: requestedRuntime(options),
1212
- transport: 'acp',
1696
+ transport: (options.transport ?? (options.backend === 'linx' ? 'native' : 'acp')),
1213
1697
  mode: requestedAutoModeMode(options),
1698
+ autoEnabled: requestedAutoEnabled(options),
1214
1699
  credentialSource: requestedCredentialSource(options),
1700
+ approvalStrategy: resolveApprovalStrategy(options),
1215
1701
  };
1216
1702
  try {
1703
+ throwIfAborted(requestedOptions.signal);
1217
1704
  session = buildConversationSession(requestedOptions);
1218
1705
  const activeSession = session;
1706
+ if (requestedOptions.signal) {
1707
+ const abortRun = () => {
1708
+ const reason = requestedOptions.signal?.reason;
1709
+ const error = reason instanceof Error
1710
+ ? reason
1711
+ : createAbortError(typeof reason === 'string' && reason.trim() ? reason : 'Auto-mode run aborted');
1712
+ fatalError ??= error;
1713
+ void activeSession.abort().catch(() => undefined);
1714
+ };
1715
+ if (requestedOptions.signal.aborted) {
1716
+ abortRun();
1717
+ }
1718
+ else {
1719
+ requestedOptions.signal.addEventListener('abort', abortRun, { once: true });
1720
+ abortCleanup = () => requestedOptions.signal?.removeEventListener('abort', abortRun);
1721
+ }
1722
+ }
1723
+ throwIfAborted(requestedOptions.signal);
1219
1724
  activeSession.display.start();
1220
- activeSession.display.showHelp();
1221
1725
  activeSession.display.setPhase('starting', `Preparing ${requestedOptions.backend}`);
1222
1726
  activeSession.display.updateQueue({
1223
1727
  steeringCount: 0,
@@ -1226,10 +1730,15 @@ export async function runAutoMode(options) {
1226
1730
  let resolvedRun;
1227
1731
  for (let attempt = 0;; attempt += 1) {
1228
1732
  try {
1229
- resolvedRun = await resolveAutoRunOptions(requestedOptions);
1733
+ throwIfAborted(requestedOptions.signal);
1734
+ resolvedRun = await withAbortSignal(resolveAutoRunOptions(requestedOptions), requestedOptions.signal);
1735
+ throwIfAborted(requestedOptions.signal);
1230
1736
  break;
1231
1737
  }
1232
1738
  catch (error) {
1739
+ if (isAbortError(error)) {
1740
+ throw error;
1741
+ }
1233
1742
  const message = error instanceof Error ? error.message : String(error);
1234
1743
  appendEntry(activeSession.record, 'system', JSON.stringify({
1235
1744
  type: 'credentials.resolve',
@@ -1237,11 +1746,20 @@ export async function runAutoMode(options) {
1237
1746
  requestedCredentialSource: requestedOptions.credentialSource,
1238
1747
  error: message,
1239
1748
  }), []);
1240
- if (isMissingProviderCredentialError(requestedOptions.backend, message)) {
1749
+ if (isAcpAutoModeWorkerBackend(requestedOptions.backend)
1750
+ && isMissingProviderCredentialError(requestedOptions.backend, message)) {
1751
+ if (requestedOptions.quiet) {
1752
+ throw new Error(`${message} Quiet worker sessions cannot prompt for provider credentials; configure the credential before dispatching Symphony workers.`);
1753
+ }
1241
1754
  if (attempt >= 2) {
1242
1755
  throw error;
1243
1756
  }
1244
- const keyAction = await promptBackendProviderKey(activeSession.display, requestedOptions.backend);
1757
+ appendEntry(activeSession.record, 'system', JSON.stringify({
1758
+ type: 'credentials.secretary.repair',
1759
+ backend: requestedOptions.backend,
1760
+ reason: 'missing',
1761
+ }), []);
1762
+ const keyAction = await promptBackendProviderCredential(activeSession.display, requestedOptions.backend, 'missing');
1245
1763
  if (keyAction !== 'saved') {
1246
1764
  throw error;
1247
1765
  }
@@ -1256,10 +1774,14 @@ export async function runAutoMode(options) {
1256
1774
  'Re-authorize LinX Cloud, then LinX will retry backend startup.',
1257
1775
  ], 'startup');
1258
1776
  if (authAction !== 'retry') {
1259
- throw error;
1777
+ const cancelError = new Error('Backend startup cancelled before LinX Cloud authorization.');
1778
+ fatalError = cancelError;
1779
+ appendAndDisplaySessionNote(activeSession.record, activeSession.display, cancelError.message, 'note');
1780
+ return 1;
1260
1781
  }
1261
1782
  }
1262
1783
  }
1784
+ throwIfAborted(requestedOptions.signal);
1263
1785
  activeSession.applyResolvedOptions(resolvedRun.options);
1264
1786
  appendEntry(activeSession.record, 'system', JSON.stringify({
1265
1787
  type: 'credentials.resolve',
@@ -1280,7 +1802,8 @@ export async function runAutoMode(options) {
1280
1802
  ]);
1281
1803
  throw new Error(message);
1282
1804
  }
1283
- await activeSession.start();
1805
+ await withAbortSignal(activeSession.start(), requestedOptions.signal);
1806
+ throwIfAborted(requestedOptions.signal);
1284
1807
  const steeringQueue = [];
1285
1808
  const followUpQueue = [];
1286
1809
  let stopRequested = false;
@@ -1316,6 +1839,28 @@ export async function runAutoMode(options) {
1316
1839
  followUpQueue.length = 0;
1317
1840
  updateQueueState();
1318
1841
  };
1842
+ if (requestedOptions.signal) {
1843
+ const wakeOnAbort = () => {
1844
+ const reason = requestedOptions.signal?.reason;
1845
+ fatalError ??= reason instanceof Error
1846
+ ? reason
1847
+ : createAbortError(typeof reason === 'string' && reason.trim() ? reason : 'Auto-mode run aborted');
1848
+ stopRequested = true;
1849
+ clearQueuedSubmissions();
1850
+ resolveWake();
1851
+ };
1852
+ if (requestedOptions.signal.aborted) {
1853
+ wakeOnAbort();
1854
+ }
1855
+ else {
1856
+ requestedOptions.signal.addEventListener('abort', wakeOnAbort, { once: true });
1857
+ const previousCleanup = abortCleanup;
1858
+ abortCleanup = () => {
1859
+ previousCleanup?.();
1860
+ requestedOptions.signal?.removeEventListener('abort', wakeOnAbort);
1861
+ };
1862
+ }
1863
+ }
1319
1864
  const restoreQueuedSubmission = () => {
1320
1865
  const restored = steeringQueue.pop() ?? followUpQueue.pop() ?? null;
1321
1866
  updateQueueState();
@@ -1348,11 +1893,20 @@ export async function runAutoMode(options) {
1348
1893
  if (!trimmed) {
1349
1894
  return;
1350
1895
  }
1896
+ if (requestedOptions.signal?.aborted) {
1897
+ const reason = requestedOptions.signal.reason;
1898
+ fatalError ??= reason instanceof Error
1899
+ ? reason
1900
+ : createAbortError(typeof reason === 'string' && reason.trim() ? reason : 'Auto-mode run aborted');
1901
+ stopRequested = true;
1902
+ resolveWake();
1903
+ return;
1904
+ }
1351
1905
  activeSession.display.showUserTurn(trimmed);
1352
1906
  activeSession.display.setPhase('running', `Running ${resolvedRun.options.backend}`);
1353
1907
  activeTurn = activeSession.sendTurn(trimmed)
1354
1908
  .catch((error) => {
1355
- fatalError = error instanceof Error ? error : new Error(String(error));
1909
+ fatalError ??= error instanceof Error ? error : new Error(String(error));
1356
1910
  })
1357
1911
  .finally(() => {
1358
1912
  activeTurn = null;
@@ -1380,6 +1934,7 @@ export async function runAutoMode(options) {
1380
1934
  if (!trimmed) {
1381
1935
  return;
1382
1936
  }
1937
+ throwIfAborted(requestedOptions.signal);
1383
1938
  const shellCommand = await handleAutoModeShellCommand({
1384
1939
  input: trimmed,
1385
1940
  session: activeSession,
@@ -1401,6 +1956,22 @@ export async function runAutoMode(options) {
1401
1956
  resolveWake();
1402
1957
  return;
1403
1958
  }
1959
+ if (shellCommand !== 'pass') {
1960
+ const projectedText = shellCommand.text.trim();
1961
+ if (!projectedText) {
1962
+ resolveWake();
1963
+ return;
1964
+ }
1965
+ if (activeTurn) {
1966
+ enqueueSubmission({
1967
+ text: projectedText,
1968
+ mode: submission.mode,
1969
+ });
1970
+ return;
1971
+ }
1972
+ runTurn(projectedText);
1973
+ return;
1974
+ }
1404
1975
  if (activeTurn) {
1405
1976
  enqueueSubmission({
1406
1977
  text: trimmed,
@@ -1446,17 +2017,24 @@ export async function runAutoMode(options) {
1446
2017
  throw fatalError;
1447
2018
  }
1448
2019
  finally {
2020
+ abortCleanup?.();
1449
2021
  if (session) {
1450
2022
  await session.close();
1451
2023
  const finalRecord = await session.finalizeAndClose(fatalError ? 'failed' : 'completed', fatalError?.message);
1452
2024
  const podSyncAbort = new AbortController();
1453
2025
  const podSyncTimeoutMessage = `timed out after ${POD_PERSISTENCE_TIMEOUT_MS}ms`;
2026
+ const podSyncSignal = options.signal && typeof AbortSignal.any === 'function'
2027
+ ? AbortSignal.any([options.signal, podSyncAbort.signal])
2028
+ : podSyncAbort.signal;
1454
2029
  await withTimeout(autoModeRuntime.persistAutoModeConversationToPod(finalRecord, undefined, {
1455
- signal: podSyncAbort.signal,
2030
+ signal: podSyncSignal,
1456
2031
  }), POD_PERSISTENCE_TIMEOUT_MS, podSyncTimeoutMessage, () => podSyncAbort.abort(new Error(podSyncTimeoutMessage))).catch((error) => {
1457
2032
  const message = error instanceof Error ? error.message : String(error);
2033
+ writeFailedPodSyncCheckpoint(finalRecord, message);
1458
2034
  appendSessionNote(finalRecord, `Pod sync failed | ${message}`, { error: message });
1459
- process.emitWarning(`LinX auto-mode Pod sync failed: ${message}`);
2035
+ if (!options.quiet) {
2036
+ process.emitWarning(`LinX auto-mode Pod sync failed: ${message}`);
2037
+ }
1460
2038
  });
1461
2039
  }
1462
2040
  if (options.plain) {
@@ -1478,12 +2056,31 @@ export function loadArchivedAutoModeSession(id) {
1478
2056
  export function loadArchivedAutoModeEvents(id) {
1479
2057
  return loadAutoModeEvents(id);
1480
2058
  }
2059
+ export function listArchivedAutoModeSessionsWithPendingSync() {
2060
+ return listAutoModeSessionsWithPendingSync();
2061
+ }
2062
+ export function hasArchivedAutoModeSessionPendingSync(record) {
2063
+ return hasPendingAutoModeSync(record);
2064
+ }
2065
+ export async function retryArchivedAutoModePodSync(id) {
2066
+ const record = loadAutoModeSession(id);
2067
+ if (!record) {
2068
+ throw new Error(`Auto-mode session not found: ${id}`);
2069
+ }
2070
+ if (record.status === 'running') {
2071
+ throw new Error(`Auto-mode session is still running: ${id}`);
2072
+ }
2073
+ return autoModeRuntime.persistAutoModeConversationToPod(record);
2074
+ }
1481
2075
  export function resumeAutoModeSession(record, options = {}) {
2076
+ if (!isAutoModeWorkerBackend(record.backend)) {
2077
+ throw new Error(`Cannot resume ${record.backend} through the worker auto-mode runner`);
2078
+ }
1482
2079
  const sessionId = record.backendSessionId?.trim() || record.id;
1483
2080
  return runAutoMode({
1484
2081
  backend: record.backend,
2082
+ autoEnabled: record.autoEnabled ?? record.mode === 'auto',
1485
2083
  mode: record.mode,
1486
- autoModeEnabled: record.mode === 'auto',
1487
2084
  resumeSessionId: sessionId,
1488
2085
  cwd: options.cwd || record.cwd,
1489
2086
  plain: Boolean(options.plain),
@@ -1492,23 +2089,28 @@ export function resumeAutoModeSession(record, options = {}) {
1492
2089
  goalMode: options.goalMode ?? record.goalMode,
1493
2090
  passthroughArgs: [...record.passthroughArgs],
1494
2091
  runtime: record.runtime,
1495
- transport: record.transport ?? 'acp',
2092
+ transport: record.transport ?? (record.backend === 'linx' ? 'native' : 'acp'),
1496
2093
  credentialSource: record.credentialSource,
1497
2094
  resolvedCredentialSource: record.resolvedCredentialSource,
1498
2095
  });
1499
2096
  }
1500
2097
  export { formatAutoModeSessionSummary };
1501
2098
  export function listSupportedAutoModeBackends() {
1502
- return listAutoModeHooks().map((hook) => ({
1503
- backend: hook.id,
1504
- label: hook.label,
1505
- description: hook.description,
1506
- capabilities: hook.capabilities,
1507
- modes: {
1508
- manual: describeAutoModeMode('manual'),
1509
- smart: describeAutoModeMode('smart'),
1510
- auto: describeAutoModeMode('auto'),
2099
+ return [
2100
+ {
2101
+ backend: linxNativeBackend.backend,
2102
+ label: linxNativeBackend.label,
2103
+ description: linxNativeBackend.description,
2104
+ capabilities: linxNativeBackend.capabilities,
2105
+ auto: describeAutoControl(),
1511
2106
  },
1512
- }));
2107
+ ...listAutoModeHooks().map((hook) => ({
2108
+ backend: hook.id,
2109
+ label: hook.label,
2110
+ description: hook.description,
2111
+ capabilities: hook.capabilities,
2112
+ auto: describeAutoControl(),
2113
+ })),
2114
+ ];
1513
2115
  }
1514
2116
  //# sourceMappingURL=runner.js.map