@undefineds.co/linx 0.2.18 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -21
- package/dist/index.js +93 -142
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-command.js +34 -30
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/auto-mode/archive.js +235 -0
- package/dist/lib/auto-mode/archive.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/auth.js +6 -6
- package/dist/lib/auto-mode/auth.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/codex-composer.js +1 -1
- package/dist/lib/auto-mode/codex-composer.js.map +1 -0
- package/dist/lib/auto-mode/codex-footer.js.map +1 -0
- package/dist/lib/auto-mode/codex-overlay.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/codex-request-form.js +2 -2
- package/dist/lib/auto-mode/codex-request-form.js.map +1 -0
- package/dist/lib/auto-mode/codex-request-input.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/display.js +63 -57
- package/dist/lib/auto-mode/display.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/format.js +24 -16
- package/dist/lib/auto-mode/format.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/hooks/claude.js +3 -2
- package/dist/lib/auto-mode/hooks/claude.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/hooks/codebuddy.js +3 -2
- package/dist/lib/auto-mode/hooks/codebuddy.js.map +1 -0
- package/dist/lib/auto-mode/hooks/codex.js +18 -0
- package/dist/lib/auto-mode/hooks/codex.js.map +1 -0
- package/dist/lib/auto-mode/hooks/index.js +27 -0
- package/dist/lib/auto-mode/hooks/index.js.map +1 -0
- package/dist/lib/auto-mode/hooks/shared.js.map +1 -0
- package/dist/lib/auto-mode/index.js.map +1 -0
- package/dist/lib/auto-mode/pod-ai.js +116 -0
- package/dist/lib/auto-mode/pod-ai.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/pod-approval.js +495 -325
- package/dist/lib/auto-mode/pod-approval.js.map +1 -0
- package/dist/lib/auto-mode/pod-persistence.js +412 -0
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/runner.js +280 -193
- package/dist/lib/auto-mode/runner.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/secretary.js +8 -8
- package/dist/lib/auto-mode/secretary.js.map +1 -0
- package/dist/lib/{watch → auto-mode}/types.js.map +1 -1
- package/dist/lib/auto-mode-command.js +114 -0
- package/dist/lib/auto-mode-command.js.map +1 -0
- package/dist/lib/codex-plugin/bridge.js +11 -12
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js +11 -12
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/linx-tui-contract.js +26 -0
- package/dist/lib/linx-tui-contract.js.map +1 -0
- package/dist/lib/login-command.js +9 -3
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +83 -104
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js +7 -1
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +9 -15
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +9 -3
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +19 -23
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +239 -1
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +64 -59
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +479 -0
- package/dist/lib/pi-adapter/pod-native.js.map +1 -0
- package/dist/lib/pi-adapter/pod-tools.js +76 -40
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +11 -66
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +276 -33
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/web-fetch.js +47 -30
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
- package/dist/lib/pod-chat-store.js +36 -38
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +97 -30
- package/dist/lib/pod-data-session.js.map +1 -1
- package/package.json +3 -3
- package/vendor/agent-runtime/dist/auto-mode.d.ts +287 -0
- package/vendor/agent-runtime/dist/auto-mode.js +1498 -0
- package/vendor/agent-runtime/dist/index.d.ts +2 -0
- package/vendor/agent-runtime/dist/index.js +2 -0
- package/vendor/agent-runtime/dist/runtime.d.ts +47 -0
- package/vendor/agent-runtime/dist/runtime.js +36 -0
- package/vendor/agent-runtime/dist/turn-controller.d.ts +4 -4
- package/vendor/agent-runtime/dist/turn-controller.js +7 -7
- package/vendor/agent-runtime/package.json +2 -0
- package/dist/lib/watch/archive.js +0 -110
- package/dist/lib/watch/archive.js.map +0 -1
- package/dist/lib/watch/auth.js.map +0 -1
- package/dist/lib/watch/codex-composer.js.map +0 -1
- package/dist/lib/watch/codex-footer.js.map +0 -1
- package/dist/lib/watch/codex-overlay.js.map +0 -1
- package/dist/lib/watch/codex-request-form.js.map +0 -1
- package/dist/lib/watch/codex-request-input.js.map +0 -1
- package/dist/lib/watch/display.js.map +0 -1
- package/dist/lib/watch/format.js.map +0 -1
- package/dist/lib/watch/hooks/claude.js.map +0 -1
- package/dist/lib/watch/hooks/codebuddy.js.map +0 -1
- package/dist/lib/watch/hooks/codex.js +0 -17
- package/dist/lib/watch/hooks/codex.js.map +0 -1
- package/dist/lib/watch/hooks/index.js +0 -24
- package/dist/lib/watch/hooks/index.js.map +0 -1
- package/dist/lib/watch/hooks/shared.js.map +0 -1
- package/dist/lib/watch/index.js.map +0 -1
- package/dist/lib/watch/pod-ai.js +0 -160
- package/dist/lib/watch/pod-ai.js.map +0 -1
- package/dist/lib/watch/pod-approval.js.map +0 -1
- package/dist/lib/watch/pod-persistence.js +0 -334
- package/dist/lib/watch/pod-persistence.js.map +0 -1
- package/dist/lib/watch/runner.js.map +0 -1
- package/dist/lib/watch/secretary.js.map +0 -1
- package/dist/watch-cli.js +0 -116
- package/dist/watch-cli.js.map +0 -1
- /package/dist/lib/{watch → auto-mode}/codex-footer.js +0 -0
- /package/dist/lib/{watch → auto-mode}/codex-overlay.js +0 -0
- /package/dist/lib/{watch → auto-mode}/codex-request-input.js +0 -0
- /package/dist/lib/{watch → auto-mode}/hooks/shared.js +0 -0
- /package/dist/lib/{watch → auto-mode}/index.js +0 -0
- /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,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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 {
|
|
12
|
+
import { resolveAutoModeSecretaryRecommendation } from './secretary.js';
|
|
13
13
|
import { promptText } from '../prompt.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
+
preflightAutoModeAuth,
|
|
19
21
|
loadPodBackendCredential,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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 =
|
|
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 ${
|
|
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(
|
|
123
|
+
return Math.max(0, Math.min(MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS, reactionWindowMs));
|
|
122
124
|
}
|
|
123
|
-
return Math.max(
|
|
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 ${
|
|
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?.(
|
|
185
|
-
},
|
|
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(
|
|
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
|
|
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(
|
|
213
|
-
const bar = `${'#'.repeat(filled)}${'-'.repeat(
|
|
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
|
|
218
|
+
async function promptLinxCloudAuth(display, lines, reason = 'manual') {
|
|
217
219
|
while (true) {
|
|
218
|
-
display.setPhase('question', '
|
|
219
|
-
const answer = (await display.chooseOption('
|
|
220
|
-
{ label: '
|
|
221
|
-
{ label: '
|
|
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 === '
|
|
224
|
-
display
|
|
225
|
-
return
|
|
226
|
+
if (answer === 'browser' || answer === 'b' || answer === '1') {
|
|
227
|
+
await runBackendLinxLogin(display);
|
|
228
|
+
return 'retry';
|
|
226
229
|
}
|
|
227
|
-
if (answer === '
|
|
228
|
-
display
|
|
229
|
-
|
|
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(
|
|
252
|
-
return
|
|
297
|
+
function requestedCredentialSource(_options) {
|
|
298
|
+
return 'cloud';
|
|
253
299
|
}
|
|
254
|
-
function requestedApprovalSource(
|
|
255
|
-
return
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
333
|
-
const
|
|
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:
|
|
369
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
:
|
|
507
|
-
|
|
508
|
-
:
|
|
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('
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
749
|
-
if (
|
|
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
|
|
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: ${
|
|
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 ${
|
|
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 | ${
|
|
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
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
|
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
|
|
867
|
+
const remote = await autoModeRuntime.createRemoteAutoModeApproval({
|
|
853
868
|
record: this.record,
|
|
854
869
|
request: interaction,
|
|
855
870
|
});
|
|
856
|
-
const remoteDecisionPromise =
|
|
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 ${
|
|
872
|
-
await
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1014
|
+
const hook = getAutoModeHook(options.backend);
|
|
997
1015
|
return new AcpSession(options, hook);
|
|
998
1016
|
}
|
|
999
|
-
async function
|
|
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
|
-
|
|
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 =
|
|
1076
|
+
const summaries = listAutoModeSessions().slice(0, 5).map(formatAutoModeSessionSummary);
|
|
1025
1077
|
if (summaries.length === 0) {
|
|
1026
|
-
appendAndDisplaySessionNote(record, display, 'No archived
|
|
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
|
|
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
|
|
1067
|
-
export async function
|
|
1068
|
-
const previousPlainEnv = process.env.
|
|
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.
|
|
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
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
|
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
|
|
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.
|
|
1367
|
+
delete process.env.LINX_BACKEND_PLAIN;
|
|
1299
1368
|
}
|
|
1300
1369
|
else {
|
|
1301
|
-
process.env.
|
|
1370
|
+
process.env.LINX_BACKEND_PLAIN = previousPlainEnv;
|
|
1302
1371
|
}
|
|
1303
1372
|
}
|
|
1304
1373
|
}
|
|
1305
1374
|
}
|
|
1306
|
-
export function
|
|
1307
|
-
return
|
|
1375
|
+
export function listArchivedAutoModeSessions() {
|
|
1376
|
+
return listAutoModeSessions();
|
|
1377
|
+
}
|
|
1378
|
+
export function loadArchivedAutoModeSession(id) {
|
|
1379
|
+
return loadAutoModeSession(id);
|
|
1308
1380
|
}
|
|
1309
|
-
export function
|
|
1310
|
-
return
|
|
1381
|
+
export function loadArchivedAutoModeEvents(id) {
|
|
1382
|
+
return loadAutoModeEvents(id);
|
|
1311
1383
|
}
|
|
1312
|
-
export function
|
|
1313
|
-
|
|
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 {
|
|
1316
|
-
export function
|
|
1317
|
-
return
|
|
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:
|
|
1324
|
-
smart:
|
|
1325
|
-
auto:
|
|
1410
|
+
manual: describeAutoModeMode('manual'),
|
|
1411
|
+
smart: describeAutoModeMode('smart'),
|
|
1412
|
+
auto: describeAutoModeMode('auto'),
|
|
1326
1413
|
},
|
|
1327
1414
|
}));
|
|
1328
1415
|
}
|