@undefineds.co/linx 0.3.5 → 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.
- package/README.md +58 -23
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +334 -162
- package/dist/index.js.map +1 -1
- package/dist/lib/account-session.js +4 -8
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +228 -178
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/auto-mode/archive.js +38 -7
- package/dist/lib/auto-mode/archive.js.map +1 -1
- package/dist/lib/auto-mode/auth.js.map +1 -1
- package/dist/lib/auto-mode/display.js +71 -45
- package/dist/lib/auto-mode/display.js.map +1 -1
- package/dist/lib/auto-mode/format.js +9 -7
- package/dist/lib/auto-mode/format.js.map +1 -1
- package/dist/lib/auto-mode/hooks/claude.js +12 -2
- package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
- package/dist/lib/auto-mode/hooks/codex.js +17 -7
- package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
- package/dist/lib/auto-mode/hooks/index.js +28 -8
- package/dist/lib/auto-mode/hooks/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-ai.js +20 -37
- package/dist/lib/auto-mode/pod-ai.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +124 -195
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/auto-mode/pod-persistence.js +169 -90
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/auto-mode/runner.js +683 -81
- package/dist/lib/auto-mode/runner.js.map +1 -1
- package/dist/lib/auto-mode/secretary.js +186 -41
- package/dist/lib/auto-mode/secretary.js.map +1 -1
- package/dist/lib/auto-mode-command.js +32 -32
- package/dist/lib/auto-mode-command.js.map +1 -1
- package/dist/lib/chat-api.js +242 -50
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js +164 -17
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/credentials-store.js +33 -42
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/linx-cloud-errors.js +61 -0
- package/dist/lib/linx-cloud-errors.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +8 -5
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/login-command.js +9 -2
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +3 -20
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +143 -17
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js +2 -6
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
- package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
- package/dist/lib/pi-adapter/backend-command.js +2 -0
- package/dist/lib/pi-adapter/backend-command.js.map +1 -0
- package/dist/lib/pi-adapter/backend-credentials.js +80 -0
- package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
- package/dist/lib/pi-adapter/branding.js +246 -108
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/control-state.js +72 -0
- package/dist/lib/pi-adapter/control-state.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +2634 -30
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +382 -210
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +531 -64
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +81 -85
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/pod-status-output.js +54 -0
- package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +458 -228
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session-control.js +509 -0
- package/dist/lib/pi-adapter/session-control.js.map +1 -0
- package/dist/lib/pi-adapter/session.js +35 -22
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +89 -32
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/sync-recovery.js +89 -0
- package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
- package/dist/lib/pi-adapter/web-fetch.js +13 -14
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
- package/dist/lib/pod-chat-store.js +254 -78
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +156 -35
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/solid-auth-store.js +27 -0
- package/dist/lib/solid-auth-store.js.map +1 -0
- package/dist/lib/solid-auth.js +2 -4
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/solid-client-credentials-login.js +100 -0
- package/dist/lib/solid-client-credentials-login.js.map +1 -0
- package/dist/lib/solid-local-store.js +31 -0
- package/dist/lib/solid-local-store.js.map +1 -0
- package/dist/lib/symphony/archive.js +328 -18
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +2222 -0
- package/dist/lib/symphony/pod-projection.js.map +1 -0
- package/dist/lib/symphony-command.js +602 -178
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/lib/sync-checkpoint-store.js +74 -0
- package/dist/lib/sync-checkpoint-store.js.map +1 -0
- package/dist/skills/symphony/SKILL.md +665 -0
- package/package.json +15 -9
- package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
- package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
- package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
- package/vendor/agent-runtime/dist/auto-mode.js +288 -31
- package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
- package/vendor/agent-runtime/dist/control-plane.js +79 -0
- package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
- package/vendor/agent-runtime/dist/file-sync.js +314 -0
- package/vendor/agent-runtime/dist/index.d.ts +7 -0
- package/vendor/agent-runtime/dist/index.js +7 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
- package/vendor/agent-runtime/dist/reconciler.js +361 -0
- package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
- package/vendor/agent-runtime/dist/symphony.js +362 -57
- package/vendor/agent-runtime/dist/sync.d.ts +271 -0
- package/vendor/agent-runtime/dist/sync.js +550 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
- package/vendor/agent-runtime/dist/turn-controller.js +2 -2
- package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
- package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
- package/vendor/agent-runtime/package.json +8 -1
- package/vendor/pi-web-access/CHANGELOG.md +387 -0
- package/vendor/pi-web-access/LICENSE +21 -0
- package/vendor/pi-web-access/README.md +352 -0
- package/vendor/pi-web-access/activity.ts +101 -0
- package/vendor/pi-web-access/banner.png +0 -0
- package/vendor/pi-web-access/chrome-cookies.ts +322 -0
- package/vendor/pi-web-access/code-search.ts +107 -0
- package/vendor/pi-web-access/curator-page.ts +3359 -0
- package/vendor/pi-web-access/curator-server.ts +605 -0
- package/vendor/pi-web-access/exa.ts +520 -0
- package/vendor/pi-web-access/extract.ts +641 -0
- package/vendor/pi-web-access/gemini-api.ts +112 -0
- package/vendor/pi-web-access/gemini-search.ts +361 -0
- package/vendor/pi-web-access/gemini-url-context.ts +126 -0
- package/vendor/pi-web-access/gemini-web-config.ts +52 -0
- package/vendor/pi-web-access/gemini-web.ts +396 -0
- package/vendor/pi-web-access/github-api.ts +196 -0
- package/vendor/pi-web-access/github-extract.ts +634 -0
- package/vendor/pi-web-access/index.ts +2346 -0
- package/vendor/pi-web-access/package.json +45 -0
- package/vendor/pi-web-access/pdf-extract.ts +192 -0
- package/vendor/pi-web-access/perplexity.ts +195 -0
- package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
- package/vendor/pi-web-access/rsc-extract.ts +338 -0
- package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
- package/vendor/pi-web-access/storage.ts +72 -0
- package/vendor/pi-web-access/summary-review.ts +276 -0
- package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
- package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
- package/vendor/pi-web-access/utils.ts +44 -0
- package/vendor/pi-web-access/video-extract.ts +378 -0
- package/vendor/pi-web-access/youtube-extract.ts +310 -0
- package/dist/lib/pi-adapter/auth.js +0 -68
- package/dist/lib/pi-adapter/auth.js.map +0 -1
- package/dist/lib/pi-adapter/pod-tools.js +0 -140
- package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
- package/dist/skills/drizzle-solid/SKILL.md +0 -340
- package/dist/skills/pod-storage/SKILL.md +0 -100
- package/dist/skills/solid-modeling/SKILL.md +0 -274
- 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 {
|
|
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: '
|
|
244
|
-
{ label: 'Exit', value: 'exit', description: 'leave
|
|
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 === '
|
|
251
|
-
|
|
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 '
|
|
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
|
|
422
|
+
async function promptBackendProviderCredential(display, backend, reason = 'missing') {
|
|
317
423
|
const provider = backendProviderId(backend);
|
|
318
424
|
const label = backendProviderLabel(backend);
|
|
319
|
-
display.
|
|
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
|
|
354
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
933
|
-
|
|
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
|
|
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
|
-
})
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
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
|