@aexol/spectral 0.9.251 → 0.9.255
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/dist/extensions/dataset-uploader/analyzer.d.ts +103 -0
- package/dist/extensions/dataset-uploader/analyzer.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/analyzer.js +266 -0
- package/dist/extensions/dataset-uploader/cleaner.d.ts +59 -0
- package/dist/extensions/dataset-uploader/cleaner.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/cleaner.js +118 -0
- package/dist/extensions/dataset-uploader/exporter.d.ts +147 -0
- package/dist/extensions/dataset-uploader/exporter.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/exporter.js +313 -0
- package/dist/extensions/dataset-uploader/extra-values.d.ts +17 -0
- package/dist/extensions/dataset-uploader/extra-values.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/extra-values.js +51 -0
- package/dist/extensions/dataset-uploader/index.d.ts +22 -0
- package/dist/extensions/dataset-uploader/index.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/index.js +247 -0
- package/dist/extensions/dataset-uploader/scrubber.d.ts +34 -0
- package/dist/extensions/dataset-uploader/scrubber.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/scrubber.js +91 -0
- package/dist/extensions/dataset-uploader/upload.d.ts +105 -0
- package/dist/extensions/dataset-uploader/upload.d.ts.map +1 -0
- package/dist/extensions/dataset-uploader/upload.js +274 -0
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +80 -6
- package/dist/generated/zeus/index.d.ts +432 -36
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/mcp/commands.d.ts.map +1 -1
- package/dist/mcp/commands.js +3 -0
- package/dist/mcp/direct-tools.d.ts.map +1 -1
- package/dist/mcp/direct-tools.js +4 -3
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +5 -1
- package/dist/mcp/mcp-auth-flow.d.ts +5 -1
- package/dist/mcp/mcp-auth-flow.d.ts.map +1 -1
- package/dist/mcp/mcp-auth-flow.js +20 -4
- package/dist/mcp/mcp-auth-start.d.ts +64 -0
- package/dist/mcp/mcp-auth-start.d.ts.map +1 -0
- package/dist/mcp/mcp-auth-start.js +136 -0
- package/dist/mcp/proxy-modes.d.ts +7 -0
- package/dist/mcp/proxy-modes.d.ts.map +1 -1
- package/dist/mcp/proxy-modes.js +71 -11
- package/dist/mcp/server-manager.d.ts +2 -1
- package/dist/mcp/server-manager.d.ts.map +1 -1
- package/dist/mcp/server-manager.js +8 -4
- package/dist/memory/commands/status.d.ts.map +1 -1
- package/dist/memory/commands/status.js +2 -1
- package/dist/memory/hooks/compaction-trigger.d.ts +14 -1
- package/dist/memory/hooks/compaction-trigger.d.ts.map +1 -1
- package/dist/memory/hooks/compaction-trigger.js +31 -4
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +4 -47
- package/dist/relay/routing.d.ts +1 -1
- package/dist/relay/routing.d.ts.map +1 -1
- package/dist/relay/routing.js +9 -0
- package/dist/sdk/ai/utils/image-source.d.ts +26 -11
- package/dist/sdk/ai/utils/image-source.d.ts.map +1 -1
- package/dist/sdk/ai/utils/image-source.js +184 -47
- package/dist/sdk/ai/utils/transient-error.d.ts +4 -0
- package/dist/sdk/ai/utils/transient-error.d.ts.map +1 -1
- package/dist/sdk/ai/utils/transient-error.js +15 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts +2 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/policy.js +2 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +10 -0
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +4 -0
- package/dist/sdk/coding-agent/core/tools/read.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/read.js +14 -7
- package/dist/sdk/coding-agent/index.d.ts +1 -1
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/sdk/coding-agent/utils/image-resize.d.ts +25 -11
- package/dist/sdk/coding-agent/utils/image-resize.d.ts.map +1 -1
- package/dist/sdk/coding-agent/utils/image-resize.js +54 -36
- package/dist/sdk/coding-agent/utils/photon.d.ts.map +1 -1
- package/dist/sdk/coding-agent/utils/photon.js +11 -2
- package/dist/server/agent-bridge.d.ts +32 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +59 -9
- package/dist/server/handlers/mcp-auth.d.ts +28 -0
- package/dist/server/handlers/mcp-auth.d.ts.map +1 -0
- package/dist/server/handlers/mcp-auth.js +41 -0
- package/dist/server/handlers/mcp-status.d.ts +8 -2
- package/dist/server/handlers/mcp-status.d.ts.map +1 -1
- package/dist/server/handlers/mcp-status.js +15 -3
- package/dist/server/preset-chain.d.ts +0 -13
- package/dist/server/preset-chain.d.ts.map +1 -1
- package/dist/server/preset-chain.js +0 -25
- package/dist/server/session-stream/memory.d.ts.map +1 -1
- package/dist/server/session-stream/memory.js +8 -1
- package/dist/server/session-stream.d.ts +15 -56
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +41 -271
- package/dist/server/wire.d.ts +18 -32
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -40,11 +40,10 @@ import { randomUUID } from "node:crypto";
|
|
|
40
40
|
import { AgentBridge } from "./agent-bridge.js";
|
|
41
41
|
import { formatAttachmentAnnotation, persistAttachments, attachmentsRootFor, } from "./attachments/persist.js";
|
|
42
42
|
import { sweepAttachments } from "./attachments/cleanup.js";
|
|
43
|
-
import { expandIfPresetReference,
|
|
43
|
+
import { expandIfPresetReference, parsePresetReference, PRESET_CHAIN_BUDGET, resolvePresetByName, } from "./preset-chain.js";
|
|
44
44
|
import { InterAgentBroker } from "./inter-agent-broker.js";
|
|
45
45
|
import { setInterAgentBroker } from "../memory/inter-agent-broker-singleton.js";
|
|
46
46
|
import { configureFanout } from "./fanout/fanout-manager.js";
|
|
47
|
-
import { getAutoOlderHistoryContextThresholdTokens } from "../sdk/coding-agent/core/compaction/policy.js";
|
|
48
47
|
import { getMemoryState, rawTokensSinceLastBound, rawTokensSinceLastCompaction, } from "../memory/branch.js";
|
|
49
48
|
import { observationPoolTokens, renderSummary } from "../memory/compaction.js";
|
|
50
49
|
import { loadConfig } from "../memory/config.js";
|
|
@@ -58,7 +57,7 @@ import { parseImagesJson } from "./image-attachments.js";
|
|
|
58
57
|
// unchanged).
|
|
59
58
|
import { formatCompactionTokenStats, isSkippedCompactionResult, } from "./session-stream/compaction-events.js";
|
|
60
59
|
import { getCompactionEndPruneSkipReason, prunePersistedHistoryAfterCompaction, } from "./session-stream/history-prune.js";
|
|
61
|
-
import { applyDcpLiteRuntimeStatus, dcpLiteEventFromServerEvent, defaultMemoryStatus,
|
|
60
|
+
import { applyDcpLiteRuntimeStatus, dcpLiteEventFromServerEvent, defaultMemoryStatus, persistObservationalMemorySnapshot, } from "./session-stream/memory.js";
|
|
62
61
|
import { selectHistoryForBridge } from "./session-stream/replay-tail.js";
|
|
63
62
|
import { assistantMessageIdsForTurn, extractCreditsFromEventsJsonl, extractMessageMetricsFromEventsJsonl, isReplayable, snapshotTurn, } from "./session-stream/turn-events.js";
|
|
64
63
|
import { resolveTurnSilenceMaxRearm, resolveTurnSilenceTimeoutMs, } from "./session-stream/turn-silence.js";
|
|
@@ -69,14 +68,12 @@ export { estimateStoredMessageReplayTokens, selectHistoryForBridge, } from "./se
|
|
|
69
68
|
export { assistantMessageIdsForTurn, extractCreditsFromEventsJsonl, extractMessageMetricsFromEventsJsonl, isReplayable, snapshotTurn, } from "./session-stream/turn-events.js";
|
|
70
69
|
export { resolveTurnSilenceMaxRearm, resolveTurnSilenceTimeoutMs, } from "./session-stream/turn-silence.js";
|
|
71
70
|
const DEFAULT_BRIDGE_FACTORY = (args) => new AgentBridge(args);
|
|
72
|
-
/** Safety limit for autonomous loop iterations per session. */
|
|
73
|
-
const MAX_LOOP_ITERATIONS = 100;
|
|
74
71
|
// Streaming-meaningful events that reset the silence timer while a turn is
|
|
75
72
|
// active. Mirrors Fix C on the landing side, but EXCLUDES `token_usage`
|
|
76
73
|
// (arrives post-turn) and excludes terminal/control events handled by their
|
|
77
74
|
// own branches (`agent_end`, `error`) and non-streaming side channels
|
|
78
75
|
// (`inter_agent_observation`, `agent_notification`, `queue_changed`,
|
|
79
|
-
// `session_renamed`, `interval_*`, `
|
|
76
|
+
// `session_renamed`, `interval_*`, `compaction_*`).
|
|
80
77
|
const STREAMING_MEANINGFUL_EVENT_TYPES = new Set([
|
|
81
78
|
"message_start",
|
|
82
79
|
"text_delta",
|
|
@@ -107,8 +104,6 @@ const UI_HISTORY_TAIL_MESSAGE_LIMIT = 60;
|
|
|
107
104
|
* at most the last `BATCH_FLUSH_INTERVAL` events, not the entire turn.
|
|
108
105
|
*/
|
|
109
106
|
const BATCH_FLUSH_INTERVAL = 10;
|
|
110
|
-
/** Marker the agent emits in its response to signal the task is complete. */
|
|
111
|
-
const LOOP_DONE_MARKER = "<LOOP_DONE>";
|
|
112
107
|
export class SessionStreamManager {
|
|
113
108
|
store;
|
|
114
109
|
cwd;
|
|
@@ -302,8 +297,6 @@ export class SessionStreamManager {
|
|
|
302
297
|
intervalMinutes: stream.intervalMinutes,
|
|
303
298
|
intervalNextTickAt: stream.intervalNextTickAt,
|
|
304
299
|
intervalLaunchCount: stream.intervalLaunchCount,
|
|
305
|
-
intervalHasLoop: stream.intervalConfig?.loop ?? false,
|
|
306
|
-
intervalGoal: stream.intervalConfig?.loopGoal ?? null,
|
|
307
300
|
};
|
|
308
301
|
}
|
|
309
302
|
/**
|
|
@@ -348,7 +341,7 @@ export class SessionStreamManager {
|
|
|
348
341
|
}
|
|
349
342
|
/**
|
|
350
343
|
* Set of session IDs with active agent work (streaming, retrying,
|
|
351
|
-
* compacting,
|
|
344
|
+
* compacting, or a pending continuation). Superset of
|
|
352
345
|
* `getActiveTurnSessionIds`; used by REST list enrichment.
|
|
353
346
|
*/
|
|
354
347
|
getBusySessionIds() {
|
|
@@ -379,7 +372,7 @@ export class SessionStreamManager {
|
|
|
379
372
|
* session so enabling/disabling a native extension takes effect without
|
|
380
373
|
* restarting the session or waiting for a new session to be created.
|
|
381
374
|
*
|
|
382
|
-
* Busy sessions (in-flight turn, compaction,
|
|
375
|
+
* Busy sessions (in-flight turn, compaction, pending continuation,
|
|
383
376
|
* or a `prompt()` dispatch in progress) are SKIPPED, never queued: the
|
|
384
377
|
* reload swaps the tool registry / extension runner underneath the
|
|
385
378
|
* running turn, so a dispatched tool can resolve against a torn-down
|
|
@@ -452,7 +445,7 @@ export class SessionStreamManager {
|
|
|
452
445
|
/**
|
|
453
446
|
* True when an extension hot-reload must not touch this stream right now:
|
|
454
447
|
* a turn is in flight, a `prompt()` dispatch is in progress (R1), or the
|
|
455
|
-
* session is otherwise busy (compaction,
|
|
448
|
+
* session is otherwise busy (compaction, pending continuation).
|
|
456
449
|
*/
|
|
457
450
|
isReloadBlocked(stream) {
|
|
458
451
|
return (stream.currentTurn !== null ||
|
|
@@ -977,9 +970,11 @@ export class SessionStreamManager {
|
|
|
977
970
|
// emitted an `error` wire event and we drop the prompt rather than
|
|
978
971
|
// recording a user message we know we won't be able to respond to.
|
|
979
972
|
//
|
|
980
|
-
// Persistence:
|
|
981
|
-
//
|
|
982
|
-
//
|
|
973
|
+
// Persistence: the model actually applied by the bridge is written
|
|
974
|
+
// back — normally identical to `effectiveModelId`, but the applied
|
|
975
|
+
// (fallback) id wins when `setModel()` substituted one for a stale
|
|
976
|
+
// model. A recovery-only application (case b) rewrites the same value
|
|
977
|
+
// that is already there.
|
|
983
978
|
const effectiveModelId = modelId ??
|
|
984
979
|
this.store.getSessionModel(sessionId) ??
|
|
985
980
|
stream.bridge.getFirstAvailableModelId?.();
|
|
@@ -994,7 +989,11 @@ export class SessionStreamManager {
|
|
|
994
989
|
}
|
|
995
990
|
if (effectiveModelId) {
|
|
996
991
|
try {
|
|
997
|
-
|
|
992
|
+
// Persist the model actually in effect: when `setModel()` substituted
|
|
993
|
+
// a fallback for a stale requested modelId, the bridge reports the
|
|
994
|
+
// applied id and it wins over the requested one.
|
|
995
|
+
const appliedModelId = stream.bridge.getLastAppliedModelId?.() ?? effectiveModelId;
|
|
996
|
+
this.store.setSessionModel(sessionId, appliedModelId);
|
|
998
997
|
}
|
|
999
998
|
catch (err) {
|
|
1000
999
|
// Persisting the sticky model is best-effort: the live turn will
|
|
@@ -1099,7 +1098,7 @@ export class SessionStreamManager {
|
|
|
1099
1098
|
// that ended without a matching `subagent_end`.
|
|
1100
1099
|
this.resetSubagentTracking(stream);
|
|
1101
1100
|
// New logical run. This is the only place `busy` transitions
|
|
1102
|
-
// false -> true (
|
|
1101
|
+
// false -> true (auto-dequeue also goes through
|
|
1103
1102
|
// `prompt()`). Retries/continuations do NOT bump the generation.
|
|
1104
1103
|
stream.runState = {
|
|
1105
1104
|
runId: randomUUID(),
|
|
@@ -1110,7 +1109,6 @@ export class SessionStreamManager {
|
|
|
1110
1109
|
streaming: true,
|
|
1111
1110
|
retrying: false,
|
|
1112
1111
|
compacting: false,
|
|
1113
|
-
looping: false,
|
|
1114
1112
|
queued: false,
|
|
1115
1113
|
},
|
|
1116
1114
|
};
|
|
@@ -1142,7 +1140,6 @@ export class SessionStreamManager {
|
|
|
1142
1140
|
this.disposed = true;
|
|
1143
1141
|
this.unsubscribeDevProcessEvents?.();
|
|
1144
1142
|
for (const stream of this.streams.values()) {
|
|
1145
|
-
stream.loopActive = false;
|
|
1146
1143
|
stream.pendingContinue = false;
|
|
1147
1144
|
this.clearIntervalTimer(stream);
|
|
1148
1145
|
// Fix E: drop any in-flight safety-net timers on teardown.
|
|
@@ -1197,12 +1194,6 @@ export class SessionStreamManager {
|
|
|
1197
1194
|
const stream = this.streams.get(sessionId);
|
|
1198
1195
|
if (!stream)
|
|
1199
1196
|
return;
|
|
1200
|
-
// Stop any active loop before disposing — if the loop was mid-iteration
|
|
1201
|
-
// the next agent_end would otherwise trigger another prompt.
|
|
1202
|
-
stream.loopActive = false;
|
|
1203
|
-
stream.loopOriginalPrompt = null;
|
|
1204
|
-
stream.loopGoal = null;
|
|
1205
|
-
stream.loopIterationCount = 0;
|
|
1206
1197
|
this.clearIntervalTimer(stream);
|
|
1207
1198
|
stream.intervalMinutes = null;
|
|
1208
1199
|
stream.intervalConfig = null;
|
|
@@ -1229,8 +1220,7 @@ export class SessionStreamManager {
|
|
|
1229
1220
|
catch {
|
|
1230
1221
|
// ignore
|
|
1231
1222
|
}
|
|
1232
|
-
// Set startError so any late `await stream.ready`
|
|
1233
|
-
// (if the loop was between iterations when we disposed) rejects cleanly
|
|
1223
|
+
// Set startError so any late `await stream.ready` rejects cleanly
|
|
1234
1224
|
// instead of hanging. Also prevents the next prompt() from waiting on
|
|
1235
1225
|
// a dead bridge's ready promise.
|
|
1236
1226
|
stream.startError = new Error("Turn cancelled");
|
|
@@ -1303,7 +1293,6 @@ export class SessionStreamManager {
|
|
|
1303
1293
|
const stream = this.streams.get(sessionId);
|
|
1304
1294
|
if (!stream)
|
|
1305
1295
|
return;
|
|
1306
|
-
stream.loopActive = false;
|
|
1307
1296
|
stream.pendingContinue = false;
|
|
1308
1297
|
this.clearIntervalTimer(stream);
|
|
1309
1298
|
stream.intervalMinutes = null;
|
|
@@ -1346,82 +1335,6 @@ export class SessionStreamManager {
|
|
|
1346
1335
|
this.disposeSessionStream(sid);
|
|
1347
1336
|
}
|
|
1348
1337
|
}
|
|
1349
|
-
/**
|
|
1350
|
-
* Set the autonomous iterative loop state for a session.
|
|
1351
|
-
*
|
|
1352
|
-
* When `active` is true, the manager replays `originalPrompt` after each
|
|
1353
|
-
* `agent_end` event — the agent sees its own file changes from prior
|
|
1354
|
-
* iterations and iteratively improves its solution (Ralph Wiggum pattern).
|
|
1355
|
-
* The loop stops when the agent emits `<LOOP_DONE>` in its response or the
|
|
1356
|
-
* safety iteration limit is reached.
|
|
1357
|
-
*/
|
|
1358
|
-
setLoopActive(sessionId, active, originalPrompt, maxIterations, goal) {
|
|
1359
|
-
const stream = this.streams.get(sessionId);
|
|
1360
|
-
if (!stream)
|
|
1361
|
-
return;
|
|
1362
|
-
// Bump the epoch on EVERY call (arm and disarm alike) so the async
|
|
1363
|
-
// "@preset:" arming tail can detect loop-state changes that happen
|
|
1364
|
-
// while its resolver is awaited (see armLoopWithExpandedPreset).
|
|
1365
|
-
stream.loopGeneration += 1;
|
|
1366
|
-
if (!active) {
|
|
1367
|
-
stream.loopActive = false;
|
|
1368
|
-
stream.loopOriginalPrompt = null;
|
|
1369
|
-
stream.loopIterationCount = 0;
|
|
1370
|
-
stream.loopMaxIterations = MAX_LOOP_ITERATIONS;
|
|
1371
|
-
stream.loopGoal = null;
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
const raw = originalPrompt ?? null;
|
|
1375
|
-
// Expand a leading "@preset:<name>" reference ONCE, at arming time, so
|
|
1376
|
-
// every loop iteration replays the resolved prompt instead of the
|
|
1377
|
-
// literal reference (which would re-expand — and re-chain — each turn).
|
|
1378
|
-
// Non-reference prompts arm synchronously exactly as before; only the
|
|
1379
|
-
// reference path defers through the resolver (raw prompt on failure).
|
|
1380
|
-
if (!raw || !parsePresetReference(raw)) {
|
|
1381
|
-
stream.loopActive = true;
|
|
1382
|
-
stream.loopOriginalPrompt = raw;
|
|
1383
|
-
if (maxIterations !== undefined && maxIterations > 0) {
|
|
1384
|
-
stream.loopMaxIterations = Math.min(maxIterations, MAX_LOOP_ITERATIONS);
|
|
1385
|
-
}
|
|
1386
|
-
stream.loopGoal = goal?.trim() || null;
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
return this.armLoopWithExpandedPreset(sessionId, raw, maxIterations, goal);
|
|
1390
|
-
}
|
|
1391
|
-
/**
|
|
1392
|
-
* Async tail of {@link setLoopActive} for "@preset:<name>" loop prompts:
|
|
1393
|
-
* resolves the preset, then arms the loop with the expanded prompt (raw
|
|
1394
|
-
* content when the resolver fails). The chain budget is NOT consumed by
|
|
1395
|
-
* loop arming — expansion happens once here, not on every iteration.
|
|
1396
|
-
*
|
|
1397
|
-
* Returns the expanded prompt so the caller (dispatcher, interval tick)
|
|
1398
|
-
* can fire the FIRST turn with the resolved content instead of the raw
|
|
1399
|
-
* reference. Resolves to `null` when arming was aborted because loop
|
|
1400
|
-
* state changed while the resolver was awaited (the write is skipped so
|
|
1401
|
-
* newer loop state is never clobbered) or when no stream exists.
|
|
1402
|
-
*/
|
|
1403
|
-
async armLoopWithExpandedPreset(sessionId, prompt, maxIterations, goal) {
|
|
1404
|
-
const stream = this.streams.get(sessionId);
|
|
1405
|
-
if (!stream)
|
|
1406
|
-
return null;
|
|
1407
|
-
// Capture the epoch BEFORE the resolver await: any other setLoopActive
|
|
1408
|
-
// call during the await (e.g. a loop stop from a client message) moves
|
|
1409
|
-
// the generation and must win over this arming write.
|
|
1410
|
-
const generation = stream.loopGeneration;
|
|
1411
|
-
const expanded = await expandPresetPrompt(prompt, (name) => this.resolvePresetForSession(sessionId, name));
|
|
1412
|
-
if (stream.loopGeneration !== generation) {
|
|
1413
|
-
console.log(`[session-stream] ${sessionId}: loop arming aborted — ` +
|
|
1414
|
-
`loop state changed while the preset was being resolved`);
|
|
1415
|
-
return null;
|
|
1416
|
-
}
|
|
1417
|
-
stream.loopActive = true;
|
|
1418
|
-
stream.loopOriginalPrompt = expanded;
|
|
1419
|
-
if (maxIterations !== undefined && maxIterations > 0) {
|
|
1420
|
-
stream.loopMaxIterations = Math.min(maxIterations, MAX_LOOP_ITERATIONS);
|
|
1421
|
-
}
|
|
1422
|
-
stream.loopGoal = goal?.trim() || null;
|
|
1423
|
-
return expanded;
|
|
1424
|
-
}
|
|
1425
1338
|
/**
|
|
1426
1339
|
* Studio team id used for machine-JWT backend calls (preset resolution):
|
|
1427
1340
|
* the project's team binding wins over the machine default.
|
|
@@ -1451,12 +1364,9 @@ export class SessionStreamManager {
|
|
|
1451
1364
|
stream.intervalConfig = config;
|
|
1452
1365
|
stream.intervalLaunchCount = 0;
|
|
1453
1366
|
this.scheduleIntervalTick(stream, minutes);
|
|
1454
|
-
const interval = this.getIntervalEventSnapshot(stream);
|
|
1455
1367
|
this.broadcast(stream, {
|
|
1456
1368
|
type: "interval_armed",
|
|
1457
|
-
intervalMinutes:
|
|
1458
|
-
hasLoop: interval.hasLoop,
|
|
1459
|
-
goal: interval.goal,
|
|
1369
|
+
intervalMinutes: minutes,
|
|
1460
1370
|
});
|
|
1461
1371
|
}
|
|
1462
1372
|
/**
|
|
@@ -1501,8 +1411,6 @@ export class SessionStreamManager {
|
|
|
1501
1411
|
return {
|
|
1502
1412
|
intervalMinutes: stream.intervalMinutes,
|
|
1503
1413
|
nextTickAt: stream.intervalNextTickAt,
|
|
1504
|
-
hasLoop: stream.intervalConfig?.loop ?? false,
|
|
1505
|
-
goal: stream.intervalConfig?.loopGoal ?? null,
|
|
1506
1414
|
occurrence: stream.intervalLaunchCount,
|
|
1507
1415
|
};
|
|
1508
1416
|
}
|
|
@@ -1511,7 +1419,7 @@ export class SessionStreamManager {
|
|
|
1511
1419
|
stream.intervalNextTickAt = Date.now() + minutes * 60_000;
|
|
1512
1420
|
stream.intervalTimer = setInterval(() => {
|
|
1513
1421
|
// intervalTick is async and may reject (e.g. a sync throw from
|
|
1514
|
-
//
|
|
1422
|
+
// prompt()): without a
|
|
1515
1423
|
// handler a timer callback rejection would be unhandled.
|
|
1516
1424
|
this.intervalTick(stream).catch((err) => {
|
|
1517
1425
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1632,10 +1540,9 @@ export class SessionStreamManager {
|
|
|
1632
1540
|
async intervalTick(stream) {
|
|
1633
1541
|
if (!stream.intervalConfig)
|
|
1634
1542
|
return;
|
|
1635
|
-
const idle =
|
|
1543
|
+
const idle = stream.currentTurn === null &&
|
|
1636
1544
|
!stream.compacting && !stream.expandingQueueItem;
|
|
1637
1545
|
if (!idle) {
|
|
1638
|
-
const interval = this.getIntervalEventSnapshot(stream);
|
|
1639
1546
|
this.broadcast(stream, {
|
|
1640
1547
|
type: "interval_skipped",
|
|
1641
1548
|
reason: "busy",
|
|
@@ -1649,18 +1556,7 @@ export class SessionStreamManager {
|
|
|
1649
1556
|
occurrence: interval.occurrence,
|
|
1650
1557
|
});
|
|
1651
1558
|
const cfg = stream.intervalConfig;
|
|
1652
|
-
|
|
1653
|
-
if (cfg.loop) {
|
|
1654
|
-
// Await so a "@preset:" prompt is expanded before the turn fires.
|
|
1655
|
-
const expanded = await this.setLoopActive(stream.sessionId, true, cfg.prompt, cfg.loopMaxIterations, cfg.loopGoal);
|
|
1656
|
-
// Same raw-reference fix as the dispatcher path: the first interval
|
|
1657
|
-
// launch must run the expanded prompt, not the literal "@preset:X"
|
|
1658
|
-
// (null = arming aborted → fall back to the raw prompt).
|
|
1659
|
-
if (typeof expanded === "string" && expanded) {
|
|
1660
|
-
launchPrompt = expanded;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
this.prompt(stream.sessionId, launchPrompt, cfg.modelId, cfg.images, cfg.reasoningEffort).catch((err) => {
|
|
1559
|
+
this.prompt(stream.sessionId, cfg.prompt, cfg.modelId, cfg.images, cfg.reasoningEffort).catch((err) => {
|
|
1664
1560
|
console.error(`[interval] launch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1665
1561
|
});
|
|
1666
1562
|
}
|
|
@@ -1707,55 +1603,6 @@ export class SessionStreamManager {
|
|
|
1707
1603
|
});
|
|
1708
1604
|
});
|
|
1709
1605
|
}
|
|
1710
|
-
/** Build the next autonomous-loop prompt after optional policy-driven compaction. */
|
|
1711
|
-
buildLoopPrompt(stream) {
|
|
1712
|
-
const parts = [];
|
|
1713
|
-
if (stream.loopGoal) {
|
|
1714
|
-
parts.push(`[GOAL]: ${stream.loopGoal}`);
|
|
1715
|
-
}
|
|
1716
|
-
parts.push(stream.loopOriginalPrompt);
|
|
1717
|
-
parts.push("");
|
|
1718
|
-
parts.push("--- LOOP INSTRUCTIONS ---\n" +
|
|
1719
|
-
"You are running in an iterative loop. Each iteration you see your previous changes\n" +
|
|
1720
|
-
"and can improve them further.\n" +
|
|
1721
|
-
(stream.loopGoal
|
|
1722
|
-
? "The [GOAL] above defines what you need to achieve.\n"
|
|
1723
|
-
: "\n") +
|
|
1724
|
-
"CRITICAL: After EVERY response you MUST self-evaluate:\n" +
|
|
1725
|
-
"- If the task IS FULLY COMPLETE (nothing meaningful left to do), " +
|
|
1726
|
-
"respond with <LOOP_DONE> as the VERY FIRST text in your reply,\n" +
|
|
1727
|
-
" followed by a brief summary of what was accomplished.\n" +
|
|
1728
|
-
"- If more work remains, continue working WITHOUT <LOOP_DONE>.\n" +
|
|
1729
|
-
"- <LOOP_DONE> is the ONLY way to stop the loop. If you do not emit it,\n" +
|
|
1730
|
-
" you WILL be prompted again to keep working.\n" +
|
|
1731
|
-
"Be decisive: if the task is done, stop. Do not keep polishing.");
|
|
1732
|
-
return parts.join("\n");
|
|
1733
|
-
}
|
|
1734
|
-
async sendNextLoopIteration(stream) {
|
|
1735
|
-
const policy = loadCompactionPolicy(stream.cwd);
|
|
1736
|
-
const loopThresholdTokens = getAutoOlderHistoryContextThresholdTokens(policy, stream.contextWindowMax);
|
|
1737
|
-
const contextWindowUsed = stream.contextWindowUsed;
|
|
1738
|
-
const shouldCompact = loopThresholdTokens !== null &&
|
|
1739
|
-
typeof contextWindowUsed === "number" &&
|
|
1740
|
-
contextWindowUsed >= loopThresholdTokens;
|
|
1741
|
-
if (shouldCompact) {
|
|
1742
|
-
console.log(`[loop] compacting context (~${contextWindowUsed.toLocaleString()} tokens >= ` +
|
|
1743
|
-
`${loopThresholdTokens.toLocaleString()} policy threshold)`);
|
|
1744
|
-
const outcome = await this.requestManagerCompaction(stream, {
|
|
1745
|
-
source: "loop",
|
|
1746
|
-
});
|
|
1747
|
-
if (outcome.status === "completed") {
|
|
1748
|
-
console.log("[loop] compaction complete, sending next iteration");
|
|
1749
|
-
}
|
|
1750
|
-
else if (outcome.status === "skipped") {
|
|
1751
|
-
console.log(`[loop] compaction skipped (${outcome.reason}), proceeding`);
|
|
1752
|
-
}
|
|
1753
|
-
else {
|
|
1754
|
-
console.error(`[loop] compaction failed: ${outcome.error.message}`);
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
await this.prompt(stream.sessionId, this.buildLoopPrompt(stream), undefined);
|
|
1758
|
-
}
|
|
1759
1606
|
// --- internals ----------------------------------------------------------
|
|
1760
1607
|
createStream(sessionId, history) {
|
|
1761
1608
|
// Resolve cwd from the owning project. Sessions without a project
|
|
@@ -1793,7 +1640,6 @@ export class SessionStreamManager {
|
|
|
1793
1640
|
streaming: false,
|
|
1794
1641
|
retrying: false,
|
|
1795
1642
|
compacting: false,
|
|
1796
|
-
looping: false,
|
|
1797
1643
|
queued: false,
|
|
1798
1644
|
},
|
|
1799
1645
|
},
|
|
@@ -1802,14 +1648,8 @@ export class SessionStreamManager {
|
|
|
1802
1648
|
currentTurnSilenceTimer: null,
|
|
1803
1649
|
currentMessageId: null,
|
|
1804
1650
|
lastFlushedEventCount: 0,
|
|
1805
|
-
loopActive: false,
|
|
1806
|
-
loopIterationCount: 0,
|
|
1807
|
-
loopOriginalPrompt: null,
|
|
1808
|
-
loopMaxIterations: MAX_LOOP_ITERATIONS,
|
|
1809
|
-
loopGoal: null,
|
|
1810
1651
|
presetChainBudget: PRESET_CHAIN_BUDGET,
|
|
1811
1652
|
expandingQueueItem: false,
|
|
1812
|
-
loopGeneration: 0,
|
|
1813
1653
|
intervalMinutes: null,
|
|
1814
1654
|
intervalTimer: null,
|
|
1815
1655
|
intervalNextTickAt: null,
|
|
@@ -2036,7 +1876,7 @@ export class SessionStreamManager {
|
|
|
2036
1876
|
// row; observed in the wild: a single row with 12 full message cycles,
|
|
2037
1877
|
// 67MB events_jsonl, content = sum of all cycles' texts). Earlier
|
|
2038
1878
|
// cycles are already persisted in their own rows; turn-level text is
|
|
2039
|
-
// kept separately in `turnAssistantText
|
|
1879
|
+
// kept separately in `turnAssistantText`.
|
|
2040
1880
|
if (event.type === "message_start") {
|
|
2041
1881
|
stream.currentTurn.events = [];
|
|
2042
1882
|
stream.currentTurn.assistantText = "";
|
|
@@ -2283,66 +2123,6 @@ export class SessionStreamManager {
|
|
|
2283
2123
|
// compact-and-retry). The deferred settle below re-resolves the final
|
|
2284
2124
|
// state once the SDK is truly settled.
|
|
2285
2125
|
stream.pendingContinue = event.willRetry === true;
|
|
2286
|
-
// Autonomous iterative loop (Ralph Wiggum pattern).
|
|
2287
|
-
// When loopActive is set, check for completion marker, then re-send
|
|
2288
|
-
// the ORIGINAL prompt so the agent sees its prior changes and
|
|
2289
|
-
// iteratively improves the solution.
|
|
2290
|
-
if (stream.loopActive && stream.loopOriginalPrompt) {
|
|
2291
|
-
// Use the turn-level accumulator: `assistantText` is rotated per
|
|
2292
|
-
// message cycle now, but the marker is emitted in the first portion
|
|
2293
|
-
// of the response — an earlier message cycle than the last one.
|
|
2294
|
-
const finishedAssistantText = finishedTurn?.turnAssistantText ?? finishedTurn?.assistantText ?? "";
|
|
2295
|
-
// Check for the loop-done marker in the first portion of the
|
|
2296
|
-
// response (the prompt instructs the agent to emit it first).
|
|
2297
|
-
const head = finishedAssistantText.slice(0, 500);
|
|
2298
|
-
if (head.includes(LOOP_DONE_MARKER)) {
|
|
2299
|
-
console.log(`[loop] completion marker detected after ${stream.loopIterationCount} iteration(s), stopping`);
|
|
2300
|
-
const completedIterations = stream.loopIterationCount;
|
|
2301
|
-
stream.loopActive = false;
|
|
2302
|
-
stream.loopIterationCount = 0;
|
|
2303
|
-
stream.loopOriginalPrompt = null;
|
|
2304
|
-
stream.loopGoal = null;
|
|
2305
|
-
this.broadcast(stream, {
|
|
2306
|
-
type: "loop_complete",
|
|
2307
|
-
iterations: completedIterations,
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
|
-
else if (stream.loopIterationCount >= stream.loopMaxIterations) {
|
|
2311
|
-
console.log(`[loop] max iterations (${stream.loopMaxIterations}) reached, stopping`);
|
|
2312
|
-
stream.loopActive = false;
|
|
2313
|
-
stream.loopOriginalPrompt = null;
|
|
2314
|
-
stream.loopGoal = null;
|
|
2315
|
-
this.broadcast(stream, {
|
|
2316
|
-
type: "loop_max_iterations",
|
|
2317
|
-
iterations: stream.loopIterationCount,
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
else {
|
|
2321
|
-
stream.loopIterationCount++;
|
|
2322
|
-
console.log(`[loop] iteration ${stream.loopIterationCount}/${stream.loopMaxIterations}`);
|
|
2323
|
-
this.broadcast(stream, {
|
|
2324
|
-
type: "loop_iteration",
|
|
2325
|
-
iteration: stream.loopIterationCount,
|
|
2326
|
-
maxIterations: stream.loopMaxIterations,
|
|
2327
|
-
prompt: stream.loopOriginalPrompt,
|
|
2328
|
-
});
|
|
2329
|
-
// Deferred via setImmediate: agent_end fires synchronously while
|
|
2330
|
-
// the agent core is still settling the run (isStreaming is still
|
|
2331
|
-
// true). Calling prompt() synchronously would throw "Agent is
|
|
2332
|
-
// already processing" because finishRun() hasn't cleared
|
|
2333
|
-
// isStreaming yet.
|
|
2334
|
-
setImmediate(() => {
|
|
2335
|
-
if (this.disposed)
|
|
2336
|
-
return;
|
|
2337
|
-
void this.sendNextLoopIteration(stream).catch((err) => {
|
|
2338
|
-
console.error(`[loop] iteration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2339
|
-
stream.loopActive = false;
|
|
2340
|
-
stream.loopOriginalPrompt = null;
|
|
2341
|
-
stream.loopGoal = null;
|
|
2342
|
-
});
|
|
2343
|
-
});
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
2126
|
// Fork & Compact: after the first assistant turn of a forked session,
|
|
2347
2127
|
// compact the context. The custom instructions reference the user's
|
|
2348
2128
|
// new message so the summary preserves relevant context.
|
|
@@ -2351,8 +2131,8 @@ export class SessionStreamManager {
|
|
|
2351
2131
|
stream.forkCompactSourceId = null; // one-shot
|
|
2352
2132
|
this.triggerForkCompact(stream);
|
|
2353
2133
|
}
|
|
2354
|
-
// Auto-dequeue: after the turn finishes
|
|
2355
|
-
//
|
|
2134
|
+
// Auto-dequeue: after the turn finishes, check the persistent prompt
|
|
2135
|
+
// queue. If there's a queued
|
|
2356
2136
|
// prompt, start it immediately without broadcasting agent_end —
|
|
2357
2137
|
// the frontend transitions seamlessly to the next turn.
|
|
2358
2138
|
// Skip if compaction is in-flight (e.g. fork-compact just started
|
|
@@ -2364,17 +2144,13 @@ export class SessionStreamManager {
|
|
|
2364
2144
|
// synchronously would throw "Agent is already processing" — the
|
|
2365
2145
|
// agent core rejects all prompt() calls until finishRun() clears
|
|
2366
2146
|
// isStreaming, which happens after this listener returns.
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
return;
|
|
2375
|
-
this.maybeAutoDequeue(stream);
|
|
2376
|
-
});
|
|
2377
|
-
}
|
|
2147
|
+
this.runTurnCleanup(finishedTurn);
|
|
2148
|
+
if (!stream.compacting) {
|
|
2149
|
+
setImmediate(() => {
|
|
2150
|
+
if (this.disposed)
|
|
2151
|
+
return;
|
|
2152
|
+
this.maybeAutoDequeue(stream);
|
|
2153
|
+
});
|
|
2378
2154
|
}
|
|
2379
2155
|
this.settleRunState(stream);
|
|
2380
2156
|
}
|
|
@@ -2403,7 +2179,7 @@ export class SessionStreamManager {
|
|
|
2403
2179
|
/**
|
|
2404
2180
|
* Resolve the authoritative run-state snapshot for a stream. This is the
|
|
2405
2181
|
* ONLY place `phase`/`flags`/`busy` are computed. Phase precedence:
|
|
2406
|
-
* retrying > compacting >
|
|
2182
|
+
* retrying > compacting > streaming > queued > idle.
|
|
2407
2183
|
*/
|
|
2408
2184
|
resolveRunState(stream) {
|
|
2409
2185
|
const bridge = stream.bridge;
|
|
@@ -2411,7 +2187,6 @@ export class SessionStreamManager {
|
|
|
2411
2187
|
streaming: stream.currentTurn != null || bridge.isStreaming?.() === true,
|
|
2412
2188
|
retrying: bridge.isRetrying?.() === true,
|
|
2413
2189
|
compacting: stream.compacting,
|
|
2414
|
-
looping: stream.loopActive,
|
|
2415
2190
|
// Prompt queue is pending work, not active work. It is exposed as a
|
|
2416
2191
|
// flag for the badge only — it never feeds `busy`.
|
|
2417
2192
|
queued: stream.queued,
|
|
@@ -2419,7 +2194,6 @@ export class SessionStreamManager {
|
|
|
2419
2194
|
const busy = flags.streaming ||
|
|
2420
2195
|
flags.retrying ||
|
|
2421
2196
|
flags.compacting ||
|
|
2422
|
-
flags.looping ||
|
|
2423
2197
|
stream.pendingContinue ||
|
|
2424
2198
|
// R1: a `prompt()` dispatch is in flight — the turn is not armed yet
|
|
2425
2199
|
// (we may still be awaiting `ready`/`setModel`), but the session is
|
|
@@ -2429,15 +2203,13 @@ export class SessionStreamManager {
|
|
|
2429
2203
|
? "retrying"
|
|
2430
2204
|
: flags.compacting
|
|
2431
2205
|
? "compacting"
|
|
2432
|
-
: flags.
|
|
2433
|
-
? "
|
|
2434
|
-
: flags.
|
|
2435
|
-
? "
|
|
2436
|
-
:
|
|
2437
|
-
? "
|
|
2438
|
-
:
|
|
2439
|
-
? "streaming" // pendingContinue gap — keep "streaming"
|
|
2440
|
-
: "idle";
|
|
2206
|
+
: flags.streaming
|
|
2207
|
+
? "streaming"
|
|
2208
|
+
: flags.queued
|
|
2209
|
+
? "queued"
|
|
2210
|
+
: busy
|
|
2211
|
+
? "streaming" // pendingContinue gap — keep "streaming"
|
|
2212
|
+
: "idle";
|
|
2441
2213
|
return { ...stream.runState, busy, phase, flags };
|
|
2442
2214
|
}
|
|
2443
2215
|
computeBusy(stream) {
|
|
@@ -2597,14 +2369,12 @@ export class SessionStreamManager {
|
|
|
2597
2369
|
}
|
|
2598
2370
|
/**
|
|
2599
2371
|
* Auto-dequeue the next prompt from the persistent queue and start a
|
|
2600
|
-
* new turn. Called from the `agent_end` handler
|
|
2372
|
+
* new turn. Called from the `agent_end` handler.
|
|
2601
2373
|
* Returns true if a prompt was dequeued and a turn started.
|
|
2602
2374
|
*/
|
|
2603
2375
|
maybeAutoDequeue(stream) {
|
|
2604
2376
|
if (stream.currentTurn || stream.compacting)
|
|
2605
2377
|
return false;
|
|
2606
|
-
if (stream.loopActive && stream.loopOriginalPrompt)
|
|
2607
|
-
return false;
|
|
2608
2378
|
// A dequeued "@preset:" reference is being expanded (async resolver) —
|
|
2609
2379
|
// never dequeue a second item into the same gap.
|
|
2610
2380
|
if (stream.expandingQueueItem)
|
package/dist/server/wire.d.ts
CHANGED
|
@@ -71,10 +71,10 @@ export interface WireMessage {
|
|
|
71
71
|
/**
|
|
72
72
|
* Authoritative per-session agent run phase. The CLI resolves this single
|
|
73
73
|
* value server-side (see `SessionStream.resolveRunState`) using the
|
|
74
|
-
* precedence: retrying > compacting >
|
|
74
|
+
* precedence: retrying > compacting > streaming > queued > idle.
|
|
75
75
|
* The frontend badge renders it but never derives it locally.
|
|
76
76
|
*/
|
|
77
|
-
export type RunPhase = "idle" | "streaming" | "retrying" | "compacting" | "
|
|
77
|
+
export type RunPhase = "idle" | "streaming" | "retrying" | "compacting" | "queued";
|
|
78
78
|
/**
|
|
79
79
|
* Single source of truth for "is the agent working right now". `busy` is the
|
|
80
80
|
* only field the frontend composer should use to decide send vs queue; `phase`
|
|
@@ -93,7 +93,6 @@ export interface WireRunState {
|
|
|
93
93
|
streaming: boolean;
|
|
94
94
|
retrying: boolean;
|
|
95
95
|
compacting: boolean;
|
|
96
|
-
looping: boolean;
|
|
97
96
|
queued: boolean;
|
|
98
97
|
};
|
|
99
98
|
}
|
|
@@ -316,18 +315,12 @@ export interface WireCompactionTokenSourceBucket {
|
|
|
316
315
|
export type ClientMessage = {
|
|
317
316
|
type: "user_message";
|
|
318
317
|
content: string;
|
|
319
|
-
/** Enable autonomous iterative loop for this message (Ralph Wiggum pattern). */
|
|
320
|
-
loop?: boolean;
|
|
321
318
|
/** Optional image attachments (hosted `url` preferred, inline base64 legacy). */
|
|
322
319
|
images?: ImageAttachment[];
|
|
323
|
-
/** Optional loop max iterations (server caps at MAX_LOOP_ITERATIONS). */
|
|
324
|
-
loopMaxIterations?: number;
|
|
325
|
-
/** Optional loop goal / acceptance criteria. */
|
|
326
|
-
loopGoal?: string;
|
|
327
320
|
/**
|
|
328
321
|
* Optional interval scheduler (minutes). When set, arms a recurring
|
|
329
|
-
* scheduler that re-launches the prompt
|
|
330
|
-
*
|
|
322
|
+
* scheduler that re-launches the prompt every N minutes, guarded by an
|
|
323
|
+
* idle check. The first
|
|
331
324
|
* launch happens immediately via the normal prompt path; the scheduler
|
|
332
325
|
* ticks from +N minutes.
|
|
333
326
|
*/
|
|
@@ -337,7 +330,7 @@ export type ClientMessage = {
|
|
|
337
330
|
* immediately (and resets the cadence); `"stop"` disarms the scheduler.
|
|
338
331
|
* Sent as a user_message with no content.
|
|
339
332
|
*/
|
|
340
|
-
|
|
333
|
+
intervalControl?: "run_now" | "stop";
|
|
341
334
|
};
|
|
342
335
|
/**
|
|
343
336
|
* Subset of `ServerEvent` that's safe to buffer per turn and replay on
|
|
@@ -394,8 +387,6 @@ export type ServerEvent = {
|
|
|
394
387
|
intervalMinutes?: number | null;
|
|
395
388
|
intervalNextTickAt?: number | null;
|
|
396
389
|
intervalLaunchCount?: number;
|
|
397
|
-
intervalHasLoop?: boolean;
|
|
398
|
-
intervalGoal?: string | null;
|
|
399
390
|
/** Authoritative run-state snapshot for reconnect/hydration. */
|
|
400
391
|
runState?: WireRunState;
|
|
401
392
|
} | {
|
|
@@ -426,8 +417,6 @@ export type ServerEvent = {
|
|
|
426
417
|
intervalMinutes?: number | null;
|
|
427
418
|
intervalNextTickAt?: number | null;
|
|
428
419
|
intervalLaunchCount?: number;
|
|
429
|
-
intervalHasLoop?: boolean;
|
|
430
|
-
intervalGoal?: string | null;
|
|
431
420
|
/** Authoritative run-state snapshot for reconnect/hydration. */
|
|
432
421
|
runState?: WireRunState;
|
|
433
422
|
} | {
|
|
@@ -559,6 +548,19 @@ export type ServerEvent = {
|
|
|
559
548
|
} | {
|
|
560
549
|
type: "error";
|
|
561
550
|
message: string;
|
|
551
|
+
} | {
|
|
552
|
+
/**
|
|
553
|
+
* Emitted when a requested modelId is no longer active in the backend's
|
|
554
|
+
* available-models registry (stale whitelist entry) and the server
|
|
555
|
+
* substituted an active fallback — the backend default (`isDefault`)
|
|
556
|
+
* when present, else the first available model — so the turn proceeds
|
|
557
|
+
* instead of failing. The turn runs with `activeModelId`, which is
|
|
558
|
+
* also persisted as the session's sticky model. Clients should update
|
|
559
|
+
* their model picker to `activeModelId`.
|
|
560
|
+
*/
|
|
561
|
+
type: "model_fallback";
|
|
562
|
+
requestedModelId: string;
|
|
563
|
+
activeModelId: string;
|
|
562
564
|
} | {
|
|
563
565
|
/**
|
|
564
566
|
* Best-effort feedback for a `stop_tool_call` request. `stopped` when the
|
|
@@ -570,26 +572,10 @@ export type ServerEvent = {
|
|
|
570
572
|
status: "stopped" | "not_found";
|
|
571
573
|
/** Echoed reason from the request, when supplied. */
|
|
572
574
|
reason?: string;
|
|
573
|
-
} | {
|
|
574
|
-
/** Emitted at the start of each loop iteration. */
|
|
575
|
-
type: "loop_iteration";
|
|
576
|
-
iteration: number;
|
|
577
|
-
maxIterations: number;
|
|
578
|
-
prompt: string;
|
|
579
|
-
} | {
|
|
580
|
-
/** Emitted when the agent signals completion via <LOOP_DONE> marker. */
|
|
581
|
-
type: "loop_complete";
|
|
582
|
-
iterations: number;
|
|
583
|
-
} | {
|
|
584
|
-
/** Emitted when the loop hits the safety iteration limit. */
|
|
585
|
-
type: "loop_max_iterations";
|
|
586
|
-
iterations: number;
|
|
587
575
|
} | {
|
|
588
576
|
/** Emitted when the interval scheduler is armed. */
|
|
589
577
|
type: "interval_armed";
|
|
590
578
|
intervalMinutes: number;
|
|
591
|
-
hasLoop: boolean;
|
|
592
|
-
goal: string | null;
|
|
593
579
|
} | {
|
|
594
580
|
/** Emitted each time the interval scheduler launches a new occurrence. */
|
|
595
581
|
type: "interval_launched";
|