mixdog 0.9.83 → 0.9.85
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/package.json +1 -1
- package/scripts/channel-daemon-stub.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +30 -3
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +38 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +48 -32
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +25 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +38 -1
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +44 -73
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -8
- package/src/runtime/channels/lib/parent-bridge.mjs +27 -15
- package/src/runtime/channels/lib/worker-ipc.mjs +17 -4
- package/src/runtime/memory/index.mjs +14 -5
- package/src/runtime/memory/lib/agent-ipc.mjs +11 -12
- package/src/runtime/shared/atomic-file.mjs +51 -24
- package/src/runtime/shared/err-text.mjs +1 -0
- package/src/runtime/shared/safe-ipc-send.mjs +34 -0
- package/src/session-runtime/lifecycle-api.mjs +9 -3
- package/src/session-runtime/runtime-core.mjs +5 -1
- package/src/standalone/channel-daemon.mjs +2 -1
- package/src/standalone/channel-worker.mjs +21 -19
- package/src/standalone/memory-runtime-proxy.mjs +98 -23
- package/src/tui/app/slash-dispatch.mjs +23 -13
- package/src/tui/app/theme-effort-pickers.mjs +3 -1
- package/src/tui/dist/index.mjs +115 -57
- package/src/tui/engine/live-share.mjs +2 -2
- package/src/tui/engine/session-flow.mjs +20 -10
- package/src/tui/engine/turn.mjs +28 -16
- package/src/tui/engine.mjs +17 -1
|
@@ -103,10 +103,52 @@ function isMemoryReadOnlyToolCall(name, args = {}) {
|
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function
|
|
107
|
-
|
|
106
|
+
function memoryAbortError(reason) {
|
|
107
|
+
if (reason instanceof Error) return reason;
|
|
108
|
+
const error = new Error(typeof reason === 'string' && reason ? reason : 'memory proxy request aborted');
|
|
109
|
+
error.name = 'AbortError';
|
|
110
|
+
error.code = 'ABORT_ERR';
|
|
111
|
+
return error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function requestJson({
|
|
115
|
+
port,
|
|
116
|
+
method = 'GET',
|
|
117
|
+
path = '/',
|
|
118
|
+
body = null,
|
|
119
|
+
timeoutMs = 10_000,
|
|
120
|
+
headers = {},
|
|
121
|
+
signal = null,
|
|
122
|
+
}) {
|
|
123
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
124
|
+
let settled = false;
|
|
125
|
+
let req = null;
|
|
126
|
+
const cleanup = () => {
|
|
127
|
+
try { signal?.removeEventListener?.('abort', onAbort); } catch {}
|
|
128
|
+
};
|
|
129
|
+
const resolve = (value) => {
|
|
130
|
+
if (settled) return;
|
|
131
|
+
settled = true;
|
|
132
|
+
cleanup();
|
|
133
|
+
resolvePromise(value);
|
|
134
|
+
};
|
|
135
|
+
const reject = (error) => {
|
|
136
|
+
if (settled) return;
|
|
137
|
+
settled = true;
|
|
138
|
+
cleanup();
|
|
139
|
+
rejectPromise(error);
|
|
140
|
+
};
|
|
141
|
+
const onAbort = () => {
|
|
142
|
+
const error = memoryAbortError(signal?.reason);
|
|
143
|
+
if (req) req.destroy(error);
|
|
144
|
+
else reject(error);
|
|
145
|
+
};
|
|
146
|
+
if (signal?.aborted) {
|
|
147
|
+
reject(memoryAbortError(signal.reason));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
108
150
|
const payload = body == null ? null : JSON.stringify(body);
|
|
109
|
-
|
|
151
|
+
req = http.request({
|
|
110
152
|
hostname: '127.0.0.1',
|
|
111
153
|
port,
|
|
112
154
|
path,
|
|
@@ -135,14 +177,19 @@ function requestJson({ port, method = 'GET', path = '/', body = null, timeoutMs
|
|
|
135
177
|
reject(error);
|
|
136
178
|
return;
|
|
137
179
|
}
|
|
138
|
-
|
|
180
|
+
resolve(parsed ?? { raw: data });
|
|
139
181
|
});
|
|
182
|
+
res.on('error', reject);
|
|
140
183
|
});
|
|
141
184
|
req.on('error', reject);
|
|
142
185
|
req.on('timeout', () => {
|
|
143
|
-
req.destroy();
|
|
144
|
-
reject(new Error(`memory proxy request timed out: ${method} ${path}`));
|
|
186
|
+
req.destroy(new Error(`memory proxy request timed out: ${method} ${path}`));
|
|
145
187
|
});
|
|
188
|
+
try { signal?.addEventListener?.('abort', onAbort, { once: true }); } catch {}
|
|
189
|
+
if (signal?.aborted) {
|
|
190
|
+
onAbort();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
146
193
|
if (payload) req.write(payload);
|
|
147
194
|
req.end();
|
|
148
195
|
});
|
|
@@ -496,26 +543,54 @@ export function createStandaloneMemoryRuntime({
|
|
|
496
543
|
return { running: true, port, mode: 'http-proxy' };
|
|
497
544
|
}
|
|
498
545
|
|
|
499
|
-
async function handleToolCall(name, args = {}) {
|
|
546
|
+
async function handleToolCall(name, args = {}, signalOrOptions = null) {
|
|
547
|
+
const signal = signalOrOptions?.signal || signalOrOptions || null;
|
|
500
548
|
const readOnlyRpc = isMemoryReadOnlyToolCall(name, args);
|
|
501
549
|
const callId = `mem_${process.pid}_${nextCallId++}`;
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
port,
|
|
550
|
+
let activePort = null;
|
|
551
|
+
let rpcStarted = false;
|
|
552
|
+
const throwIfAborted = () => {
|
|
553
|
+
if (signal?.aborted) throw memoryAbortError(signal.reason);
|
|
554
|
+
};
|
|
555
|
+
const cancelRemote = () => {
|
|
556
|
+
if (!activePort || !rpcStarted) return;
|
|
557
|
+
void requestJson({
|
|
558
|
+
port: activePort,
|
|
512
559
|
method: 'POST',
|
|
513
|
-
path: '/api/
|
|
514
|
-
body: {
|
|
515
|
-
timeoutMs:
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
560
|
+
path: '/api/cancel',
|
|
561
|
+
body: { callId },
|
|
562
|
+
timeoutMs: 1500,
|
|
563
|
+
}).catch(() => {});
|
|
564
|
+
};
|
|
565
|
+
throwIfAborted();
|
|
566
|
+
try { signal?.addEventListener?.('abort', cancelRemote, { once: true }); } catch {}
|
|
567
|
+
try {
|
|
568
|
+
return await withTransientMemoryRpcRetry(async () => {
|
|
569
|
+
throwIfAborted();
|
|
570
|
+
await start();
|
|
571
|
+
throwIfAborted();
|
|
572
|
+
let port = portCache || await findLivePort({ allowStarting: true });
|
|
573
|
+
if (!port) throw new Error('memory runtime is not available');
|
|
574
|
+
// ensureClientRegistered may respawn onto a fresh daemon/port; target the
|
|
575
|
+
// port it hands back so the RPC and registration always hit the same one.
|
|
576
|
+
port = await ensureClientRegistered(port);
|
|
577
|
+
if (!port) throw new Error('memory runtime is not available');
|
|
578
|
+
activePort = port;
|
|
579
|
+
throwIfAborted();
|
|
580
|
+
rpcStarted = true;
|
|
581
|
+
return await requestJson({
|
|
582
|
+
port,
|
|
583
|
+
method: 'POST',
|
|
584
|
+
path: '/api/tool',
|
|
585
|
+
body: { name, arguments: args || {} },
|
|
586
|
+
timeoutMs: Math.max(1000, Number(process.env.MIXDOG_MEMORY_TOOL_TIMEOUT_MS) || 180_000),
|
|
587
|
+
headers: { 'X-Mixdog-Call-Id': callId },
|
|
588
|
+
signal,
|
|
589
|
+
});
|
|
590
|
+
}, { readOnlyRpc });
|
|
591
|
+
} finally {
|
|
592
|
+
try { signal?.removeEventListener?.('abort', cancelRemote); } catch {}
|
|
593
|
+
}
|
|
519
594
|
}
|
|
520
595
|
|
|
521
596
|
async function buildSessionCoreMemoryPayload(sessionCwd) {
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* defined later in the App factory zone are passed as lazy getters so their
|
|
10
10
|
* live binding is used at call time.
|
|
11
11
|
*/
|
|
12
|
+
import { presentErrorText } from '../../runtime/shared/err-text.mjs';
|
|
13
|
+
|
|
14
|
+
export function compactFailureNotice(error) {
|
|
15
|
+
if (error == null || String(error).trim() === '') return 'Compact failed.';
|
|
16
|
+
const reason = presentErrorText(error, { surface: 'compact', max: 320 });
|
|
17
|
+
if (!reason || reason === 'Unknown error') return 'Compact failed.';
|
|
18
|
+
return /^compact(?:ion)? failed\b/i.test(reason) ? reason : `Compact failed: ${reason}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
export function createSlashDispatch({
|
|
13
22
|
state,
|
|
14
23
|
store,
|
|
@@ -179,24 +188,21 @@ export function createSlashDispatch({
|
|
|
179
188
|
}
|
|
180
189
|
return true;
|
|
181
190
|
}
|
|
182
|
-
case 'effort':
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
case 'effort': {
|
|
192
|
+
// A running turn no longer blocks the switch: the in-flight turn keeps
|
|
193
|
+
// the effort it started with, and the new level applies from the next
|
|
194
|
+
// turn. Same for /fast below.
|
|
195
|
+
const pendingTurn = state.busy ? ' (applies from the next turn)' : '';
|
|
187
196
|
if (!arg) {
|
|
188
197
|
openEffortPicker();
|
|
189
198
|
return true;
|
|
190
199
|
}
|
|
191
200
|
void store.setEffort(arg)
|
|
192
|
-
.then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
|
|
201
|
+
.then(result => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
|
|
193
202
|
.catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
|
|
194
203
|
return true;
|
|
204
|
+
}
|
|
195
205
|
case 'fast': {
|
|
196
|
-
if (state.busy) {
|
|
197
|
-
store.pushNotice('wait for the current turn to finish before /fast', 'warn');
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
206
|
const value = String(arg || '').trim().toLowerCase();
|
|
201
207
|
const setTo = value
|
|
202
208
|
? ['1', 'true', 'yes', 'on', 'enable', 'enabled'].includes(value)
|
|
@@ -216,7 +222,11 @@ export function createSlashDispatch({
|
|
|
216
222
|
store.pushNotice('Fast mode switch is already running.', 'warn');
|
|
217
223
|
return;
|
|
218
224
|
}
|
|
219
|
-
store.pushNotice(
|
|
225
|
+
store.pushNotice(
|
|
226
|
+
`Fast mode ${enabled ? 'on' : 'off'} for ${state.provider}/${state.model}`
|
|
227
|
+
+ (state.busy ? ' (applies from the next turn)' : ''),
|
|
228
|
+
'info',
|
|
229
|
+
);
|
|
220
230
|
})
|
|
221
231
|
.catch((e) => store.pushNotice(`Couldn’t update fast mode: ${e?.message || e}`, 'error'));
|
|
222
232
|
return true;
|
|
@@ -345,7 +355,7 @@ export function createSlashDispatch({
|
|
|
345
355
|
return;
|
|
346
356
|
}
|
|
347
357
|
if (r.error) {
|
|
348
|
-
store.pushNotice(
|
|
358
|
+
store.pushNotice(compactFailureNotice(r.error), 'error');
|
|
349
359
|
return;
|
|
350
360
|
}
|
|
351
361
|
if (r.changed === false && r.reason) {
|
|
@@ -358,7 +368,7 @@ export function createSlashDispatch({
|
|
|
358
368
|
}
|
|
359
369
|
store.pushNotice('Compact done.', 'info');
|
|
360
370
|
})
|
|
361
|
-
.catch(() => store.pushNotice(
|
|
371
|
+
.catch((error) => store.pushNotice(compactFailureNotice(error), 'error'));
|
|
362
372
|
return true;
|
|
363
373
|
case 'resume':
|
|
364
374
|
if (state.busy) {
|
|
@@ -140,8 +140,10 @@ export function createThemeEffortPickers({
|
|
|
140
140
|
items: pickerItems,
|
|
141
141
|
onSelect: (value) => {
|
|
142
142
|
setPicker(null);
|
|
143
|
+
// Mid-turn picks are allowed; the running turn keeps its own effort.
|
|
144
|
+
const pendingTurn = state.busy ? ' (applies from the next turn)' : '';
|
|
143
145
|
void store.setEffort(value)
|
|
144
|
-
.then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
|
|
146
|
+
.then(result => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
|
|
145
147
|
.catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
|
|
146
148
|
},
|
|
147
149
|
onCancel: () => {
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -454,6 +454,15 @@ import {
|
|
|
454
454
|
unlinkSync as unlinkSync2,
|
|
455
455
|
writeFileSync as writeFileSync3
|
|
456
456
|
} from "fs";
|
|
457
|
+
import {
|
|
458
|
+
link as linkAsync,
|
|
459
|
+
mkdir as mkdirAsync,
|
|
460
|
+
open as openAsync,
|
|
461
|
+
readFile as readFileAsync2,
|
|
462
|
+
rename as renameAsync,
|
|
463
|
+
unlink as unlinkAsync,
|
|
464
|
+
writeFile as writeFileAsync
|
|
465
|
+
} from "node:fs/promises";
|
|
457
466
|
import { dirname as dirname4, basename as basename4, join as join4 } from "path";
|
|
458
467
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
459
468
|
import { execFile as execFile2, execFileSync } from "child_process";
|
|
@@ -496,6 +505,22 @@ function renameWithRetrySync(src, dst, opts = {}) {
|
|
|
496
505
|
}
|
|
497
506
|
throw lastErr;
|
|
498
507
|
}
|
|
508
|
+
async function renameWithRetry(src, dst, opts = {}) {
|
|
509
|
+
const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0 ? opts.backoffs : DEFAULT_BACKOFFS_MS;
|
|
510
|
+
let lastErr = null;
|
|
511
|
+
for (let attempt = 0; attempt <= backoffs.length; attempt++) {
|
|
512
|
+
try {
|
|
513
|
+
await renameAsync(src, dst);
|
|
514
|
+
return true;
|
|
515
|
+
} catch (err) {
|
|
516
|
+
lastErr = err;
|
|
517
|
+
if (!RETRY_CODES.has(err?.code) || attempt >= backoffs.length) break;
|
|
518
|
+
const jitter = Math.floor(Math.random() * Math.min(50, Math.max(1, backoffs[attempt])));
|
|
519
|
+
await _asyncSleep(backoffs[attempt] + jitter);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
throw lastErr;
|
|
523
|
+
}
|
|
499
524
|
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
500
525
|
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DEFAULT_LOCK_TIMEOUT_MS;
|
|
501
526
|
const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 3e4;
|
|
@@ -893,55 +918,55 @@ function writeJsonAtomicSync(filePath, value, opts = {}) {
|
|
|
893
918
|
async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
894
919
|
const run = async () => {
|
|
895
920
|
const dir = dirname4(filePath);
|
|
896
|
-
|
|
921
|
+
await mkdirAsync(dir, { recursive: true });
|
|
897
922
|
const tmp = join4(dir, `.${basename4(filePath)}.${randomBytes2(12).toString("hex")}.tmp`);
|
|
898
923
|
try {
|
|
899
924
|
const writeOpts = { encoding: opts.encoding || "utf8", flag: "wx", mode: opts.mode !== void 0 ? opts.mode : 384 };
|
|
900
|
-
|
|
925
|
+
await writeFileAsync(tmp, data, writeOpts);
|
|
901
926
|
if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(tmp);
|
|
902
927
|
if (opts.fsync !== false) {
|
|
903
928
|
let fd = null;
|
|
904
929
|
try {
|
|
905
|
-
fd =
|
|
906
|
-
|
|
930
|
+
fd = await openAsync(tmp, "r");
|
|
931
|
+
await fd.sync();
|
|
907
932
|
} catch (err) {
|
|
908
933
|
if (!["EPERM", "ENOTSUP", "EINVAL"].includes(err?.code)) throw err;
|
|
909
934
|
} finally {
|
|
910
935
|
try {
|
|
911
|
-
if (fd !== null)
|
|
936
|
+
if (fd !== null) await fd.close();
|
|
912
937
|
} catch {
|
|
913
938
|
}
|
|
914
939
|
}
|
|
915
940
|
}
|
|
916
941
|
if (opts.createOnly === true) {
|
|
917
942
|
try {
|
|
918
|
-
|
|
943
|
+
await linkAsync(tmp, filePath);
|
|
919
944
|
} catch (err) {
|
|
920
945
|
try {
|
|
921
|
-
|
|
946
|
+
await unlinkAsync(tmp);
|
|
922
947
|
} catch {
|
|
923
948
|
}
|
|
924
949
|
if (err?.code === "EEXIST") return false;
|
|
925
950
|
throw err;
|
|
926
951
|
}
|
|
927
952
|
try {
|
|
928
|
-
|
|
953
|
+
await unlinkAsync(tmp);
|
|
929
954
|
} catch {
|
|
930
955
|
}
|
|
931
956
|
} else {
|
|
932
|
-
|
|
957
|
+
await renameWithRetry(tmp, filePath, opts);
|
|
933
958
|
}
|
|
934
959
|
if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(filePath);
|
|
935
960
|
if (opts.fsyncDir === true) {
|
|
936
961
|
let dfd = null;
|
|
937
962
|
try {
|
|
938
|
-
dfd =
|
|
939
|
-
|
|
963
|
+
dfd = await openAsync(dir, "r");
|
|
964
|
+
await dfd.sync();
|
|
940
965
|
} catch (err) {
|
|
941
966
|
if (!["EPERM", "ENOTSUP", "EINVAL", "EACCES"].includes(err?.code)) throw err;
|
|
942
967
|
} finally {
|
|
943
968
|
try {
|
|
944
|
-
if (dfd !== null)
|
|
969
|
+
if (dfd !== null) await dfd.close();
|
|
945
970
|
} catch {
|
|
946
971
|
}
|
|
947
972
|
}
|
|
@@ -949,7 +974,7 @@ async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
|
949
974
|
return true;
|
|
950
975
|
} catch (err) {
|
|
951
976
|
try {
|
|
952
|
-
|
|
977
|
+
await unlinkAsync(tmp);
|
|
953
978
|
} catch {
|
|
954
979
|
}
|
|
955
980
|
throw err;
|
|
@@ -965,16 +990,16 @@ function writeJsonAtomicAsync(filePath, value, opts = {}) {
|
|
|
965
990
|
}
|
|
966
991
|
async function updateJsonAtomic(filePath, mutator, opts = {}) {
|
|
967
992
|
const { lock: _lock, ...writeOpts } = opts;
|
|
968
|
-
return withFileLock(`${filePath}.lock`, () => {
|
|
993
|
+
return withFileLock(`${filePath}.lock`, async () => {
|
|
969
994
|
let cur = null;
|
|
970
995
|
try {
|
|
971
|
-
cur = JSON.parse(
|
|
996
|
+
cur = JSON.parse(await readFileAsync2(filePath, "utf8"));
|
|
972
997
|
} catch {
|
|
973
998
|
cur = null;
|
|
974
999
|
}
|
|
975
1000
|
const next = mutator(cur);
|
|
976
1001
|
if (next === void 0) return cur;
|
|
977
|
-
|
|
1002
|
+
await writeJsonAtomicAsync(filePath, next, { ...writeOpts, lock: false });
|
|
978
1003
|
return next;
|
|
979
1004
|
}, opts);
|
|
980
1005
|
}
|
|
@@ -11989,6 +12014,7 @@ function presentErrorText(error, options = {}) {
|
|
|
11989
12014
|
return "Context too large.";
|
|
11990
12015
|
}
|
|
11991
12016
|
if (/\bcompact(?:ion)?\b.*\b(?:failed|error|overflow)\b|\b(?:failed|error)\b.*\bcompact(?:ion)?\b/i.test(text)) {
|
|
12017
|
+
if (String(surface).toLowerCase().includes("compact")) return capText(text, max);
|
|
11992
12018
|
return "Compact failed.";
|
|
11993
12019
|
}
|
|
11994
12020
|
const quotaRetry = /retryAfter=([^\s:]+)/i.exec(text);
|
|
@@ -15657,7 +15683,8 @@ function createThemeEffortPickers({
|
|
|
15657
15683
|
items: pickerItems,
|
|
15658
15684
|
onSelect: (value) => {
|
|
15659
15685
|
setPicker(null);
|
|
15660
|
-
|
|
15686
|
+
const pendingTurn = state.busy ? " (applies from the next turn)" : "";
|
|
15687
|
+
void store.setEffort(value).then((result) => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : "Effort switch is already running.", result ? "info" : "warn")).catch((e) => store.pushNotice(`Couldn\u2019t switch effort: ${e?.message || e}`, "error"));
|
|
15661
15688
|
},
|
|
15662
15689
|
onCancel: () => {
|
|
15663
15690
|
setPicker(null);
|
|
@@ -19206,6 +19233,12 @@ function createSettingsPicker({
|
|
|
19206
19233
|
}
|
|
19207
19234
|
|
|
19208
19235
|
// src/tui/app/slash-dispatch.mjs
|
|
19236
|
+
function compactFailureNotice(error) {
|
|
19237
|
+
if (error == null || String(error).trim() === "") return "Compact failed.";
|
|
19238
|
+
const reason = presentErrorText(error, { surface: "compact", max: 320 });
|
|
19239
|
+
if (!reason || reason === "Unknown error") return "Compact failed.";
|
|
19240
|
+
return /^compact(?:ion)? failed\b/i.test(reason) ? reason : `Compact failed: ${reason}`;
|
|
19241
|
+
}
|
|
19209
19242
|
function createSlashDispatch({
|
|
19210
19243
|
state,
|
|
19211
19244
|
store,
|
|
@@ -19365,22 +19398,16 @@ function createSlashDispatch({
|
|
|
19365
19398
|
}
|
|
19366
19399
|
return true;
|
|
19367
19400
|
}
|
|
19368
|
-
case "effort":
|
|
19369
|
-
|
|
19370
|
-
store.pushNotice("wait for the current turn to finish before /effort", "warn");
|
|
19371
|
-
return false;
|
|
19372
|
-
}
|
|
19401
|
+
case "effort": {
|
|
19402
|
+
const pendingTurn = state.busy ? " (applies from the next turn)" : "";
|
|
19373
19403
|
if (!arg) {
|
|
19374
19404
|
openEffortPicker();
|
|
19375
19405
|
return true;
|
|
19376
19406
|
}
|
|
19377
|
-
void store.setEffort(arg).then((result) => store.pushNotice(result ? `Effort set to ${result}` : "Effort switch is already running.", result ? "info" : "warn")).catch((e) => store.pushNotice(`Couldn\u2019t switch effort: ${e?.message || e}`, "error"));
|
|
19407
|
+
void store.setEffort(arg).then((result) => store.pushNotice(result ? `Effort set to ${result}${pendingTurn}` : "Effort switch is already running.", result ? "info" : "warn")).catch((e) => store.pushNotice(`Couldn\u2019t switch effort: ${e?.message || e}`, "error"));
|
|
19378
19408
|
return true;
|
|
19409
|
+
}
|
|
19379
19410
|
case "fast": {
|
|
19380
|
-
if (state.busy) {
|
|
19381
|
-
store.pushNotice("wait for the current turn to finish before /fast", "warn");
|
|
19382
|
-
return false;
|
|
19383
|
-
}
|
|
19384
19411
|
const value = String(arg || "").trim().toLowerCase();
|
|
19385
19412
|
const setTo = value ? ["1", "true", "yes", "on", "enable", "enabled"].includes(value) ? true : ["0", "false", "no", "off", "disable", "disabled"].includes(value) ? false : null : void 0;
|
|
19386
19413
|
if (setTo === null) {
|
|
@@ -19393,7 +19420,10 @@ function createSlashDispatch({
|
|
|
19393
19420
|
store.pushNotice("Fast mode switch is already running.", "warn");
|
|
19394
19421
|
return;
|
|
19395
19422
|
}
|
|
19396
|
-
store.pushNotice(
|
|
19423
|
+
store.pushNotice(
|
|
19424
|
+
`Fast mode ${enabled ? "on" : "off"} for ${state.provider}/${state.model}` + (state.busy ? " (applies from the next turn)" : ""),
|
|
19425
|
+
"info"
|
|
19426
|
+
);
|
|
19397
19427
|
}).catch((e) => store.pushNotice(`Couldn\u2019t update fast mode: ${e?.message || e}`, "error"));
|
|
19398
19428
|
return true;
|
|
19399
19429
|
}
|
|
@@ -19516,7 +19546,7 @@ function createSlashDispatch({
|
|
|
19516
19546
|
return;
|
|
19517
19547
|
}
|
|
19518
19548
|
if (r.error) {
|
|
19519
|
-
store.pushNotice(
|
|
19549
|
+
store.pushNotice(compactFailureNotice(r.error), "error");
|
|
19520
19550
|
return;
|
|
19521
19551
|
}
|
|
19522
19552
|
if (r.changed === false && r.reason) {
|
|
@@ -19528,7 +19558,7 @@ function createSlashDispatch({
|
|
|
19528
19558
|
return;
|
|
19529
19559
|
}
|
|
19530
19560
|
store.pushNotice("Compact done.", "info");
|
|
19531
|
-
}).catch(() => store.pushNotice(
|
|
19561
|
+
}).catch((error) => store.pushNotice(compactFailureNotice(error), "error"));
|
|
19532
19562
|
return true;
|
|
19533
19563
|
case "resume":
|
|
19534
19564
|
if (state.busy) {
|
|
@@ -26155,8 +26185,10 @@ function createSessionFlow(bag) {
|
|
|
26155
26185
|
const mode = options.mode || "prompt";
|
|
26156
26186
|
const priority = options.priority || defaultQueuePriority(mode);
|
|
26157
26187
|
const displayText = promptDisplayText(text, options);
|
|
26188
|
+
const submittedAt = Number(options.submittedAt);
|
|
26158
26189
|
return {
|
|
26159
26190
|
id: options.id || nextId2(),
|
|
26191
|
+
submittedAt: Number.isFinite(submittedAt) && submittedAt > 0 ? Math.round(submittedAt) : Date.now(),
|
|
26160
26192
|
text: displayText,
|
|
26161
26193
|
content: text,
|
|
26162
26194
|
pastedImages: options.pastedImages && typeof options.pastedImages === "object" ? options.pastedImages : null,
|
|
@@ -26350,6 +26382,8 @@ function createSessionFlow(bag) {
|
|
|
26350
26382
|
set({ queued: [...getState().queued, entry] });
|
|
26351
26383
|
if (isQueuedEntryEditable(entry)) flushEmitImmediate?.();
|
|
26352
26384
|
}
|
|
26385
|
+
const queueAgeMs = Math.max(0, Date.now() - Number(entry.submittedAt || Date.now()));
|
|
26386
|
+
tuiDebug2(`prompt-queued id=${String(entry.id)} busy=${getState().busy ? 1 : 0} ageMs=${queueAgeMs}`);
|
|
26353
26387
|
if (getState().busy) tuiDebug2(`busy-queue enqueue mode=${entry.mode} pending=${pending.length}`);
|
|
26354
26388
|
void drain();
|
|
26355
26389
|
return true;
|
|
@@ -26368,19 +26402,25 @@ function createSessionFlow(bag) {
|
|
|
26368
26402
|
if (batch.length === 0) break;
|
|
26369
26403
|
for (const entry of batch) {
|
|
26370
26404
|
const content = entry.content;
|
|
26405
|
+
const steeringMeta = {
|
|
26406
|
+
id: entry.id,
|
|
26407
|
+
submittedAt: entry.submittedAt,
|
|
26408
|
+
...Array.isArray(entry.images) && entry.images.length ? { images: entry.images } : {}
|
|
26409
|
+
};
|
|
26371
26410
|
if (entry.suppressDisplay) {
|
|
26372
26411
|
if (Array.isArray(content) ? content.length > 0 : String(content ?? "").trim().length > 0) {
|
|
26373
|
-
out.push({ text: STEERING_SUPPRESSED_DISPLAY, content });
|
|
26412
|
+
out.push({ ...steeringMeta, text: STEERING_SUPPRESSED_DISPLAY, content });
|
|
26374
26413
|
}
|
|
26375
26414
|
continue;
|
|
26376
26415
|
}
|
|
26377
|
-
const
|
|
26378
|
-
if (typeof
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
26416
|
+
const displayText = String(entry.text || "").trim();
|
|
26417
|
+
if (typeof content === "string") {
|
|
26418
|
+
const value = content.trim();
|
|
26419
|
+
if (value.length > 0) out.push({ ...steeringMeta, text: displayText || value, content: value });
|
|
26420
|
+
} else if (Array.isArray(content)) {
|
|
26421
|
+
if (content.length > 0) out.push({ ...steeringMeta, text: displayText, content });
|
|
26422
|
+
} else if (String(content ?? "").trim().length > 0) {
|
|
26423
|
+
out.push({ ...steeringMeta, text: displayText, content });
|
|
26384
26424
|
}
|
|
26385
26425
|
}
|
|
26386
26426
|
commitSteeringQueueEntries(batch);
|
|
@@ -26779,8 +26819,11 @@ function createRunTurn(bag) {
|
|
|
26779
26819
|
drain,
|
|
26780
26820
|
drainPendingSteering
|
|
26781
26821
|
} = bag;
|
|
26782
|
-
const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}) => {
|
|
26783
|
-
set({
|
|
26822
|
+
const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}, extra = {}) => {
|
|
26823
|
+
set({
|
|
26824
|
+
streamingTail: { ...getState().streamingTail || {}, ...patch, kind: "assistant", id, streaming: true },
|
|
26825
|
+
...extra
|
|
26826
|
+
});
|
|
26784
26827
|
return true;
|
|
26785
26828
|
});
|
|
26786
26829
|
const settleStreamingTail = settleStreamingTailFromStore || ((id, patch = {}) => {
|
|
@@ -27293,7 +27336,12 @@ function createRunTurn(bag) {
|
|
|
27293
27336
|
patch.spinner = { ...getState().spinner, responseLength: responseLengthVal, thinking: false, thinkingLastEndedAt: _pendingThinkingLastEndedAt || getState().spinner.thinkingLastEndedAt, mode: compactingActive ? "compacting" : "responding" };
|
|
27294
27337
|
_publishedThinkingActive = false;
|
|
27295
27338
|
}
|
|
27296
|
-
if (
|
|
27339
|
+
if (patch.streamingTail) {
|
|
27340
|
+
const { streamingTail, ...extra } = patch;
|
|
27341
|
+
updateStreamingTail(streamingTail.id, streamingTail, extra);
|
|
27342
|
+
} else if (Object.keys(patch).length > 0) {
|
|
27343
|
+
set(patch);
|
|
27344
|
+
}
|
|
27297
27345
|
_pendingThinkingLastEndedAt = 0;
|
|
27298
27346
|
}
|
|
27299
27347
|
if (_pendingThinkFlush) {
|
|
@@ -27384,7 +27432,7 @@ function createRunTurn(bag) {
|
|
|
27384
27432
|
onStreamDelta: () => {
|
|
27385
27433
|
markTurnProgress("stream-delta");
|
|
27386
27434
|
},
|
|
27387
|
-
onSteerMessage: (text) => {
|
|
27435
|
+
onSteerMessage: (text, steeringMeta = {}) => {
|
|
27388
27436
|
if (!markTurnProgress("steer-message")) return;
|
|
27389
27437
|
if (text === STEERING_SUPPRESSED_DISPLAY) return;
|
|
27390
27438
|
flushStreamBatch();
|
|
@@ -27393,7 +27441,13 @@ function createRunTurn(bag) {
|
|
|
27393
27441
|
const value = String(text || "").trim();
|
|
27394
27442
|
if (value) {
|
|
27395
27443
|
clearAggregateContinuation();
|
|
27396
|
-
|
|
27444
|
+
const steeringIds = Array.isArray(steeringMeta?.ids) ? steeringMeta.ids.filter((id) => id !== void 0 && id !== null) : [];
|
|
27445
|
+
pushUserOrSyntheticItem(
|
|
27446
|
+
value,
|
|
27447
|
+
steeringIds[0],
|
|
27448
|
+
"injected",
|
|
27449
|
+
Array.isArray(steeringMeta?.images) && steeringMeta.images.length ? { images: steeringMeta.images } : null
|
|
27450
|
+
);
|
|
27397
27451
|
}
|
|
27398
27452
|
},
|
|
27399
27453
|
onToolCall: async (_iter, calls) => {
|
|
@@ -27593,17 +27647,11 @@ function createRunTurn(bag) {
|
|
|
27593
27647
|
_emittedVisibleText = "";
|
|
27594
27648
|
if (currentAssistantId) {
|
|
27595
27649
|
if (currentAssistantText) {
|
|
27596
|
-
|
|
27597
|
-
|
|
27598
|
-
|
|
27599
|
-
|
|
27600
|
-
|
|
27601
|
-
text: currentAssistantText,
|
|
27602
|
-
streaming: true,
|
|
27603
|
-
at: getState().streamingTail?.at || Date.now(),
|
|
27604
|
-
...turnRouteMeta
|
|
27605
|
-
}
|
|
27606
|
-
});
|
|
27650
|
+
updateStreamingTail(currentAssistantId, {
|
|
27651
|
+
text: currentAssistantText,
|
|
27652
|
+
at: getState().streamingTail?.at || Date.now(),
|
|
27653
|
+
...turnRouteMeta
|
|
27654
|
+
}, {}, { resetText: true });
|
|
27607
27655
|
} else {
|
|
27608
27656
|
clearStreamingTail(currentAssistantId);
|
|
27609
27657
|
currentAssistantId = null;
|
|
@@ -29992,7 +30040,7 @@ function createLiveShare({
|
|
|
29992
30040
|
const applyViewerFrame = (frame, socket) => {
|
|
29993
30041
|
if (frame.t === "full") {
|
|
29994
30042
|
viewerApply.replaceItems(Array.isArray(frame.items) ? frame.items : []);
|
|
29995
|
-
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail);
|
|
30043
|
+
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail, {}, { resetText: true });
|
|
29996
30044
|
viewerApply.set({ spinner: frame.spinner || null });
|
|
29997
30045
|
applyLiveState(frame.live);
|
|
29998
30046
|
return;
|
|
@@ -30016,7 +30064,7 @@ function createLiveShare({
|
|
|
30016
30064
|
requestSync(socket);
|
|
30017
30065
|
}
|
|
30018
30066
|
} else if ("tail" in frame) {
|
|
30019
|
-
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail);
|
|
30067
|
+
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail, {}, { resetText: true });
|
|
30020
30068
|
else viewerApply.clearStreamingTail();
|
|
30021
30069
|
}
|
|
30022
30070
|
if ("spinner" in frame) viewerApply.set({ spinner: frame.spinner || null });
|
|
@@ -30937,10 +30985,20 @@ async function createEngineSession({
|
|
|
30937
30985
|
...extra
|
|
30938
30986
|
});
|
|
30939
30987
|
};
|
|
30940
|
-
const
|
|
30988
|
+
const streamingTailTextEpochKey = /* @__PURE__ */ Symbol.for("mixdog.streaming-tail-text-epoch");
|
|
30989
|
+
let streamingTailTextEpoch = 0;
|
|
30990
|
+
const updateStreamingTail = (id, patch = {}, extra = {}, { resetText = false } = {}) => {
|
|
30941
30991
|
if (id == null) return false;
|
|
30942
30992
|
const current = state.streamingTail?.id === id ? state.streamingTail : { kind: "assistant", id, text: "", streaming: true };
|
|
30943
30993
|
const next = { ...current, ...patch, kind: "assistant", id, streaming: true };
|
|
30994
|
+
const currentTextEpoch = current[streamingTailTextEpochKey];
|
|
30995
|
+
const textEpoch = !resetText && Number.isSafeInteger(currentTextEpoch) ? currentTextEpoch : ++streamingTailTextEpoch;
|
|
30996
|
+
Object.defineProperty(next, streamingTailTextEpochKey, {
|
|
30997
|
+
value: textEpoch,
|
|
30998
|
+
enumerable: false,
|
|
30999
|
+
configurable: false,
|
|
31000
|
+
writable: false
|
|
31001
|
+
});
|
|
30944
31002
|
let changed = state.streamingTail !== current;
|
|
30945
31003
|
if (!changed) {
|
|
30946
31004
|
for (const [key, value] of Object.entries(next)) {
|
|
@@ -446,7 +446,7 @@ export function createLiveShare({
|
|
|
446
446
|
const applyViewerFrame = (frame, socket) => {
|
|
447
447
|
if (frame.t === 'full') {
|
|
448
448
|
viewerApply.replaceItems(Array.isArray(frame.items) ? frame.items : []);
|
|
449
|
-
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail);
|
|
449
|
+
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail, {}, { resetText: true });
|
|
450
450
|
viewerApply.set({ spinner: frame.spinner || null });
|
|
451
451
|
applyLiveState(frame.live);
|
|
452
452
|
return;
|
|
@@ -472,7 +472,7 @@ export function createLiveShare({
|
|
|
472
472
|
requestSync(socket);
|
|
473
473
|
}
|
|
474
474
|
} else if ('tail' in frame) {
|
|
475
|
-
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail);
|
|
475
|
+
if (frame.tail) viewerApply.updateStreamingTail(frame.tail.id, frame.tail, {}, { resetText: true });
|
|
476
476
|
else viewerApply.clearStreamingTail();
|
|
477
477
|
}
|
|
478
478
|
if ('spinner' in frame) viewerApply.set({ spinner: frame.spinner || null });
|