@undefineds.co/linx 0.3.4 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
import { createAgentRuntime, resolveAgentRuntimeConfig, runThreadReconcilerCycle, } from '../../../vendor/agent-runtime/dist/index.js';
|
|
2
|
+
import { createRemoteCompletionResult, isRemoteAuthExpiredError } from '../chat-api.js';
|
|
3
|
+
import { DEFAULT_LINX_CLOUD_MODEL_ID } from '../default-model.js';
|
|
4
|
+
import { getDefaultPodDataSession } from '../pod-data-session.js';
|
|
5
|
+
import { resolveRuntimeTarget } from '../runtime-target.js';
|
|
6
|
+
const AUTO_INPUT_DELAY_MS = 50;
|
|
7
|
+
const AUTO_INPUT_IDLE_WATCHDOG_MS = 1_000;
|
|
8
|
+
const DEFAULT_AUTO_INPUT_RECOVERY_DELAYS_MS = [500, 1_500, 3_000];
|
|
9
|
+
const DEFAULT_TRANSIENT_REMOTE_RECOVERY_DELAY_MS = 30_000;
|
|
10
|
+
const DEFAULT_GOAL_MODE_SUPERVISOR_INTERVAL_MS = 10 * 60 * 1000;
|
|
11
|
+
const MAX_CONTEXT_MESSAGES = 16;
|
|
12
|
+
const MAX_CONTEXT_CHARS = 12_000;
|
|
13
|
+
const MAX_GENERATED_INPUT_CHARS = 8_000;
|
|
14
|
+
const MAX_AUTO_INPUT_ATTEMPTS = 2;
|
|
15
|
+
const SECRETARY_AGENT_ID = '__secretary__';
|
|
16
|
+
const SECRETARY_AGENT_LABEL = 'AI Secretary';
|
|
17
|
+
const DEFAULT_SECRETARY_RUNTIME_BACKEND = {
|
|
18
|
+
backend: 'linx',
|
|
19
|
+
model: DEFAULT_LINX_CLOUD_MODEL_ID,
|
|
20
|
+
credentialSource: 'cloud',
|
|
21
|
+
};
|
|
22
|
+
const SECRETARY_SYSTEM_PROMPT = [
|
|
23
|
+
'You are the LinX AI Secretary running the auto input controller.',
|
|
24
|
+
'Auto mode is on, so you are taking over the next user input slot for the active backend session.',
|
|
25
|
+
'Produce only the exact next user message to submit.',
|
|
26
|
+
'Do not include reasoning, labels, markdown fences, or explanations.',
|
|
27
|
+
'When the backend asks for ordinary conversational input, a game move, or a next turn you can reasonably infer, answer as the user and keep the session moving.',
|
|
28
|
+
'For games, including 成语接龙, provide the next valid move directly.',
|
|
29
|
+
'When goal mode is on, act as a supervisor: send a concise steer only when the current chat peer needs direction, and return empty when no intervention is needed.',
|
|
30
|
+
'Outside goal mode, return an empty response only for missing credentials, missing authority, unsafe or destructive action, or genuinely unresolvable ambiguity.',
|
|
31
|
+
].join(' ');
|
|
32
|
+
const SECRETARY_RETRY_PREFIX = [
|
|
33
|
+
'The previous auto-input attempt returned empty.',
|
|
34
|
+
'Auto mode should not stop for ordinary conversation, games, or backend requests for the next user turn.',
|
|
35
|
+
'Return empty only for missing credentials, missing authority, unsafe or destructive action, or genuinely unresolvable ambiguity.',
|
|
36
|
+
'Otherwise write the exact next user input now.',
|
|
37
|
+
].join('\n');
|
|
38
|
+
export function getSecretaryAutoInputController(interactive, runtime, sessionControl) {
|
|
39
|
+
if (interactive?.__linxAutoInputController) {
|
|
40
|
+
return interactive.__linxAutoInputController;
|
|
41
|
+
}
|
|
42
|
+
const controller = new SecretaryAutoInputControllerImpl(interactive, runtime, sessionControl);
|
|
43
|
+
if (interactive && typeof interactive === 'object') {
|
|
44
|
+
interactive.__linxAutoInputController = controller;
|
|
45
|
+
}
|
|
46
|
+
return controller;
|
|
47
|
+
}
|
|
48
|
+
class SecretaryAutoInputControllerImpl {
|
|
49
|
+
interactive;
|
|
50
|
+
runtime;
|
|
51
|
+
sessionControl;
|
|
52
|
+
active = false;
|
|
53
|
+
running = false;
|
|
54
|
+
scheduled = null;
|
|
55
|
+
idleWatchdog = null;
|
|
56
|
+
recoveryTimer = null;
|
|
57
|
+
pendingReason = null;
|
|
58
|
+
generation = 0;
|
|
59
|
+
unsubscribe = null;
|
|
60
|
+
recoveryAttempts = 0;
|
|
61
|
+
pausedAssistantSignature = null;
|
|
62
|
+
goalModeSupervisedAssistantSignature = null;
|
|
63
|
+
currentTurnAbortController = null;
|
|
64
|
+
constructor(interactive, runtime, sessionControl) {
|
|
65
|
+
this.interactive = interactive;
|
|
66
|
+
this.runtime = runtime;
|
|
67
|
+
this.sessionControl = sessionControl;
|
|
68
|
+
}
|
|
69
|
+
start(options = {}) {
|
|
70
|
+
if (this.active) {
|
|
71
|
+
if (options.scheduleImmediately !== false) {
|
|
72
|
+
this.schedule('auto-on');
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.active = true;
|
|
77
|
+
this.generation += 1;
|
|
78
|
+
this.installRuntimeHooks();
|
|
79
|
+
this.startIdleWatchdog();
|
|
80
|
+
if (options.scheduleImmediately !== false) {
|
|
81
|
+
this.schedule('auto-on');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
stop() {
|
|
85
|
+
this.active = false;
|
|
86
|
+
this.generation += 1;
|
|
87
|
+
this.pendingReason = null;
|
|
88
|
+
this.abortCurrentTurn();
|
|
89
|
+
if (this.scheduled) {
|
|
90
|
+
clearTimeout(this.scheduled);
|
|
91
|
+
this.scheduled = null;
|
|
92
|
+
}
|
|
93
|
+
this.clearRecoveryTimer();
|
|
94
|
+
this.stopIdleWatchdog();
|
|
95
|
+
this.recoveryAttempts = 0;
|
|
96
|
+
this.pausedAssistantSignature = null;
|
|
97
|
+
this.goalModeSupervisedAssistantSignature = null;
|
|
98
|
+
}
|
|
99
|
+
abortCurrentTurn() {
|
|
100
|
+
this.currentTurnAbortController?.abort();
|
|
101
|
+
this.currentTurnAbortController = null;
|
|
102
|
+
}
|
|
103
|
+
schedule(reason) {
|
|
104
|
+
if (!this.active) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.pendingReason = reason;
|
|
108
|
+
this.clearRecoveryTimer();
|
|
109
|
+
if (this.scheduled || this.running) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.scheduled = setTimeout(() => {
|
|
113
|
+
this.scheduled = null;
|
|
114
|
+
void this.runOnce();
|
|
115
|
+
}, AUTO_INPUT_DELAY_MS);
|
|
116
|
+
}
|
|
117
|
+
async submit(text, options = {}) {
|
|
118
|
+
const trimmed = text.trim();
|
|
119
|
+
if (!trimmed) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!this.active) {
|
|
123
|
+
this.start({ scheduleImmediately: false });
|
|
124
|
+
}
|
|
125
|
+
const session = this.interactive?.session;
|
|
126
|
+
const reason = options.reason ?? 'auto-on';
|
|
127
|
+
const snapshot = this.sessionControl.ensureControlSession('auto');
|
|
128
|
+
const backend = normalizeString(this.runtime?.backendCommandRouter?.backend)
|
|
129
|
+
?? normalizeString(this.runtime?.backendSessionRef?.backend)
|
|
130
|
+
?? normalizeString(this.runtime?.backend);
|
|
131
|
+
try {
|
|
132
|
+
const projection = this.sessionControl.recordSecretaryRuntimeIntent({
|
|
133
|
+
text: trimmed,
|
|
134
|
+
reason,
|
|
135
|
+
});
|
|
136
|
+
const delivery = await deliverProjectedInput(this.interactive, session, trimmed);
|
|
137
|
+
this.sessionControl.recordAutoInputEvent('delivered', {
|
|
138
|
+
reason,
|
|
139
|
+
runtimeProjection: {
|
|
140
|
+
targetRole: projectedDeliveryTargetRole(delivery),
|
|
141
|
+
source: 'secretary-runtime-intent',
|
|
142
|
+
controlDecision: projection,
|
|
143
|
+
},
|
|
144
|
+
businessSession: snapshot.businessSession,
|
|
145
|
+
backend,
|
|
146
|
+
length: trimmed.length,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
151
|
+
this.sessionControl.recordAutoInputEvent('failed', {
|
|
152
|
+
reason,
|
|
153
|
+
message,
|
|
154
|
+
businessSession: snapshot.businessSession,
|
|
155
|
+
backend,
|
|
156
|
+
});
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
installRuntimeHooks() {
|
|
161
|
+
if (this.unsubscribe || typeof this.interactive?.session?.subscribe !== 'function') {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.unsubscribe = this.interactive.session.subscribe((event) => {
|
|
165
|
+
if (!isRecord(event) || event.type !== 'agent_end') {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (isGoalModeActive(this.interactive, this.runtime)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.schedule('agent-end');
|
|
172
|
+
});
|
|
173
|
+
const originalStop = this.interactive?.stop?.bind(this.interactive);
|
|
174
|
+
if (typeof originalStop === 'function' && !this.interactive.__linxAutoInputStopPatched) {
|
|
175
|
+
this.interactive.stop = (...args) => {
|
|
176
|
+
try {
|
|
177
|
+
this.stop();
|
|
178
|
+
this.unsubscribe?.();
|
|
179
|
+
this.unsubscribe = null;
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
originalStop(...args);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
this.interactive.__linxAutoInputStopPatched = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
startIdleWatchdog() {
|
|
189
|
+
if (this.idleWatchdog) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.idleWatchdog = setInterval(() => {
|
|
193
|
+
if (!this.active || this.running || this.scheduled || this.recoveryTimer || this.interactive?.__autoEnabled !== true) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const session = this.interactive?.session;
|
|
197
|
+
if (!session || session.isStreaming === true) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const recentMessages = resolveRecentMessages(this.interactive);
|
|
201
|
+
const latest = recentMessages.at(-1);
|
|
202
|
+
if (latest?.role !== 'assistant') {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const signature = createAssistantSignature(latest.text);
|
|
206
|
+
if (signature && signature === this.pausedAssistantSignature) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (isGoalModeActive(this.interactive, this.runtime)) {
|
|
210
|
+
if (signature && signature === this.goalModeSupervisedAssistantSignature) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!isGoalModeSupervisorDue(this.interactive, this.runtime)) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
markGoalModeSupervisorChecked(this.interactive, this.runtime);
|
|
217
|
+
this.goalModeSupervisedAssistantSignature = signature;
|
|
218
|
+
this.schedule('runtime-idle');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.goalModeSupervisedAssistantSignature = null;
|
|
222
|
+
this.schedule('runtime-idle');
|
|
223
|
+
}, AUTO_INPUT_IDLE_WATCHDOG_MS);
|
|
224
|
+
this.idleWatchdog.unref?.();
|
|
225
|
+
}
|
|
226
|
+
stopIdleWatchdog() {
|
|
227
|
+
if (!this.idleWatchdog) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
clearInterval(this.idleWatchdog);
|
|
231
|
+
this.idleWatchdog = null;
|
|
232
|
+
}
|
|
233
|
+
async runOnce() {
|
|
234
|
+
if (!this.active || this.running || this.interactive?.__autoEnabled !== true) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const session = this.interactive?.session;
|
|
238
|
+
if (!session || session.isStreaming === true) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
this.running = true;
|
|
242
|
+
const generation = this.generation;
|
|
243
|
+
const reason = this.pendingReason;
|
|
244
|
+
this.pendingReason = null;
|
|
245
|
+
const turnAbortController = new AbortController();
|
|
246
|
+
this.currentTurnAbortController = turnAbortController;
|
|
247
|
+
let context = null;
|
|
248
|
+
try {
|
|
249
|
+
context = this.buildContext();
|
|
250
|
+
if (!context || !shouldGenerateNextUserInput(context)) {
|
|
251
|
+
this.resetRecoveryState();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (resolveAssistantSignature(context) === this.pausedAssistantSignature) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const execution = await this.runThroughThreadReconciler({
|
|
258
|
+
context,
|
|
259
|
+
reason,
|
|
260
|
+
generation,
|
|
261
|
+
signal: turnAbortController.signal,
|
|
262
|
+
});
|
|
263
|
+
context = execution.context;
|
|
264
|
+
const { reconciliation, scheduler, turn, text, attempts } = execution;
|
|
265
|
+
if (!this.active || generation !== this.generation || this.interactive?.__autoEnabled !== true) {
|
|
266
|
+
this.sessionControl.recordAutoInputEvent('failed', {
|
|
267
|
+
reason,
|
|
268
|
+
message: 'AI Secretary auto input projection was cancelled before delivery',
|
|
269
|
+
run: summarizeRuntimeRun(turn.run),
|
|
270
|
+
steps: summarizeRuntimeSteps(turn.steps),
|
|
271
|
+
businessSession: context.snapshot.businessSession,
|
|
272
|
+
backend: context.backend,
|
|
273
|
+
reconciler: reconciliation,
|
|
274
|
+
scheduler,
|
|
275
|
+
attempts,
|
|
276
|
+
});
|
|
277
|
+
this.scheduleRecovery('runtime-idle', 'AI Secretary returned empty user input projection', context, generation);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!text) {
|
|
281
|
+
if (context.goalMode) {
|
|
282
|
+
this.resetRecoveryState();
|
|
283
|
+
this.sessionControl.recordAutoInputEvent('skipped', {
|
|
284
|
+
reason,
|
|
285
|
+
message: 'Goal supervision found no Secretary intervention needed.',
|
|
286
|
+
run: summarizeRuntimeRun(turn.run),
|
|
287
|
+
steps: summarizeRuntimeSteps(turn.steps),
|
|
288
|
+
businessSession: context.snapshot.businessSession,
|
|
289
|
+
backend: context.backend,
|
|
290
|
+
reconciler: reconciliation,
|
|
291
|
+
scheduler,
|
|
292
|
+
attempts,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.sessionControl.recordAutoInputEvent('failed', {
|
|
297
|
+
reason,
|
|
298
|
+
message: 'AI Secretary returned empty user input projection',
|
|
299
|
+
run: summarizeRuntimeRun(turn.run),
|
|
300
|
+
steps: summarizeRuntimeSteps(turn.steps),
|
|
301
|
+
businessSession: context.snapshot.businessSession,
|
|
302
|
+
backend: context.backend,
|
|
303
|
+
reconciler: reconciliation,
|
|
304
|
+
scheduler,
|
|
305
|
+
attempts,
|
|
306
|
+
});
|
|
307
|
+
this.scheduleRecovery('runtime-idle', 'AI Secretary returned empty user input projection', context, generation);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const projection = this.sessionControl.recordSecretaryRuntimeIntent({
|
|
311
|
+
text,
|
|
312
|
+
reason,
|
|
313
|
+
});
|
|
314
|
+
const delivery = await deliverProjectedInput(this.interactive, session, text);
|
|
315
|
+
this.resetRecoveryState();
|
|
316
|
+
this.sessionControl.recordAutoInputEvent('delivered', {
|
|
317
|
+
reason,
|
|
318
|
+
runtimeProjection: {
|
|
319
|
+
targetRole: projectedDeliveryTargetRole(delivery),
|
|
320
|
+
source: 'secretary-runtime-intent',
|
|
321
|
+
controlDecision: projection,
|
|
322
|
+
},
|
|
323
|
+
run: summarizeRuntimeRun(turn.run),
|
|
324
|
+
steps: summarizeRuntimeSteps(turn.steps),
|
|
325
|
+
businessSession: context.snapshot.businessSession,
|
|
326
|
+
backend: context.backend,
|
|
327
|
+
reconciler: reconciliation,
|
|
328
|
+
scheduler,
|
|
329
|
+
length: text.length,
|
|
330
|
+
attempts,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
335
|
+
this.sessionControl.recordAutoInputEvent('failed', {
|
|
336
|
+
reason,
|
|
337
|
+
message,
|
|
338
|
+
run: isAgentRuntimeTurnError(error) ? summarizeRuntimeRun(error.run) : undefined,
|
|
339
|
+
steps: isAgentRuntimeTurnError(error) ? summarizeRuntimeSteps(error.steps) : undefined,
|
|
340
|
+
});
|
|
341
|
+
this.scheduleRecovery('runtime-idle', message, context, generation, error);
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
this.running = false;
|
|
345
|
+
if (this.currentTurnAbortController === turnAbortController) {
|
|
346
|
+
this.currentTurnAbortController = null;
|
|
347
|
+
}
|
|
348
|
+
if (this.active && this.pendingReason) {
|
|
349
|
+
this.schedule(this.pendingReason);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
buildContext() {
|
|
354
|
+
const snapshot = this.sessionControl.ensureControlSession('auto');
|
|
355
|
+
const recentMessages = resolveRecentMessages(this.interactive);
|
|
356
|
+
if (recentMessages.length === 0) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
snapshot,
|
|
361
|
+
backend: normalizeString(this.runtime?.backendCommandRouter?.backend)
|
|
362
|
+
?? normalizeString(this.runtime?.backendSessionRef?.backend)
|
|
363
|
+
?? normalizeString(this.runtime?.backend),
|
|
364
|
+
cwd: normalizeString(this.interactive?.session?.cwd)
|
|
365
|
+
?? normalizeString(this.runtime?.cwd)
|
|
366
|
+
?? snapshot.businessSession.cwd,
|
|
367
|
+
model: normalizeString(this.interactive?.session?.model?.id)
|
|
368
|
+
?? normalizeString(this.runtime?.model),
|
|
369
|
+
goalMode: isGoalModeActive(this.interactive, this.runtime),
|
|
370
|
+
supervisorIntervalMs: resolveGoalModeSupervisorIntervalMs(this.interactive, this.runtime),
|
|
371
|
+
recentMessages,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async runThroughThreadReconciler(input) {
|
|
375
|
+
let context = null;
|
|
376
|
+
let inputText = '';
|
|
377
|
+
let wakeError;
|
|
378
|
+
let turn = null;
|
|
379
|
+
let text = null;
|
|
380
|
+
let attempts = 1;
|
|
381
|
+
const injectedResolver = resolveInjectedResolver(this.runtime);
|
|
382
|
+
const cycle = await runThreadReconcilerCycle({
|
|
383
|
+
policy: {
|
|
384
|
+
kind: 'auto',
|
|
385
|
+
secretaryAgent: SECRETARY_AGENT_ID,
|
|
386
|
+
},
|
|
387
|
+
handleWakeJob: async ({ decisionSummary }) => {
|
|
388
|
+
try {
|
|
389
|
+
const reconciliation = decisionSummary;
|
|
390
|
+
context = {
|
|
391
|
+
...input.context,
|
|
392
|
+
reconciliation,
|
|
393
|
+
};
|
|
394
|
+
inputText = renderSecretaryAutoInputContext(context);
|
|
395
|
+
const firstTurn = await runSecretaryAutoInputTurn({
|
|
396
|
+
runtime: this.runtime,
|
|
397
|
+
context,
|
|
398
|
+
reason: input.reason,
|
|
399
|
+
inputText,
|
|
400
|
+
injectedResolver,
|
|
401
|
+
reconciliation,
|
|
402
|
+
attempt: 1,
|
|
403
|
+
signal: input.signal,
|
|
404
|
+
});
|
|
405
|
+
const firstText = normalizeGeneratedInput(firstTurn.content);
|
|
406
|
+
const shouldRetry = !firstText
|
|
407
|
+
&& this.active
|
|
408
|
+
&& input.generation === this.generation
|
|
409
|
+
&& this.interactive?.__autoEnabled === true;
|
|
410
|
+
turn = shouldRetry
|
|
411
|
+
? await runSecretaryAutoInputTurn({
|
|
412
|
+
runtime: this.runtime,
|
|
413
|
+
context,
|
|
414
|
+
reason: input.reason,
|
|
415
|
+
inputText: renderSecretaryAutoInputRetryContext(inputText),
|
|
416
|
+
injectedResolver,
|
|
417
|
+
reconciliation,
|
|
418
|
+
attempt: 2,
|
|
419
|
+
signal: input.signal,
|
|
420
|
+
})
|
|
421
|
+
: firstTurn;
|
|
422
|
+
attempts = shouldRetry ? MAX_AUTO_INPUT_ATTEMPTS : 1;
|
|
423
|
+
text = shouldRetry ? normalizeGeneratedInput(turn.content) : firstText;
|
|
424
|
+
return {
|
|
425
|
+
attempts,
|
|
426
|
+
textLength: text?.length ?? 0,
|
|
427
|
+
run: turn.run.id,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
wakeError = error;
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
event: createAutoInputThreadEvent(input.context, input.reason),
|
|
436
|
+
dispatchOptions: {
|
|
437
|
+
randomId: `auto-${input.reason ?? 'scheduled'}-${Date.now()}`,
|
|
438
|
+
},
|
|
439
|
+
onDispatched: (dispatch) => {
|
|
440
|
+
const reconciliation = dispatch.summary;
|
|
441
|
+
context = {
|
|
442
|
+
...input.context,
|
|
443
|
+
reconciliation,
|
|
444
|
+
};
|
|
445
|
+
inputText = renderSecretaryAutoInputContext(context);
|
|
446
|
+
this.sessionControl.recordAutoInputEvent('requested', {
|
|
447
|
+
reason: input.reason,
|
|
448
|
+
businessSession: context.snapshot.businessSession,
|
|
449
|
+
backend: context.backend,
|
|
450
|
+
reconciler: reconciliation,
|
|
451
|
+
scheduler: {
|
|
452
|
+
wakeRecords: dispatch.wakeRecordSummaries,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
const reconciliation = cycle.summary;
|
|
458
|
+
const scheduler = cycle.schedulerSummary;
|
|
459
|
+
if (scheduler.failed.length > 0) {
|
|
460
|
+
throw wakeError ?? new Error(String(scheduler.failed[0]?.error ?? 'AI Secretary wake job failed'));
|
|
461
|
+
}
|
|
462
|
+
if (!turn) {
|
|
463
|
+
throw new Error('AI Secretary was not awakened for auto input.');
|
|
464
|
+
}
|
|
465
|
+
if (!context) {
|
|
466
|
+
throw new Error('AI Secretary auto input context was not prepared by the Thread Reconciler.');
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
context,
|
|
470
|
+
reconciliation,
|
|
471
|
+
scheduler,
|
|
472
|
+
turn,
|
|
473
|
+
text,
|
|
474
|
+
attempts,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
scheduleRecovery(reason, message, context, generation, error) {
|
|
478
|
+
if (!this.active || generation !== this.generation || this.interactive?.__autoEnabled !== true) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (!isRecoverableAutoInputFailure(message, error)) {
|
|
482
|
+
this.pauseOnAssistant(context, `Auto waiting for user: ${message}`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (isTransientRemoteAutoInputFailure(message, error)) {
|
|
486
|
+
this.scheduleTransientRemoteRecovery(reason, message, generation);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const recoveryDelaysMs = resolveAutoInputRecoveryDelaysMs();
|
|
490
|
+
if (this.recoveryAttempts >= recoveryDelaysMs.length) {
|
|
491
|
+
this.pauseOnAssistant(context, `Auto waiting for user: Secretary could not recover after ${this.recoveryAttempts} restart attempts. ${message}`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const attempt = this.recoveryAttempts + 1;
|
|
495
|
+
const delayMs = recoveryDelaysMs[this.recoveryAttempts];
|
|
496
|
+
this.recoveryAttempts = attempt;
|
|
497
|
+
this.clearRecoveryTimer();
|
|
498
|
+
this.interactive?.showStatus?.(`Auto recovering: restarting Secretary (${attempt}/${recoveryDelaysMs.length}).`);
|
|
499
|
+
this.interactive?.ui?.requestRender?.();
|
|
500
|
+
this.recoveryTimer = setTimeout(() => {
|
|
501
|
+
this.recoveryTimer = null;
|
|
502
|
+
if (!this.active || generation !== this.generation || this.interactive?.__autoEnabled !== true) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.schedule(reason);
|
|
506
|
+
}, delayMs);
|
|
507
|
+
this.recoveryTimer.unref?.();
|
|
508
|
+
}
|
|
509
|
+
scheduleTransientRemoteRecovery(reason, message, generation) {
|
|
510
|
+
const attempt = this.recoveryAttempts + 1;
|
|
511
|
+
const delayMs = resolveTransientRemoteRecoveryDelayMs();
|
|
512
|
+
this.recoveryAttempts = attempt;
|
|
513
|
+
this.clearRecoveryTimer();
|
|
514
|
+
this.interactive?.showStatus?.(`Auto waiting for LinX Cloud: temporarily unavailable. Secretary will retry in ${formatDelay(delayMs)}. ${message}`);
|
|
515
|
+
this.interactive?.ui?.requestRender?.();
|
|
516
|
+
this.recoveryTimer = setTimeout(() => {
|
|
517
|
+
this.recoveryTimer = null;
|
|
518
|
+
if (!this.active || generation !== this.generation || this.interactive?.__autoEnabled !== true) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.schedule(reason);
|
|
522
|
+
}, delayMs);
|
|
523
|
+
this.recoveryTimer.unref?.();
|
|
524
|
+
}
|
|
525
|
+
pauseOnAssistant(context, message) {
|
|
526
|
+
this.pausedAssistantSignature = context ? resolveAssistantSignature(context) : null;
|
|
527
|
+
this.clearRecoveryTimer();
|
|
528
|
+
this.interactive?.showStatus?.(message);
|
|
529
|
+
this.interactive?.ui?.requestRender?.();
|
|
530
|
+
}
|
|
531
|
+
clearRecoveryTimer() {
|
|
532
|
+
if (!this.recoveryTimer) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
clearTimeout(this.recoveryTimer);
|
|
536
|
+
this.recoveryTimer = null;
|
|
537
|
+
}
|
|
538
|
+
resetRecoveryState() {
|
|
539
|
+
this.recoveryAttempts = 0;
|
|
540
|
+
this.pausedAssistantSignature = null;
|
|
541
|
+
this.clearRecoveryTimer();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function shouldGenerateNextUserInput(context) {
|
|
545
|
+
const latest = context.recentMessages.at(-1);
|
|
546
|
+
return latest?.role === 'assistant';
|
|
547
|
+
}
|
|
548
|
+
function isGoalModeActive(interactive, runtime) {
|
|
549
|
+
return interactive?.__linxGoalModeEnabled === true || runtime?.goalMode === true;
|
|
550
|
+
}
|
|
551
|
+
function resolveGoalModeSupervisorIntervalMs(interactive, runtime) {
|
|
552
|
+
const value = Number(interactive?.__linxGoalModeSupervisorIntervalMs
|
|
553
|
+
?? runtime?.goalModeSupervisorIntervalMs
|
|
554
|
+
?? interactive?.__linxSymphonyWorkerSupervisorIntervalMs
|
|
555
|
+
?? runtime?.symphonyWorkerSupervisorIntervalMs
|
|
556
|
+
?? DEFAULT_GOAL_MODE_SUPERVISOR_INTERVAL_MS);
|
|
557
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
558
|
+
return DEFAULT_GOAL_MODE_SUPERVISOR_INTERVAL_MS;
|
|
559
|
+
}
|
|
560
|
+
return Math.trunc(value);
|
|
561
|
+
}
|
|
562
|
+
function resolveGoalModeSupervisorLastAt(interactive, runtime) {
|
|
563
|
+
const value = Number(interactive?.__linxGoalModeSupervisorLastAt ?? runtime?.goalModeSupervisorLastAt);
|
|
564
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
565
|
+
}
|
|
566
|
+
function markGoalModeSupervisorChecked(interactive, runtime, at = Date.now()) {
|
|
567
|
+
if (interactive && typeof interactive === 'object') {
|
|
568
|
+
interactive.__linxGoalModeSupervisorLastAt = at;
|
|
569
|
+
}
|
|
570
|
+
if (runtime && typeof runtime === 'object') {
|
|
571
|
+
runtime.goalModeSupervisorLastAt = at;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function isGoalModeSupervisorDue(interactive, runtime) {
|
|
575
|
+
const now = Date.now();
|
|
576
|
+
const lastAt = resolveGoalModeSupervisorLastAt(interactive, runtime);
|
|
577
|
+
if (!lastAt) {
|
|
578
|
+
markGoalModeSupervisorChecked(interactive, runtime, now);
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return now - lastAt >= resolveGoalModeSupervisorIntervalMs(interactive, runtime);
|
|
582
|
+
}
|
|
583
|
+
function createAutoInputThreadEvent(context, reason) {
|
|
584
|
+
const businessSession = context.snapshot.businessSession;
|
|
585
|
+
const thread = businessSession.id ?? businessSession.file ?? `cwd:${businessSession.cwd}`;
|
|
586
|
+
return {
|
|
587
|
+
type: 'message.appended',
|
|
588
|
+
thread,
|
|
589
|
+
chat: businessSession.id ?? businessSession.file,
|
|
590
|
+
actor: {
|
|
591
|
+
id: context.backend ?? 'backend-runtime',
|
|
592
|
+
role: 'primary-agent',
|
|
593
|
+
},
|
|
594
|
+
data: {
|
|
595
|
+
source: 'primary-agent',
|
|
596
|
+
reason,
|
|
597
|
+
backend: context.backend,
|
|
598
|
+
businessSession: businessSession.id,
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function resolveInjectedResolver(runtime) {
|
|
603
|
+
const candidates = [
|
|
604
|
+
runtime?.sessionControl?.resolveNextUserInput,
|
|
605
|
+
runtime?.resolveNextUserInput,
|
|
606
|
+
runtime?.resolveSecretaryNextUserInput,
|
|
607
|
+
];
|
|
608
|
+
for (const candidate of candidates) {
|
|
609
|
+
if (typeof candidate === 'function') {
|
|
610
|
+
return candidate;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
async function runSecretaryAutoInputTurn(input) {
|
|
616
|
+
const startupOverride = resolveAgentRuntimeStartupOverride(input.runtime);
|
|
617
|
+
const agentConfig = resolveAgentRuntimeConfig({
|
|
618
|
+
agent: SECRETARY_AGENT_ID,
|
|
619
|
+
role: 'secretary',
|
|
620
|
+
model: DEFAULT_LINX_CLOUD_MODEL_ID,
|
|
621
|
+
label: SECRETARY_AGENT_LABEL,
|
|
622
|
+
runtime: DEFAULT_SECRETARY_RUNTIME_BACKEND,
|
|
623
|
+
systemPrompt: SECRETARY_SYSTEM_PROMPT,
|
|
624
|
+
metadata: {
|
|
625
|
+
mode: 'auto',
|
|
626
|
+
backend: input.context.backend,
|
|
627
|
+
cwd: input.context.cwd,
|
|
628
|
+
businessSession: input.context.snapshot.businessSession,
|
|
629
|
+
controlSession: input.context.snapshot.controlSession,
|
|
630
|
+
reconciler: input.reconciliation,
|
|
631
|
+
},
|
|
632
|
+
}, {
|
|
633
|
+
model: startupOverride?.model ?? input.context.model,
|
|
634
|
+
runtime: startupOverride,
|
|
635
|
+
});
|
|
636
|
+
return createAgentRuntime(agentConfig, async ({ agent, messages, signal }) => {
|
|
637
|
+
if (input.injectedResolver) {
|
|
638
|
+
const text = await input.injectedResolver(input.context);
|
|
639
|
+
return {
|
|
640
|
+
content: normalizeGeneratedInput(text) ?? '',
|
|
641
|
+
raw: {
|
|
642
|
+
source: 'injected-resolver',
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
return resolveNextUserInputFromSecretaryRuntime(input.runtime, input.context, agent, messages, signal);
|
|
647
|
+
}).runTurn({
|
|
648
|
+
input: input.inputText,
|
|
649
|
+
messages: [{
|
|
650
|
+
role: 'user',
|
|
651
|
+
source: 'user',
|
|
652
|
+
content: input.inputText,
|
|
653
|
+
metadata: {
|
|
654
|
+
businessSession: input.context.snapshot.businessSession,
|
|
655
|
+
controlSession: input.context.snapshot.controlSession,
|
|
656
|
+
reconciler: input.reconciliation,
|
|
657
|
+
},
|
|
658
|
+
}],
|
|
659
|
+
signal: combineAbortSignals(AbortSignal.timeout(15_000), input.signal),
|
|
660
|
+
metadata: {
|
|
661
|
+
reason: input.reason,
|
|
662
|
+
backend: input.context.backend,
|
|
663
|
+
attempt: input.attempt,
|
|
664
|
+
reconciler: input.reconciliation,
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
function combineAbortSignals(signal, extraSignal) {
|
|
669
|
+
if (!extraSignal) {
|
|
670
|
+
return signal;
|
|
671
|
+
}
|
|
672
|
+
if (typeof AbortSignal.any === 'function') {
|
|
673
|
+
return AbortSignal.any([signal, extraSignal]);
|
|
674
|
+
}
|
|
675
|
+
const controller = new AbortController();
|
|
676
|
+
const abort = () => controller.abort();
|
|
677
|
+
if (signal.aborted || extraSignal.aborted) {
|
|
678
|
+
abort();
|
|
679
|
+
return controller.signal;
|
|
680
|
+
}
|
|
681
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
682
|
+
extraSignal.addEventListener('abort', abort, { once: true });
|
|
683
|
+
return controller.signal;
|
|
684
|
+
}
|
|
685
|
+
async function resolveNextUserInputFromSecretaryRuntime(runtime, context, agent, messages, signal) {
|
|
686
|
+
const backend = normalizeString(agent.runtime?.backend) ?? 'linx';
|
|
687
|
+
if (backend !== 'linx') {
|
|
688
|
+
throw new SecretaryAutoInputBlockedError(`Secretary runtime backend ${backend} is not supported yet.`);
|
|
689
|
+
}
|
|
690
|
+
const session = await resolveSecretaryPodDataSession(runtime);
|
|
691
|
+
if (!session) {
|
|
692
|
+
throw new SecretaryAutoInputBlockedError('LinX login is required before Secretary can drive auto input.');
|
|
693
|
+
}
|
|
694
|
+
const target = resolveRuntimeTarget({ issuerUrl: session.credentials.url });
|
|
695
|
+
const model = normalizeString(agent.runtime?.model) ?? agent.model;
|
|
696
|
+
const result = await createRemoteCompletionResult({
|
|
697
|
+
runtimeUrl: target.runtimeUrl,
|
|
698
|
+
authFetch: session.runtimeFetch,
|
|
699
|
+
model,
|
|
700
|
+
messages,
|
|
701
|
+
signal,
|
|
702
|
+
});
|
|
703
|
+
return {
|
|
704
|
+
content: result.content,
|
|
705
|
+
reasoningContent: result.reasoningContent,
|
|
706
|
+
finishReason: result.finishReason,
|
|
707
|
+
usage: result.usage ? { ...result.usage } : undefined,
|
|
708
|
+
raw: result,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
async function resolveSecretaryPodDataSession(runtime) {
|
|
712
|
+
const runtimeSessionGetter = runtime?.getPodDataSession;
|
|
713
|
+
if (typeof runtimeSessionGetter === 'function') {
|
|
714
|
+
return runtimeSessionGetter.call(runtime);
|
|
715
|
+
}
|
|
716
|
+
return getDefaultPodDataSession();
|
|
717
|
+
}
|
|
718
|
+
function resolveAgentRuntimeStartupOverride(runtime) {
|
|
719
|
+
const candidates = [
|
|
720
|
+
runtime?.agentRuntimeConfig,
|
|
721
|
+
runtime?.agentRuntime,
|
|
722
|
+
];
|
|
723
|
+
for (const candidate of candidates) {
|
|
724
|
+
if (!isRecord(candidate)) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const resolved = normalizeAgentRuntimeBackendConfig(candidate);
|
|
728
|
+
if (resolved) {
|
|
729
|
+
return resolved;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return undefined;
|
|
733
|
+
}
|
|
734
|
+
function normalizeAgentRuntimeBackendConfig(value) {
|
|
735
|
+
const backend = normalizeString(value.backend);
|
|
736
|
+
const model = normalizeString(value.model);
|
|
737
|
+
const credentialSource = normalizeString(value.credentialSource);
|
|
738
|
+
const runtime = normalizeString(value.runtime);
|
|
739
|
+
const transport = normalizeString(value.transport);
|
|
740
|
+
const endpoint = normalizeString(value.endpoint);
|
|
741
|
+
const metadata = isRecord(value.metadata) ? { ...value.metadata } : undefined;
|
|
742
|
+
const resolved = {
|
|
743
|
+
...(backend ? { backend } : {}),
|
|
744
|
+
...(model ? { model } : {}),
|
|
745
|
+
...(credentialSource ? { credentialSource } : {}),
|
|
746
|
+
...(runtime ? { runtime } : {}),
|
|
747
|
+
...(transport ? { transport } : {}),
|
|
748
|
+
...(endpoint ? { endpoint } : {}),
|
|
749
|
+
...(metadata ? { metadata } : {}),
|
|
750
|
+
};
|
|
751
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
752
|
+
}
|
|
753
|
+
function renderSecretaryAutoInputContext(context) {
|
|
754
|
+
return [
|
|
755
|
+
`Backend: ${context.backend ?? 'unknown'}`,
|
|
756
|
+
`Workspace: ${context.cwd}`,
|
|
757
|
+
context.model ? `Model: ${context.model}` : null,
|
|
758
|
+
`Goal mode: ${context.goalMode ? 'on' : 'off'}`,
|
|
759
|
+
context.goalMode && context.supervisorIntervalMs
|
|
760
|
+
? `Supervisor interval: ${context.supervisorIntervalMs}ms`
|
|
761
|
+
: null,
|
|
762
|
+
'',
|
|
763
|
+
'Recent visible conversation:',
|
|
764
|
+
...context.recentMessages.map((message) => `[${message.role}] ${message.text}`),
|
|
765
|
+
'',
|
|
766
|
+
context.goalMode
|
|
767
|
+
? 'Write the next supervisor steer only when useful. Return empty if no intervention is needed.'
|
|
768
|
+
: 'Write the next user input only.',
|
|
769
|
+
].filter((line) => line !== null).join('\n');
|
|
770
|
+
}
|
|
771
|
+
function renderSecretaryAutoInputRetryContext(inputText) {
|
|
772
|
+
return [
|
|
773
|
+
SECRETARY_RETRY_PREFIX,
|
|
774
|
+
'',
|
|
775
|
+
inputText,
|
|
776
|
+
].join('\n');
|
|
777
|
+
}
|
|
778
|
+
function resolveRecentMessages(interactive) {
|
|
779
|
+
const fromEntries = resolveRecentMessagesFromEntries(interactive?.sessionManager?.getEntries?.()
|
|
780
|
+
?? interactive?.session?.sessionManager?.getEntries?.());
|
|
781
|
+
if (fromEntries.length > 0) {
|
|
782
|
+
return fromEntries;
|
|
783
|
+
}
|
|
784
|
+
return resolveRecentMessagesFromAgentState(interactive?.session?.agent?.state?.messages);
|
|
785
|
+
}
|
|
786
|
+
function resolveRecentMessagesFromEntries(entries) {
|
|
787
|
+
if (!Array.isArray(entries)) {
|
|
788
|
+
return [];
|
|
789
|
+
}
|
|
790
|
+
return entries
|
|
791
|
+
.filter((entry) => isRecord(entry) && entry.type === 'message')
|
|
792
|
+
.map((entry) => normalizeMessage(entry.message))
|
|
793
|
+
.filter((message) => message !== null)
|
|
794
|
+
.slice(-MAX_CONTEXT_MESSAGES);
|
|
795
|
+
}
|
|
796
|
+
function resolveRecentMessagesFromAgentState(messages) {
|
|
797
|
+
if (!Array.isArray(messages)) {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
return messages
|
|
801
|
+
.map((message) => normalizeMessage(message))
|
|
802
|
+
.filter((message) => message !== null)
|
|
803
|
+
.slice(-MAX_CONTEXT_MESSAGES);
|
|
804
|
+
}
|
|
805
|
+
function normalizeMessage(message) {
|
|
806
|
+
if (!isRecord(message)) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
const role = normalizeString(message.role);
|
|
810
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
const text = clipText(extractTextContent(message.content), MAX_CONTEXT_CHARS);
|
|
814
|
+
return text ? { role, text } : null;
|
|
815
|
+
}
|
|
816
|
+
function extractTextContent(content) {
|
|
817
|
+
if (typeof content === 'string') {
|
|
818
|
+
return content;
|
|
819
|
+
}
|
|
820
|
+
if (!Array.isArray(content)) {
|
|
821
|
+
return '';
|
|
822
|
+
}
|
|
823
|
+
return content
|
|
824
|
+
.map((part) => isRecord(part) && part.type === 'text' && typeof part.text === 'string' ? part.text : '')
|
|
825
|
+
.filter(Boolean)
|
|
826
|
+
.join('\n');
|
|
827
|
+
}
|
|
828
|
+
async function deliverAsUserInput(session, text) {
|
|
829
|
+
if (typeof session?.sendUserMessage === 'function') {
|
|
830
|
+
await session.sendUserMessage(text, session.isStreaming ? { deliverAs: 'followUp' } : undefined);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (typeof session?.prompt === 'function') {
|
|
834
|
+
await session.prompt(text, session.isStreaming ? { streamingBehavior: 'followUp' } : undefined);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
throw new Error('Active LinX session cannot accept user input projection');
|
|
838
|
+
}
|
|
839
|
+
async function deliverProjectedInput(interactive, session, text) {
|
|
840
|
+
if (text.trim().startsWith('/') && typeof interactive?.__linxHandleProjectedCommand === 'function') {
|
|
841
|
+
const handled = await interactive.__linxHandleProjectedCommand(text);
|
|
842
|
+
if (handled === 'peer-command') {
|
|
843
|
+
return 'peer-command';
|
|
844
|
+
}
|
|
845
|
+
if (handled === true) {
|
|
846
|
+
return 'control-command';
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
await deliverAsUserInput(session, text);
|
|
850
|
+
return 'backend';
|
|
851
|
+
}
|
|
852
|
+
function projectedDeliveryTargetRole(delivery) {
|
|
853
|
+
if (delivery === 'backend') {
|
|
854
|
+
return 'user';
|
|
855
|
+
}
|
|
856
|
+
return delivery;
|
|
857
|
+
}
|
|
858
|
+
function normalizeGeneratedInput(value) {
|
|
859
|
+
if (typeof value !== 'string') {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const text = clipText(stripWrappingFence(value.trim()), MAX_GENERATED_INPUT_CHARS);
|
|
863
|
+
return text.length > 0 ? text : null;
|
|
864
|
+
}
|
|
865
|
+
class SecretaryAutoInputBlockedError extends Error {
|
|
866
|
+
constructor(message) {
|
|
867
|
+
super(message);
|
|
868
|
+
this.name = 'SecretaryAutoInputBlockedError';
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function isRecoverableAutoInputFailure(message, error) {
|
|
872
|
+
if (error instanceof SecretaryAutoInputBlockedError || isRemoteAuthExpiredError(error)) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
const normalized = message.toLowerCase();
|
|
876
|
+
if (normalized.includes('login is required')
|
|
877
|
+
|| normalized.includes('login expired')
|
|
878
|
+
|| normalized.includes('credential')
|
|
879
|
+
|| normalized.includes('unauthorized')
|
|
880
|
+
|| normalized.includes('permission')
|
|
881
|
+
|| normalized.includes('missing authority')) {
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
function isTransientRemoteAutoInputFailure(message, error) {
|
|
887
|
+
const status = resolveTransientRemoteFailureStatus(error);
|
|
888
|
+
if (status !== null) {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
const normalized = message.toLowerCase();
|
|
892
|
+
return normalized.includes('linx cloud is temporarily unavailable')
|
|
893
|
+
|| (normalized.includes('502') && normalized.includes('bad gateway'))
|
|
894
|
+
|| (normalized.includes('503') && normalized.includes('service unavailable'))
|
|
895
|
+
|| (normalized.includes('504') && normalized.includes('gateway timeout'));
|
|
896
|
+
}
|
|
897
|
+
function resolveTransientRemoteFailureStatus(error) {
|
|
898
|
+
if (typeof error === 'object' && error !== null) {
|
|
899
|
+
const candidates = [
|
|
900
|
+
error.status,
|
|
901
|
+
error.response?.status,
|
|
902
|
+
error.cause?.status,
|
|
903
|
+
];
|
|
904
|
+
for (const candidate of candidates) {
|
|
905
|
+
if (candidate === 502 || candidate === 503 || candidate === 504) {
|
|
906
|
+
return candidate;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
function resolveAutoInputRecoveryDelaysMs() {
|
|
913
|
+
return parsePositiveIntegerListEnv('LINX_AUTO_INPUT_RECOVERY_DELAYS_MS')
|
|
914
|
+
?? [...DEFAULT_AUTO_INPUT_RECOVERY_DELAYS_MS];
|
|
915
|
+
}
|
|
916
|
+
function resolveTransientRemoteRecoveryDelayMs() {
|
|
917
|
+
return parsePositiveIntegerEnv('LINX_AUTO_INPUT_TRANSIENT_RECOVERY_DELAY_MS')
|
|
918
|
+
?? DEFAULT_TRANSIENT_REMOTE_RECOVERY_DELAY_MS;
|
|
919
|
+
}
|
|
920
|
+
function parsePositiveIntegerListEnv(name) {
|
|
921
|
+
const raw = process.env[name];
|
|
922
|
+
if (!raw) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const values = raw.split(',').map((part) => Number(part.trim()));
|
|
926
|
+
if (values.length === 0 || values.some((value) => !Number.isFinite(value) || value <= 0)) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
return values.map((value) => Math.trunc(value));
|
|
930
|
+
}
|
|
931
|
+
function parsePositiveIntegerEnv(name) {
|
|
932
|
+
const value = Number(process.env[name]);
|
|
933
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : null;
|
|
934
|
+
}
|
|
935
|
+
function formatDelay(delayMs) {
|
|
936
|
+
if (delayMs < 1_000) {
|
|
937
|
+
return `${delayMs}ms`;
|
|
938
|
+
}
|
|
939
|
+
const seconds = delayMs / 1_000;
|
|
940
|
+
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
|
|
941
|
+
}
|
|
942
|
+
function resolveAssistantSignature(context) {
|
|
943
|
+
const latest = context.recentMessages.at(-1);
|
|
944
|
+
return latest?.role === 'assistant' ? createAssistantSignature(latest.text) : null;
|
|
945
|
+
}
|
|
946
|
+
function createAssistantSignature(text) {
|
|
947
|
+
const normalized = text.trim();
|
|
948
|
+
return normalized ? normalized.slice(-512) : null;
|
|
949
|
+
}
|
|
950
|
+
function stripWrappingFence(value) {
|
|
951
|
+
const match = value.match(/^```(?:text|markdown|md)?\s*\n([\s\S]*?)\n```$/i);
|
|
952
|
+
return match?.[1]?.trim() ?? value;
|
|
953
|
+
}
|
|
954
|
+
function clipText(value, maxChars) {
|
|
955
|
+
return value.length > maxChars ? value.slice(0, maxChars) : value;
|
|
956
|
+
}
|
|
957
|
+
function normalizeString(value) {
|
|
958
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
959
|
+
}
|
|
960
|
+
function isRecord(value) {
|
|
961
|
+
return typeof value === 'object' && value !== null;
|
|
962
|
+
}
|
|
963
|
+
function summarizeRuntimeRun(run) {
|
|
964
|
+
return {
|
|
965
|
+
id: run.id,
|
|
966
|
+
agent: run.agent,
|
|
967
|
+
role: run.role,
|
|
968
|
+
model: run.model,
|
|
969
|
+
trigger: run.trigger,
|
|
970
|
+
status: run.status,
|
|
971
|
+
startedAt: run.startedAt,
|
|
972
|
+
completedAt: run.completedAt,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function summarizeRuntimeSteps(steps) {
|
|
976
|
+
return steps.map((step) => ({
|
|
977
|
+
id: step.id,
|
|
978
|
+
stepType: step.stepType,
|
|
979
|
+
message: step.message,
|
|
980
|
+
createdAt: step.createdAt,
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
983
|
+
function isAgentRuntimeTurnError(error) {
|
|
984
|
+
return isRecord(error)
|
|
985
|
+
&& isRecord(error.run)
|
|
986
|
+
&& Array.isArray(error.steps);
|
|
987
|
+
}
|
|
988
|
+
//# sourceMappingURL=auto-input-controller.js.map
|