scream-code 0.17.7 → 0.17.9
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 +4 -4
- package/dist/{app-BnHwLVy5.mjs → app-DiPC-zEu.mjs} +994 -73
- package/dist/{dispatch-DVZFRvzo.mjs → dispatch-BGYq0dnV.mjs} +18741 -17257
- package/dist/{dispatch-Ccdic2EJ.mjs → dispatch-DoaBG--u.mjs} +1 -1
- package/dist/main.mjs +1 -1
- package/dist/public/assets/{index-CVXe8l3P.css → index-B1g39y1G.css} +1 -1
- package/dist/public/assets/{index-CKa_o5qA.js → index-BTL8OnOo.js} +36 -36
- package/dist/public/assets/index-BTL8OnOo.js.map +1 -0
- package/dist/public/index.html +2 -2
- package/dist/{src-BhZujafw.mjs → src-C7C9e5Ds.mjs} +80 -46
- package/dist/{src-CUURS-i6.mjs → src-qit_jRUM.mjs} +1 -1
- package/dist/{text-input-dialog-CD0sakGq.mjs → text-input-dialog-BlSqBYxO.mjs} +40 -4
- package/dist/{text-input-dialog-DcjQLJFz.mjs → text-input-dialog-HfsxLcQf.mjs} +1 -1
- package/package.json +6 -4
- package/dist/public/assets/index-CKa_o5qA.js.map +0 -1
|
@@ -4,9 +4,9 @@ import { dirname as __cjsShimDirname } from 'node:path';
|
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { $n as encodeWorkDirKey, $t as lerpGradient, A as highlightLines, An as embeddingFailureDetail, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as detectShellEnvironment, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getLlmNotSetMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as sortSlashCommands, Dt as createEditorTheme, E as UserMessageComponent, En as BUILTIN_SLASH_COMMANDS, Et as resolveThemeSync, F as StatusMessageComponent, Fn as PULSE_WAVE_FRAMES, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_COMMAND_NAME, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getInputHistoryFile, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiConfigParseError, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as PRODUCT_NAME, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_UI_MODE, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as TuiLikePreferencesSchema, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as hasEmbeddingModelCache, Mt as createThemeStyles, N as ThinkingComponent, Nn as startManualEmbeddingDownload, Nt as contrastTextHex, O as renderDiffLines, On as isExperimentalFlagEnabled, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PIXEL_PULSE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as ScreamHarness, Qt as FooterComponent, R as SkillActivationComponent, Rn as loadTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getCtrlDHint, St as supportsBalance, Tn as buildSkillSlashCommands, U as getSharedSpeedTracker, Un as getLogDir, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getDataDir, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as detectInstallSource, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as fetchCatalog, Xt as isBusy, Y as resetBreathingClock, Yn as DEFAULT_CATALOG_URL, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as saveCatalogCache, Zt as isStreaming, _n as EXIT_CONFIRM_WINDOW_MS, a as syncDiagramCapabilityPrompt, an as printableChar, ar as isRealUserPrompt, at as toTerminalHyperlink, b as readUpdateCache, bn as TIP_ROTATION_INTERVAL_MS, c as disposeChildren, cn as appendStreamingArgsPreview, cr as
|
|
9
|
-
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-
|
|
7
|
+
import { A as sharedMemoStore } from "./src-C7C9e5Ds.mjs";
|
|
8
|
+
import { $n as encodeWorkDirKey, $t as lerpGradient, A as highlightLines, An as embeddingFailureDetail, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as detectShellEnvironment, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getLlmNotSetMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as sortSlashCommands, Dt as createEditorTheme, E as UserMessageComponent, En as BUILTIN_SLASH_COMMANDS, Et as resolveThemeSync, F as StatusMessageComponent, Fn as PULSE_WAVE_FRAMES, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_COMMAND_NAME, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getInputHistoryFile, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiConfigParseError, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as PRODUCT_NAME, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_UI_MODE, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as TuiLikePreferencesSchema, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as hasEmbeddingModelCache, Mt as createThemeStyles, N as ThinkingComponent, Nn as startManualEmbeddingDownload, Nt as contrastTextHex, O as renderDiffLines, On as isExperimentalFlagEnabled, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PIXEL_PULSE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as ScreamHarness, Qt as FooterComponent, R as SkillActivationComponent, Rn as loadTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getCtrlDHint, St as supportsBalance, Tn as buildSkillSlashCommands, U as getSharedSpeedTracker, Un as getLogDir, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getDataDir, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as detectInstallSource, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as fetchCatalog, Xt as isBusy, Y as resetBreathingClock, Yn as DEFAULT_CATALOG_URL, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as saveCatalogCache, Zt as isStreaming, _n as EXIT_CONFIRM_WINDOW_MS, a as syncDiagramCapabilityPrompt, an as printableChar, ar as isRealUserPrompt, at as toTerminalHyperlink, b as readUpdateCache, bn as TIP_ROTATION_INTERVAL_MS, c as disposeChildren, cn as appendStreamingArgsPreview, cr as flushDiagnosticLogs, d as isPlanExpandable, dn as isTodoItemShape, dr as isScreamError, en as SubagentSlots, er as normalizeWorkDir, f as MoonLoader, fn as parseStreamingArgs, fr as isOrphanedToolCallError, ft as handleFusionPlanCommand, gn as EMPTY_SESSION_HINT_URL, hn as truncateErrorMessage, i as buildRoleAdditionalText, it as isInternalMessage, j as langFromPath, jn as getKnowledgeStore, jt as toggleEmptySessionHint, k as renderDiffLinesClustered, kn as setExperimentalFlags, kt as isEmptySessionHintDismissed, l as hasDispose, ln as argsRecord, lr as log, mn as stringValue, mr as SCREAM_ERROR_INFO, mt as handlePlanCommand, n as clearEvalPanelState, nr as readSessionIndex, on as STATUS_BULLET, or as parseRlmMaxDepthArg, ot as changeThinkingLevel, p as formatMemoryMemoForInjection, pn as serializeToolResultOutput, pr as ErrorCodes, q as BREATHE_CYCLE_MS, qn as CLI_USER_AGENT_PRODUCT, qt as TERMINAL_FOCUS_OUT, r as openUrl, rr as removeSessionIndexEntry, sn as appendSessionHeaderHint, sr as resolveScreamHome, st as getModelCycleLevel, t as dispatchInput, tr as appendSessionIndexEntry, u as isExpandable, un as formatErrorMessage, ur as resolveGlobalLogPath, v as selectUpdateTarget, vn as MAIN_AGENT_ID, w as getDaemonInstructions, wn as getNoActiveSessionMessage, x as appendJsonlLine, xn as getCtrlCHint, xt as refreshProviderBalance, y as refreshUpdateCache, yn as SESSION_TIPS, z as AssistantMessageComponent, zn as saveTuiConfig, zt as ENABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-BGYq0dnV.mjs";
|
|
9
|
+
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-BlSqBYxO.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -33,7 +33,7 @@ function optionalBuildString(value) {
|
|
|
33
33
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
34
34
|
}
|
|
35
35
|
const SCREAM_BUILD_INFO = {
|
|
36
|
-
version: optionalBuildString("0.17.
|
|
36
|
+
version: optionalBuildString("0.17.9"),
|
|
37
37
|
channel: optionalBuildString(""),
|
|
38
38
|
commit: optionalBuildString(""),
|
|
39
39
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -2171,6 +2171,211 @@ function renderChildRequestNotice(fields, name) {
|
|
|
2171
2171
|
};
|
|
2172
2172
|
}
|
|
2173
2173
|
//#endregion
|
|
2174
|
+
//#region src/tui/utils/subagent-instances.ts
|
|
2175
|
+
/**
|
|
2176
|
+
* agent-core synthesizes `rlm_<timestamp>_<rand>` parent tool-call ids for
|
|
2177
|
+
* rlm() spawns (packages/agent-core/src/agent/tool/index.ts:374), so no
|
|
2178
|
+
* tool-call row ever exists for them — the id shape is the only RLM marker
|
|
2179
|
+
* available to the TUI. A renamed prefix degrades to the generic "unknown"
|
|
2180
|
+
* attribution, never to a wrong one.
|
|
2181
|
+
*/
|
|
2182
|
+
const RLM_TOOL_CALL_PREFIX = "rlm_";
|
|
2183
|
+
/** Guard against a corrupt or cyclic parent chain hanging the render path. */
|
|
2184
|
+
const MAX_CHAIN_DEPTH = 16;
|
|
2185
|
+
function descriptionFromToolCall(call) {
|
|
2186
|
+
const fromArgs = call?.args["description"];
|
|
2187
|
+
if (typeof call?.description === "string" && call.description.length > 0) return call.description;
|
|
2188
|
+
return typeof fromArgs === "string" && fromArgs.length > 0 ? fromArgs : void 0;
|
|
2189
|
+
}
|
|
2190
|
+
/** Snapshot a spawn event (+ the spawning tool call, still in flight at that
|
|
2191
|
+
* moment) into a registry record. `spawnedAt` is passed in so a resume keeps
|
|
2192
|
+
* the instance's original start time. */
|
|
2193
|
+
function createSubagentInstanceInfo(event, parentToolCall, spawnedAt = Date.now()) {
|
|
2194
|
+
return {
|
|
2195
|
+
agentId: event.subagentId,
|
|
2196
|
+
type: event.subagentName,
|
|
2197
|
+
description: event.description,
|
|
2198
|
+
parentAgentId: event.parentAgentId,
|
|
2199
|
+
parentToolCallId: event.parentToolCallId,
|
|
2200
|
+
parentToolName: parentToolCall?.name,
|
|
2201
|
+
parentToolDescription: descriptionFromToolCall(parentToolCall),
|
|
2202
|
+
spawnedAt
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
/** Close a registry record on subagent.completed / subagent.failed. */
|
|
2206
|
+
function withSubagentInstanceEnded(info, outcome, endedAt = Date.now()) {
|
|
2207
|
+
return {
|
|
2208
|
+
...info,
|
|
2209
|
+
endedAt,
|
|
2210
|
+
outcome
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
/** Walk `parentAgentId` upwards, collecting parent types (nearest first). */
|
|
2214
|
+
function buildAncestorChain(instance, instances) {
|
|
2215
|
+
const names = [];
|
|
2216
|
+
const seen = new Set([instance.agentId]);
|
|
2217
|
+
let parentId = instance.parentAgentId;
|
|
2218
|
+
while (parentId !== void 0 && parentId !== "main" && parentId.length > 0) {
|
|
2219
|
+
if (seen.has(parentId) || names.length >= MAX_CHAIN_DEPTH) return {
|
|
2220
|
+
names,
|
|
2221
|
+
truncated: true
|
|
2222
|
+
};
|
|
2223
|
+
seen.add(parentId);
|
|
2224
|
+
const parent = instances.get(parentId);
|
|
2225
|
+
if (parent === void 0) return {
|
|
2226
|
+
names,
|
|
2227
|
+
truncated: true
|
|
2228
|
+
};
|
|
2229
|
+
names.push(parent.type);
|
|
2230
|
+
parentId = parent.parentAgentId;
|
|
2231
|
+
}
|
|
2232
|
+
return {
|
|
2233
|
+
names,
|
|
2234
|
+
truncated: false
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
/** Best-effort "who triggered this instance" resolution. */
|
|
2238
|
+
function resolveSource(instance, instances) {
|
|
2239
|
+
const parentId = instance.parentAgentId;
|
|
2240
|
+
const parentInstance = parentId !== void 0 && parentId !== "main" ? instances.get(parentId) : void 0;
|
|
2241
|
+
const isRlm = instance.parentToolCallId.startsWith(RLM_TOOL_CALL_PREFIX);
|
|
2242
|
+
if (parentInstance !== void 0) return {
|
|
2243
|
+
kind: isRlm ? "rlm" : "agent",
|
|
2244
|
+
name: parentInstance.type,
|
|
2245
|
+
description: parentInstance.description
|
|
2246
|
+
};
|
|
2247
|
+
if (isRlm) return {
|
|
2248
|
+
kind: "rlm",
|
|
2249
|
+
name: void 0,
|
|
2250
|
+
description: instance.description
|
|
2251
|
+
};
|
|
2252
|
+
if (instance.parentToolName !== void 0) return {
|
|
2253
|
+
kind: "tool",
|
|
2254
|
+
name: instance.parentToolName,
|
|
2255
|
+
description: instance.parentToolDescription
|
|
2256
|
+
};
|
|
2257
|
+
if (parentId === void 0 || parentId === "main") return {
|
|
2258
|
+
kind: "main",
|
|
2259
|
+
name: void 0,
|
|
2260
|
+
description: void 0
|
|
2261
|
+
};
|
|
2262
|
+
return {
|
|
2263
|
+
kind: "unknown",
|
|
2264
|
+
name: void 0,
|
|
2265
|
+
description: void 0
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Map the slot snapshot + instance registry into Agents-view rows.
|
|
2270
|
+
*
|
|
2271
|
+
* One row per slot that is live or has any instance history in this session
|
|
2272
|
+
* (never-used slots stay out), plus one row per *registry-only* type: past
|
|
2273
|
+
* `MAX_SUBAGENT_SLOTS` the slot machine returns a detached slot that never
|
|
2274
|
+
* reaches `slots`, so the 17th+ type would otherwise spawn and finish
|
|
2275
|
+
* invisibly while the registry still records every instance. A synthesized
|
|
2276
|
+
* row is never live, takes its status from the type's latest instance
|
|
2277
|
+
* (`completed` / `failed`, else `idle` when nothing terminal was recorded),
|
|
2278
|
+
* counts every recorded instance of the type and has no `instanceId`; its
|
|
2279
|
+
* source and ancestors use the same derivation as slot-backed rows. Live rows
|
|
2280
|
+
* sort first (most recent activity on top), then ended ones by their end time.
|
|
2281
|
+
*
|
|
2282
|
+
* `recent` is the archive ring (newest last): rows derived when an instance
|
|
2283
|
+
* closed. A type the live snapshot no longer covers keeps its archived row, so
|
|
2284
|
+
* splitting the registry into live + ring does not change what the view shows
|
|
2285
|
+
* for anything inside the ring window. Called without `recent` (as before the
|
|
2286
|
+
* ring existed) the output depends on `slots` + `instances` alone.
|
|
2287
|
+
*/
|
|
2288
|
+
function buildAgentRows(slots, instances, recent = []) {
|
|
2289
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2290
|
+
for (const info of instances.values()) {
|
|
2291
|
+
const aggregate = byType.get(info.type);
|
|
2292
|
+
if (aggregate === void 0) {
|
|
2293
|
+
byType.set(info.type, {
|
|
2294
|
+
latest: info,
|
|
2295
|
+
count: 1
|
|
2296
|
+
});
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
aggregate.count += 1;
|
|
2300
|
+
if (info.spawnedAt >= aggregate.latest.spawnedAt) aggregate.latest = info;
|
|
2301
|
+
}
|
|
2302
|
+
const rows = [];
|
|
2303
|
+
const slottedTypes = /* @__PURE__ */ new Set();
|
|
2304
|
+
for (const slot of slots) {
|
|
2305
|
+
slottedTypes.add(slot.type);
|
|
2306
|
+
const latest = byType.get(slot.type)?.latest;
|
|
2307
|
+
const live = slot.count > 0 || slot.status !== "idle";
|
|
2308
|
+
if (!live && latest === void 0) continue;
|
|
2309
|
+
const current = (slot.agentId === void 0 ? void 0 : instances.get(slot.agentId)) ?? latest;
|
|
2310
|
+
const status = live ? slot.status : latest?.outcome ?? "idle";
|
|
2311
|
+
const chain = current === void 0 ? {
|
|
2312
|
+
names: [],
|
|
2313
|
+
truncated: false
|
|
2314
|
+
} : buildAncestorChain(current, instances);
|
|
2315
|
+
const row = {
|
|
2316
|
+
key: slot.type,
|
|
2317
|
+
type: slot.type,
|
|
2318
|
+
status,
|
|
2319
|
+
live,
|
|
2320
|
+
count: slot.count,
|
|
2321
|
+
detail: slot.detail,
|
|
2322
|
+
lastActivityAt: slot.lastActivityAt,
|
|
2323
|
+
description: current?.description,
|
|
2324
|
+
instanceId: live ? slot.agentId : void 0,
|
|
2325
|
+
source: current === void 0 ? {
|
|
2326
|
+
kind: "unknown",
|
|
2327
|
+
name: void 0,
|
|
2328
|
+
description: void 0
|
|
2329
|
+
} : resolveSource(current, instances),
|
|
2330
|
+
ancestors: chain.names,
|
|
2331
|
+
chainTruncated: chain.truncated
|
|
2332
|
+
};
|
|
2333
|
+
const sortAt = live ? slot.lastActivityAt : latest?.endedAt ?? latest?.spawnedAt ?? slot.lastActivityAt;
|
|
2334
|
+
rows.push({
|
|
2335
|
+
row,
|
|
2336
|
+
sortAt
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
for (const [type, aggregate] of byType) {
|
|
2340
|
+
if (slottedTypes.has(type)) continue;
|
|
2341
|
+
const { latest } = aggregate;
|
|
2342
|
+
const activityAt = latest.endedAt ?? latest.spawnedAt;
|
|
2343
|
+
const chain = buildAncestorChain(latest, instances);
|
|
2344
|
+
rows.push({
|
|
2345
|
+
row: {
|
|
2346
|
+
key: type,
|
|
2347
|
+
type,
|
|
2348
|
+
status: latest.outcome ?? "idle",
|
|
2349
|
+
live: false,
|
|
2350
|
+
count: aggregate.count,
|
|
2351
|
+
detail: void 0,
|
|
2352
|
+
lastActivityAt: activityAt,
|
|
2353
|
+
description: latest.description,
|
|
2354
|
+
instanceId: void 0,
|
|
2355
|
+
source: resolveSource(latest, instances),
|
|
2356
|
+
ancestors: chain.names,
|
|
2357
|
+
chainTruncated: chain.truncated
|
|
2358
|
+
},
|
|
2359
|
+
sortAt: activityAt
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
const coveredTypes = new Set(rows.map((entry) => entry.row.key));
|
|
2363
|
+
const archivedByType = /* @__PURE__ */ new Map();
|
|
2364
|
+
for (const row of recent) archivedByType.set(row.key, row);
|
|
2365
|
+
for (const row of archivedByType.values()) {
|
|
2366
|
+
if (coveredTypes.has(row.key)) continue;
|
|
2367
|
+
rows.push({
|
|
2368
|
+
row,
|
|
2369
|
+
sortAt: row.lastActivityAt
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
rows.sort((a, b) => {
|
|
2373
|
+
if (a.row.live !== b.row.live) return a.row.live ? -1 : 1;
|
|
2374
|
+
return b.sortAt - a.sortAt;
|
|
2375
|
+
});
|
|
2376
|
+
return rows.map((entry) => entry.row);
|
|
2377
|
+
}
|
|
2378
|
+
//#endregion
|
|
2174
2379
|
//#region src/tui/utils/background-task-status.ts
|
|
2175
2380
|
const MAX_DETAIL_LENGTH = 240;
|
|
2176
2381
|
function truncate(value) {
|
|
@@ -2505,6 +2710,17 @@ function subtractTokenUsage(current, previous) {
|
|
|
2505
2710
|
function isZeroUsage(u) {
|
|
2506
2711
|
return u.inputOther === 0 && u.inputCacheRead === 0 && u.inputCacheCreation === 0 && u.output === 0;
|
|
2507
2712
|
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Whether a status event may patch main-agent state (RLM mode / recursion cap).
|
|
2715
|
+
* Only the main agent's own events qualify; subagent status updates carry their
|
|
2716
|
+
* own agentId and must never overwrite the main display. Events without an
|
|
2717
|
+
* agentId (older emitters) are accepted for backward compatibility.
|
|
2718
|
+
*
|
|
2719
|
+
* Exported (pure) so the filtering rule is unit-testable without a TUI.
|
|
2720
|
+
*/
|
|
2721
|
+
function isMainAgentStatusEvent(event) {
|
|
2722
|
+
return event.agentId === void 0 || event.agentId === "main";
|
|
2723
|
+
}
|
|
2508
2724
|
var SessionEventHandler = class {
|
|
2509
2725
|
host;
|
|
2510
2726
|
constructor(host) {
|
|
@@ -2531,6 +2747,22 @@ var SessionEventHandler = class {
|
|
|
2531
2747
|
subagentInfo = /* @__PURE__ */ new Map();
|
|
2532
2748
|
/** Sidebar subagent slot state machine (see utils/subagent-slots.ts). */
|
|
2533
2749
|
subagentSlots = new SubagentSlots();
|
|
2750
|
+
/**
|
|
2751
|
+
* Per-instance provenance registry (see utils/subagent-instances.ts), read
|
|
2752
|
+
* by the /tasks browser's Agents view. Captured at spawn because the two
|
|
2753
|
+
* clues stop being available later: `subagentInfo.parentToolCallId` is
|
|
2754
|
+
* rewritten to a WolfPack routing id for WolfPack children, and the
|
|
2755
|
+
* spawning tool call leaves the active set as soon as it returns.
|
|
2756
|
+
*/
|
|
2757
|
+
subagentInstances = /* @__PURE__ */ new Map();
|
|
2758
|
+
/**
|
|
2759
|
+
* Archive ring behind {@link getRecentSubagentInstances}: the last derived
|
|
2760
|
+
* Agents-view row of every closed instance, oldest first, capped at
|
|
2761
|
+
* {@link MAX_RECENT_SUBAGENT_INSTANCES}. Closing an instance releases its
|
|
2762
|
+
* registry record, so this is what keeps recently-ended agents visible in the
|
|
2763
|
+
* /tasks Agents view instead of letting the registry grow for the session.
|
|
2764
|
+
*/
|
|
2765
|
+
recentSubagentInstances = [];
|
|
2534
2766
|
/** Tool callId of the in-flight UpdateGoal(complete) (judging), if any. */
|
|
2535
2767
|
goalJudgeCallId;
|
|
2536
2768
|
/** Rolling first-token latencies of real requests, feeding the sidebar Hub
|
|
@@ -2551,6 +2783,8 @@ var SessionEventHandler = class {
|
|
|
2551
2783
|
this.backgroundTaskTranscriptedTerminal.clear();
|
|
2552
2784
|
this.subagentInfo.clear();
|
|
2553
2785
|
this.subagentSlots.reset();
|
|
2786
|
+
this.subagentInstances.clear();
|
|
2787
|
+
this.recentSubagentInstances.length = 0;
|
|
2554
2788
|
this.renderedSkillActivationIds.clear();
|
|
2555
2789
|
this.renderedMcpServerStatusKeys.clear();
|
|
2556
2790
|
this.stopAllMcpServerStatusSpinners();
|
|
@@ -2733,6 +2967,50 @@ var SessionEventHandler = class {
|
|
|
2733
2967
|
getSubagentSlots() {
|
|
2734
2968
|
return this.subagentSlots.getSlots();
|
|
2735
2969
|
}
|
|
2970
|
+
/** Read-only snapshot of the per-instance provenance registry (Agents view
|
|
2971
|
+
* of the /tasks browser). Keyed by subagent agentId. */
|
|
2972
|
+
getSubagentInstances() {
|
|
2973
|
+
return this.subagentInstances;
|
|
2974
|
+
}
|
|
2975
|
+
/** Read-only snapshot of the archive ring: the last derived Agents-view row
|
|
2976
|
+
* of each closed instance, oldest first, bounded to
|
|
2977
|
+
* {@link MAX_RECENT_SUBAGENT_INSTANCES}. */
|
|
2978
|
+
getRecentSubagentInstances() {
|
|
2979
|
+
return this.recentSubagentInstances;
|
|
2980
|
+
}
|
|
2981
|
+
/** Remember (or refresh, on resume) one spawned instance. The spawning tool
|
|
2982
|
+
* call is resolved here, while it is still in flight. */
|
|
2983
|
+
rememberSubagentInstance(event, fallbackParentAgentId) {
|
|
2984
|
+
const previous = this.subagentInstances.get(event.subagentId);
|
|
2985
|
+
const parentToolCall = this.host.streamingUI.getActiveToolCall(event.parentToolCallId);
|
|
2986
|
+
this.subagentInstances.set(event.subagentId, createSubagentInstanceInfo({
|
|
2987
|
+
...event,
|
|
2988
|
+
parentAgentId: event.parentAgentId ?? fallbackParentAgentId
|
|
2989
|
+
}, parentToolCall, previous?.spawnedAt));
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Close an instance record on subagent.completed / subagent.failed: archive
|
|
2993
|
+
* its last display row in the ring, then release the registry record — the
|
|
2994
|
+
* registry is reset per session, not kept per spawned agent.
|
|
2995
|
+
*
|
|
2996
|
+
* The archived row is derived by the very builder the view uses (over the
|
|
2997
|
+
* registry snapshot that still contains this instance and its terminal
|
|
2998
|
+
* outcome), so it is field-identical to what the view rendered while the
|
|
2999
|
+
* record was live; the deletion below is what makes that derivation the last
|
|
3000
|
+
* chance to produce it.
|
|
3001
|
+
*/
|
|
3002
|
+
closeSubagentInstance(agentId, outcome) {
|
|
3003
|
+
const info = this.subagentInstances.get(agentId);
|
|
3004
|
+
if (info === void 0) return;
|
|
3005
|
+
const ended = withSubagentInstanceEnded(info, outcome);
|
|
3006
|
+
const snapshot = new Map([...this.subagentInstances, [agentId, ended]]);
|
|
3007
|
+
const row = buildAgentRows(this.subagentSlots.getSlots(), snapshot).find((candidate) => candidate.key === ended.type);
|
|
3008
|
+
if (row !== void 0) {
|
|
3009
|
+
this.recentSubagentInstances.push(row);
|
|
3010
|
+
if (this.recentSubagentInstances.length > 50) this.recentSubagentInstances.shift();
|
|
3011
|
+
}
|
|
3012
|
+
this.subagentInstances.delete(agentId);
|
|
3013
|
+
}
|
|
2736
3014
|
/** Keep the last few first-token latencies, per provider. Steps without a
|
|
2737
3015
|
* measured latency (no stream, cached turn) are ignored rather than counted
|
|
2738
3016
|
* as a zero. */
|
|
@@ -2773,6 +3051,7 @@ var SessionEventHandler = class {
|
|
|
2773
3051
|
switch (event.type) {
|
|
2774
3052
|
case "subagent.spawned":
|
|
2775
3053
|
this.subagentSlots.onSpawned(event.subagentId, event.subagentName, event.description);
|
|
3054
|
+
this.rememberSubagentInstance(event, agentId);
|
|
2776
3055
|
break;
|
|
2777
3056
|
case "subagent.started":
|
|
2778
3057
|
this.subagentSlots.onStarted(event.subagentId);
|
|
@@ -2780,6 +3059,7 @@ var SessionEventHandler = class {
|
|
|
2780
3059
|
case "subagent.completed":
|
|
2781
3060
|
case "subagent.failed":
|
|
2782
3061
|
this.subagentSlots.onTerminated(event.subagentId);
|
|
3062
|
+
this.closeSubagentInstance(event.subagentId, event.type === "subagent.failed" ? "failed" : "completed");
|
|
2783
3063
|
break;
|
|
2784
3064
|
case "tool.call.started":
|
|
2785
3065
|
if (event.name === "ContactParent") {
|
|
@@ -3170,7 +3450,11 @@ var SessionEventHandler = class {
|
|
|
3170
3450
|
if (event.maxContextTokens !== void 0) patch.maxContextTokens = event.maxContextTokens;
|
|
3171
3451
|
if (event.planMode !== void 0) patch.planMode = event.planMode ? event.planStrategy === "fusion" ? "fusionplan" : "plan" : "off";
|
|
3172
3452
|
if (event.wolfpackMode !== void 0) patch.wolfpackMode = event.wolfpackMode;
|
|
3173
|
-
if (event.rlmEnabled !== void 0
|
|
3453
|
+
if (event.rlmEnabled !== void 0 || event.rlmMaxDepth !== void 0) {
|
|
3454
|
+
const fromMain = isMainAgentStatusEvent(event);
|
|
3455
|
+
if (fromMain && event.rlmEnabled !== void 0) patch.rlmEnabled = event.rlmEnabled;
|
|
3456
|
+
if (fromMain && event.rlmMaxDepth !== void 0) patch.rlmMaxDepth = event.rlmMaxDepth;
|
|
3457
|
+
}
|
|
3174
3458
|
if (event.permission !== void 0) patch.permissionMode = event.permission;
|
|
3175
3459
|
if (Object.keys(patch).length > 0) this.host.setAppState(patch);
|
|
3176
3460
|
}
|
|
@@ -3422,6 +3706,7 @@ var SessionEventHandler = class {
|
|
|
3422
3706
|
}
|
|
3423
3707
|
handleSubagentSpawned(event) {
|
|
3424
3708
|
this.subagentSlots.onSpawned(event.subagentId, event.subagentName, event.description);
|
|
3709
|
+
this.rememberSubagentInstance(event);
|
|
3425
3710
|
const { streamingUI } = this.host;
|
|
3426
3711
|
if (streamingUI.getActiveToolCall(event.parentToolCallId)?.name === "WolfPack") {
|
|
3427
3712
|
const routingId = event.subagentId;
|
|
@@ -3495,6 +3780,7 @@ var SessionEventHandler = class {
|
|
|
3495
3780
|
}
|
|
3496
3781
|
handleSubagentCompleted(event) {
|
|
3497
3782
|
this.subagentSlots.onTerminated(event.subagentId);
|
|
3783
|
+
this.closeSubagentInstance(event.subagentId, "completed");
|
|
3498
3784
|
this.recordSubagentUsage(event.subagentId, void 0, event.usage);
|
|
3499
3785
|
const { streamingUI } = this.host;
|
|
3500
3786
|
const backgroundMeta = this.backgroundAgentMetadata.get(event.subagentId);
|
|
@@ -3525,6 +3811,7 @@ var SessionEventHandler = class {
|
|
|
3525
3811
|
}
|
|
3526
3812
|
handleSubagentFailed(event) {
|
|
3527
3813
|
this.subagentSlots.onTerminated(event.subagentId);
|
|
3814
|
+
this.closeSubagentInstance(event.subagentId, "failed");
|
|
3528
3815
|
this.recordSubagentUsage(event.subagentId, void 0, event.usage);
|
|
3529
3816
|
const { streamingUI } = this.host;
|
|
3530
3817
|
const backgroundMeta = this.backgroundAgentMetadata.get(event.subagentId);
|
|
@@ -4380,6 +4667,89 @@ function isControlCharacter(ch) {
|
|
|
4380
4667
|
const code = ch.codePointAt(0) ?? 0;
|
|
4381
4668
|
return code >= 0 && code <= 31 || code >= 127 && code <= 159;
|
|
4382
4669
|
}
|
|
4670
|
+
/** Max characters kept across head + tail of the preview combined. */
|
|
4671
|
+
const UI_OUTPUT_MAX_CHARS = 8e3;
|
|
4672
|
+
/** Same marker string as agent-core's `result-builder.ts` TRUNCATION_MARKER. */
|
|
4673
|
+
const TRUNCATION_MARKER = "[...truncated]";
|
|
4674
|
+
function elisionMarker(elided) {
|
|
4675
|
+
return `[…${String(elided)} lines elided…]`;
|
|
4676
|
+
}
|
|
4677
|
+
/**
|
|
4678
|
+
* Bounds one tool-output string to a head + tail preview:
|
|
4679
|
+
* - at most {@link UI_OUTPUT_MAX_HEAD_LINES} lines from the start and
|
|
4680
|
+
* {@link UI_OUTPUT_MAX_TAIL_LINES} lines from the end (the middle is
|
|
4681
|
+
* replaced with `[…N lines elided…]`);
|
|
4682
|
+
* - the kept head + tail text stays within {@link UI_OUTPUT_MAX_CHARS} chars,
|
|
4683
|
+
* dropping whole lines first and hard-slicing a lone oversized line last
|
|
4684
|
+
* (marked `[...truncated]`).
|
|
4685
|
+
*
|
|
4686
|
+
* Returns the input unchanged when it already fits both budgets.
|
|
4687
|
+
*/
|
|
4688
|
+
function boundToolOutput(output) {
|
|
4689
|
+
const lines = output.split("\n");
|
|
4690
|
+
const totalLines = lines.length;
|
|
4691
|
+
const overflowLines = totalLines > 80;
|
|
4692
|
+
const overflowChars = output.length > UI_OUTPUT_MAX_CHARS;
|
|
4693
|
+
if (!overflowLines && !overflowChars) return output;
|
|
4694
|
+
const head = lines.slice(0, 40);
|
|
4695
|
+
const tail = overflowLines ? lines.slice(totalLines - 40) : lines.slice(40);
|
|
4696
|
+
let elided = totalLines - head.length - tail.length;
|
|
4697
|
+
const contentBudget = UI_OUTPUT_MAX_CHARS - 2;
|
|
4698
|
+
let headText = head.join("\n");
|
|
4699
|
+
let tailText = tail.join("\n");
|
|
4700
|
+
while (headText.length + tailText.length > contentBudget) {
|
|
4701
|
+
if (head.length > 1 && (tail.length <= 1 || headText.length >= tailText.length)) {
|
|
4702
|
+
head.pop();
|
|
4703
|
+
headText = head.join("\n");
|
|
4704
|
+
elided += 1;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
if (tail.length > 1) {
|
|
4708
|
+
tail.shift();
|
|
4709
|
+
tailText = tail.join("\n");
|
|
4710
|
+
elided += 1;
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4713
|
+
break;
|
|
4714
|
+
}
|
|
4715
|
+
if (headText.length + tailText.length > contentBudget) {
|
|
4716
|
+
const tailKeep = Math.min(tailText.length, Math.floor(contentBudget / 2));
|
|
4717
|
+
const headKeep = Math.min(headText.length, contentBudget - tailKeep);
|
|
4718
|
+
headText = headText.slice(0, headKeep);
|
|
4719
|
+
tailText = tailText.slice(tailText.length - tailKeep);
|
|
4720
|
+
}
|
|
4721
|
+
const marker = elided > 0 ? elisionMarker(elided) : TRUNCATION_MARKER;
|
|
4722
|
+
const parts = [];
|
|
4723
|
+
if (headText.length > 0) parts.push(headText);
|
|
4724
|
+
parts.push(marker);
|
|
4725
|
+
if (tailText.length > 0) parts.push(tailText);
|
|
4726
|
+
return parts.join("\n");
|
|
4727
|
+
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Returns a transcript entry safe to keep in `state.transcriptEntries`:
|
|
4730
|
+
* tool-group entries get their `result.output` replaced with the bounded
|
|
4731
|
+
* preview (a copy — message, brief, titles, args and every other field stay
|
|
4732
|
+
* as they are). Non-tool entries — and tool entries already within budget —
|
|
4733
|
+
* come back untouched (no copy).
|
|
4734
|
+
*/
|
|
4735
|
+
function boundEntryForUi(entry) {
|
|
4736
|
+
const toolCallData = entry.toolCallData;
|
|
4737
|
+
if (toolCallData === void 0) return entry;
|
|
4738
|
+
const result = toolCallData.result;
|
|
4739
|
+
if (result === void 0) return entry;
|
|
4740
|
+
const boundedOutput = boundToolOutput(result.output);
|
|
4741
|
+
if (boundedOutput === result.output) return entry;
|
|
4742
|
+
return {
|
|
4743
|
+
...entry,
|
|
4744
|
+
toolCallData: {
|
|
4745
|
+
...toolCallData,
|
|
4746
|
+
result: {
|
|
4747
|
+
...result,
|
|
4748
|
+
output: boundedOutput
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4383
4753
|
//#endregion
|
|
4384
4754
|
//#region src/tui/controllers/streaming-ui.ts
|
|
4385
4755
|
var StreamingUIController = class StreamingUIController {
|
|
@@ -4874,9 +5244,12 @@ var StreamingUIController = class StreamingUIController {
|
|
|
4874
5244
|
}
|
|
4875
5245
|
/**
|
|
4876
5246
|
* Advance the smooth-render cursor by this frame's character budget.
|
|
4877
|
-
*
|
|
4878
|
-
*
|
|
4879
|
-
*
|
|
5247
|
+
* The base budget clamps to the measured arrival rate (slow models never
|
|
5248
|
+
* fall behind) with a floor so the stream never freezes and a ceiling so one
|
|
5249
|
+
* oversized burst is spread across frames. Once the backlog passes
|
|
5250
|
+
* SMOOTH_CATCHUP_THRESHOLD, a catch-up budget (backlog / DIVISOR, capped at
|
|
5251
|
+
* MAX_PER_FRAME) raises it so a fast stream converges to a bounded lag
|
|
5252
|
+
* instead of accumulating until finalize.
|
|
4880
5253
|
*/
|
|
4881
5254
|
advanceAssistantShown() {
|
|
4882
5255
|
const draft = this._assistantDraft;
|
|
@@ -4884,7 +5257,9 @@ var StreamingUIController = class StreamingUIController {
|
|
|
4884
5257
|
const measured = getSharedSpeedTracker().getSpeed();
|
|
4885
5258
|
const tokPerSec = measured > 0 ? measured : 50;
|
|
4886
5259
|
const requested = charsForTokenBudget(draft.slice(this._shownAssistantLength), tokPerSec * (50 / 1e3));
|
|
4887
|
-
|
|
5260
|
+
let budget = Math.min(25, Math.max(1, requested));
|
|
5261
|
+
const backlog = draft.length - this._shownAssistantLength;
|
|
5262
|
+
if (backlog > 250) budget = Math.max(budget, Math.min(Math.ceil(backlog / 8), 300));
|
|
4888
5263
|
this._shownAssistantLength = Math.min(draft.length, this._shownAssistantLength + budget);
|
|
4889
5264
|
return this._shownAssistantLength;
|
|
4890
5265
|
}
|
|
@@ -5134,7 +5509,16 @@ var StreamingUIController = class StreamingUIController {
|
|
|
5134
5509
|
this.host.transcriptController.unmarkPending(tc);
|
|
5135
5510
|
this._pendingToolComponents.delete(toolCallId);
|
|
5136
5511
|
const entry = this.host.state.transcriptEntries.find((e) => e.toolCallData?.id === toolCallId);
|
|
5137
|
-
if (entry?.toolCallData !== void 0)
|
|
5512
|
+
if (entry?.toolCallData !== void 0) {
|
|
5513
|
+
const bounded = boundEntryForUi({
|
|
5514
|
+
...entry,
|
|
5515
|
+
toolCallData: {
|
|
5516
|
+
...entry.toolCallData,
|
|
5517
|
+
result
|
|
5518
|
+
}
|
|
5519
|
+
});
|
|
5520
|
+
entry.toolCallData.result = bounded.toolCallData?.result ?? result;
|
|
5521
|
+
}
|
|
5138
5522
|
this.host.transcriptController.commit();
|
|
5139
5523
|
state.ui.requestRender();
|
|
5140
5524
|
return;
|
|
@@ -5533,6 +5917,64 @@ function statusColor(colors, status) {
|
|
|
5533
5917
|
function isTerminal(status) {
|
|
5534
5918
|
return status === "completed" || status === "failed" || status === "killed" || status === "lost";
|
|
5535
5919
|
}
|
|
5920
|
+
/** Width of the agent-name column in the Agents list (leaves room for the
|
|
5921
|
+
* status word, the instance count and an activity hint on narrow panes). */
|
|
5922
|
+
const AGENT_NAME_COLS = 12;
|
|
5923
|
+
/** Localized word per agent row status: the six live slot states plus the two
|
|
5924
|
+
* terminal outcomes the instance registry can report. */
|
|
5925
|
+
function agentStatusText(status) {
|
|
5926
|
+
switch (status) {
|
|
5927
|
+
case "idle": return t("sidebar.agent_idle");
|
|
5928
|
+
case "working": return t("sidebar.agent_working");
|
|
5929
|
+
case "outputting": return t("sidebar.agent_outputting");
|
|
5930
|
+
case "messaging": return t("sidebar.agent_messaging");
|
|
5931
|
+
case "reworking": return t("sidebar.agent_reworking");
|
|
5932
|
+
case "requesting": return t("sidebar.agent_requesting");
|
|
5933
|
+
case "completed": return t("taskbrowser.agents_completed");
|
|
5934
|
+
case "failed": return t("taskbrowser.agents_failed");
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
function agentStatusColor(colors, status) {
|
|
5938
|
+
switch (status) {
|
|
5939
|
+
case "working":
|
|
5940
|
+
case "outputting": return colors.success;
|
|
5941
|
+
case "messaging": return colors.accent;
|
|
5942
|
+
case "reworking":
|
|
5943
|
+
case "requesting": return colors.warning;
|
|
5944
|
+
case "completed": return colors.textMuted;
|
|
5945
|
+
case "failed": return colors.error;
|
|
5946
|
+
case "idle": return colors.textDim;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
/** "Who triggered this agent" line. Each kind names its own evidence, and a
|
|
5950
|
+
* missing clue is voiced as unknown instead of guessed. */
|
|
5951
|
+
function formatAgentSource(source) {
|
|
5952
|
+
switch (source.kind) {
|
|
5953
|
+
case "tool":
|
|
5954
|
+
if (source.name === void 0) return t("taskbrowser.agent_source_unknown");
|
|
5955
|
+
return source.description === void 0 ? source.name : `${source.name} · ${source.description}`;
|
|
5956
|
+
case "agent": {
|
|
5957
|
+
const via = source.name === void 0 ? t("taskbrowser.agent_source_unknown") : t("taskbrowser.agent_source_via", { name: source.name });
|
|
5958
|
+
return source.description === void 0 ? via : `${via} · ${source.description}`;
|
|
5959
|
+
}
|
|
5960
|
+
case "rlm": {
|
|
5961
|
+
const rlm = t("taskbrowser.agent_source_rlm");
|
|
5962
|
+
return source.name === void 0 ? rlm : `${rlm} · ${t("taskbrowser.agent_source_via", { name: source.name })}`;
|
|
5963
|
+
}
|
|
5964
|
+
case "main": return t("taskbrowser.agent_source_main");
|
|
5965
|
+
case "unknown": return t("taskbrowser.agent_source_unknown");
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
/** Full spawn chain, root first: `main → researcher → coder`, `…` when the
|
|
5969
|
+
* walk stopped early (missing link / cycle / depth cap). */
|
|
5970
|
+
function formatAgentChain(agent) {
|
|
5971
|
+
const parents = [...agent.ancestors].toReversed();
|
|
5972
|
+
return `${[
|
|
5973
|
+
t("taskbrowser.agent_main"),
|
|
5974
|
+
...parents,
|
|
5975
|
+
agent.type
|
|
5976
|
+
].join(" → ")}${agent.chainTruncated ? " → …" : ""}`;
|
|
5977
|
+
}
|
|
5536
5978
|
function formatRelativeTime$2(ts) {
|
|
5537
5979
|
if (ts === null || ts === void 0 || !Number.isFinite(ts) || ts <= 0) return "";
|
|
5538
5980
|
const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
|
|
@@ -5560,8 +6002,22 @@ function fitExactly$1(line, width) {
|
|
|
5560
6002
|
return padToWidth$1(s, width);
|
|
5561
6003
|
}
|
|
5562
6004
|
function visibleTasks(tasks, filter) {
|
|
5563
|
-
if (filter === "
|
|
5564
|
-
return tasks
|
|
6005
|
+
if (filter === "active") return tasks.filter((t) => !isTerminal(t.status));
|
|
6006
|
+
return [...tasks];
|
|
6007
|
+
}
|
|
6008
|
+
/**
|
|
6009
|
+
* Keep `selectedIndex` inside the window `scrollOffset … scrollOffset+rows-1`,
|
|
6010
|
+
* clamped to the list bounds. Shared by the task list and the agent list.
|
|
6011
|
+
*/
|
|
6012
|
+
function clampListScroll(selectedIndex, scrollOffset, total, visibleRows) {
|
|
6013
|
+
if (visibleRows <= 0) return 0;
|
|
6014
|
+
let next = scrollOffset;
|
|
6015
|
+
if (selectedIndex < next) next = selectedIndex;
|
|
6016
|
+
else if (selectedIndex >= next + visibleRows) next = selectedIndex - visibleRows + 1;
|
|
6017
|
+
const maxScroll = Math.max(0, total - visibleRows);
|
|
6018
|
+
if (next < 0) next = 0;
|
|
6019
|
+
if (next > maxScroll) next = maxScroll;
|
|
6020
|
+
return next;
|
|
5565
6021
|
}
|
|
5566
6022
|
function compareTasks(a, b) {
|
|
5567
6023
|
const aTerminal = isTerminal(a.status);
|
|
@@ -5601,6 +6057,12 @@ var TasksBrowserApp = class extends Container {
|
|
|
5601
6057
|
sortedVisible;
|
|
5602
6058
|
selectedIndex = 0;
|
|
5603
6059
|
listScroll = 0;
|
|
6060
|
+
/** Agents view selection — component-local: agent rows are read-only, so
|
|
6061
|
+
* nothing needs to travel back to the controller. `selectedAgentKey` is the
|
|
6062
|
+
* anchor: rows re-sort as statuses change, the index must not decide. */
|
|
6063
|
+
agentIndex = 0;
|
|
6064
|
+
agentScroll = 0;
|
|
6065
|
+
selectedAgentKey = void 0;
|
|
5604
6066
|
pendingStopTaskId = void 0;
|
|
5605
6067
|
pendingStopTimer = void 0;
|
|
5606
6068
|
constructor(props, terminal) {
|
|
@@ -5614,6 +6076,7 @@ var TasksBrowserApp = class extends Container {
|
|
|
5614
6076
|
this.props = next;
|
|
5615
6077
|
this.sortedVisible = visibleTasks(next.tasks, next.filter).toSorted(compareTasks);
|
|
5616
6078
|
this.syncSelectionFromProps();
|
|
6079
|
+
this.syncAgentSelectionFromProps();
|
|
5617
6080
|
if (this.pendingStopTaskId !== void 0) {
|
|
5618
6081
|
const task = next.tasks.find((t) => t.taskId === this.pendingStopTaskId);
|
|
5619
6082
|
if (task === void 0 || isTerminal(task.status)) this.clearPendingStop();
|
|
@@ -5635,6 +6098,34 @@ var TasksBrowserApp = class extends Container {
|
|
|
5635
6098
|
}
|
|
5636
6099
|
if (this.selectedIndex >= this.sortedVisible.length) this.selectedIndex = this.sortedVisible.length - 1;
|
|
5637
6100
|
}
|
|
6101
|
+
/** Keep the agents selection pinned to the same slot type across refreshes
|
|
6102
|
+
* (rows re-sort as statuses change); fall back to clamping. */
|
|
6103
|
+
syncAgentSelectionFromProps() {
|
|
6104
|
+
const agents = this.props.agents;
|
|
6105
|
+
if (agents.length === 0) {
|
|
6106
|
+
this.agentIndex = 0;
|
|
6107
|
+
this.agentScroll = 0;
|
|
6108
|
+
this.selectedAgentKey = void 0;
|
|
6109
|
+
return;
|
|
6110
|
+
}
|
|
6111
|
+
if (this.selectedAgentKey !== void 0) {
|
|
6112
|
+
const byKey = agents.findIndex((agent) => agent.key === this.selectedAgentKey);
|
|
6113
|
+
if (byKey !== -1) {
|
|
6114
|
+
this.agentIndex = byKey;
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
if (this.agentIndex >= agents.length) this.agentIndex = agents.length - 1;
|
|
6119
|
+
this.selectedAgentKey = agents[this.agentIndex]?.key;
|
|
6120
|
+
}
|
|
6121
|
+
/** Move the agents cursor (rows are read-only: no controller round-trip). */
|
|
6122
|
+
selectAgent(index) {
|
|
6123
|
+
const agents = this.props.agents;
|
|
6124
|
+
if (agents.length === 0) return;
|
|
6125
|
+
this.agentIndex = Math.min(agents.length - 1, Math.max(0, index));
|
|
6126
|
+
this.selectedAgentKey = agents[this.agentIndex]?.key;
|
|
6127
|
+
this.invalidate();
|
|
6128
|
+
}
|
|
5638
6129
|
clearPendingStop() {
|
|
5639
6130
|
this.pendingStopTaskId = void 0;
|
|
5640
6131
|
if (this.pendingStopTimer !== void 0) {
|
|
@@ -5642,12 +6133,21 @@ var TasksBrowserApp = class extends Container {
|
|
|
5642
6133
|
this.pendingStopTimer = void 0;
|
|
5643
6134
|
}
|
|
5644
6135
|
}
|
|
6136
|
+
/**
|
|
6137
|
+
* Component-local teardown, called by the controller's close() before the
|
|
6138
|
+
* component is swapped out: the pending-stop confirm timer must not
|
|
6139
|
+
* outlive the browser.
|
|
6140
|
+
*/
|
|
6141
|
+
close() {
|
|
6142
|
+
this.clearPendingStop();
|
|
6143
|
+
}
|
|
5645
6144
|
emitSelect() {
|
|
5646
6145
|
const task = this.sortedVisible[this.selectedIndex];
|
|
5647
6146
|
if (task) this.props.onSelect(task.taskId);
|
|
5648
6147
|
}
|
|
5649
6148
|
handleInput(data) {
|
|
5650
6149
|
const k = printableChar(data);
|
|
6150
|
+
const agentsMode = this.props.filter === "agents";
|
|
5651
6151
|
if (this.pendingStopTaskId !== void 0) {
|
|
5652
6152
|
if (k === "y" || k === "Y") {
|
|
5653
6153
|
const taskId = this.pendingStopTaskId;
|
|
@@ -5665,6 +6165,10 @@ var TasksBrowserApp = class extends Container {
|
|
|
5665
6165
|
return;
|
|
5666
6166
|
}
|
|
5667
6167
|
if (matchesKey(data, Key.up) || k === "k") {
|
|
6168
|
+
if (agentsMode) {
|
|
6169
|
+
this.selectAgent(this.agentIndex - 1);
|
|
6170
|
+
return;
|
|
6171
|
+
}
|
|
5668
6172
|
if (this.sortedVisible.length === 0) return;
|
|
5669
6173
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
5670
6174
|
this.emitSelect();
|
|
@@ -5672,6 +6176,10 @@ var TasksBrowserApp = class extends Container {
|
|
|
5672
6176
|
return;
|
|
5673
6177
|
}
|
|
5674
6178
|
if (matchesKey(data, Key.down) || k === "j") {
|
|
6179
|
+
if (agentsMode) {
|
|
6180
|
+
this.selectAgent(this.agentIndex + 1);
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
5675
6183
|
if (this.sortedVisible.length === 0) return;
|
|
5676
6184
|
this.selectedIndex = Math.min(this.sortedVisible.length - 1, this.selectedIndex + 1);
|
|
5677
6185
|
this.emitSelect();
|
|
@@ -5686,6 +6194,7 @@ var TasksBrowserApp = class extends Container {
|
|
|
5686
6194
|
this.props.onRefresh();
|
|
5687
6195
|
return;
|
|
5688
6196
|
}
|
|
6197
|
+
if (agentsMode) return;
|
|
5689
6198
|
if (k === "s" || k === "S") {
|
|
5690
6199
|
const task = this.sortedVisible[this.selectedIndex];
|
|
5691
6200
|
if (task === void 0) return;
|
|
@@ -5729,15 +6238,26 @@ var TasksBrowserApp = class extends Container {
|
|
|
5729
6238
|
renderHeader(width) {
|
|
5730
6239
|
const colors = this.props.colors;
|
|
5731
6240
|
const title = chalk.hex(colors.primary).bold(" TASK BROWSER ");
|
|
5732
|
-
const
|
|
5733
|
-
const
|
|
5734
|
-
const
|
|
5735
|
-
if (
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
6241
|
+
const filterLabel = this.props.filter === "all" ? "ALL" : this.props.filter === "active" ? "ACTIVE" : "AGENTS";
|
|
6242
|
+
const filterText = chalk.hex(colors.textMuted)(` filter=${filterLabel} `);
|
|
6243
|
+
const segments = [];
|
|
6244
|
+
if (this.props.filter === "agents") {
|
|
6245
|
+
const live = this.props.agents.filter((agent) => agent.live).length;
|
|
6246
|
+
const ended = this.props.agents.length - live;
|
|
6247
|
+
if (live > 0) segments.push(chalk.hex(colors.success)(` ${String(live)} ${t("taskbrowser.agents_live")} `));
|
|
6248
|
+
if (ended > 0) segments.push(chalk.hex(colors.textDim)(` ${String(ended)} ${t("taskbrowser.agents_ended")} `));
|
|
6249
|
+
if (this.props.agents.length === 0) segments.push(chalk.hex(colors.textMuted)(` 0 ${t("taskbrowser.agents")} `));
|
|
6250
|
+
} else {
|
|
6251
|
+
const counts = countByStatus(this.props.tasks);
|
|
6252
|
+
if (counts.running > 0) segments.push(chalk.hex(colors.success)(` ${String(counts.running)} ${t("taskbrowser.running")} `));
|
|
6253
|
+
if (counts.awaiting > 0) segments.push(chalk.hex(colors.warning)(` ${String(counts.awaiting)} ${t("taskbrowser.awaiting")} `));
|
|
6254
|
+
if (counts.completed > 0) segments.push(chalk.hex(colors.textDim)(` ${String(counts.completed)} ${t("taskbrowser.completed")} `));
|
|
6255
|
+
if (counts.terminalFailed > 0) segments.push(chalk.hex(colors.error)(` ${String(counts.terminalFailed)} ${t("taskbrowser.interrupted")} `));
|
|
6256
|
+
segments.push(chalk.hex(colors.textMuted)(` ${String(this.props.tasks.length)} ${t("taskbrowser.total")} `));
|
|
6257
|
+
const liveAgents = this.props.agents.filter((agent) => agent.live).length;
|
|
6258
|
+
if (liveAgents > 0) segments.push(chalk.hex(colors.success)(` ${String(liveAgents)} ${t("taskbrowser.agents")} `));
|
|
6259
|
+
}
|
|
6260
|
+
return fitExactly$1(title + filterText + segments.join(""), width);
|
|
5741
6261
|
}
|
|
5742
6262
|
renderFooter(width) {
|
|
5743
6263
|
const colors = this.props.colors;
|
|
@@ -5747,14 +6267,19 @@ var TasksBrowserApp = class extends Container {
|
|
|
5747
6267
|
const warn = (text) => chalk.hex(colors.warning).bold(text);
|
|
5748
6268
|
return fitExactly$1(` ${warn(t("taskbrowser.stop"))} ${chalk.hex(colors.text)(this.pendingStopTaskId)}? ${key("Y")} ${dim(t("taskbrowser.confirm"))} ${key("N")} ${dim(t("taskbrowser.cancel"))} `, width);
|
|
5749
6269
|
}
|
|
5750
|
-
const left = [
|
|
6270
|
+
const left = (this.props.filter === "agents" ? [
|
|
6271
|
+
` ${key("↑↓")} ${dim(t("taskbrowser.select"))}`,
|
|
6272
|
+
`${key("Tab")} ${dim(t("taskbrowser.filter"))}`,
|
|
6273
|
+
`${key("R")} ${dim(t("taskbrowser.refresh"))}`,
|
|
6274
|
+
`${key("Q/Esc")} ${dim(t("taskbrowser.exit"))} `
|
|
6275
|
+
] : [
|
|
5751
6276
|
` ${key("↑↓")} ${dim(t("taskbrowser.select"))}`,
|
|
5752
6277
|
`${key("Enter/O")} ${dim(t("taskbrowser.output"))}`,
|
|
5753
6278
|
`${key("S")} ${dim(t("taskbrowser.stop_action"))}`,
|
|
5754
6279
|
`${key("R")} ${dim(t("taskbrowser.refresh"))}`,
|
|
5755
6280
|
`${key("Tab")} ${dim(t("taskbrowser.filter"))}`,
|
|
5756
6281
|
`${key("Q/Esc")} ${dim(t("taskbrowser.exit"))} `
|
|
5757
|
-
].join(" ");
|
|
6282
|
+
]).join(" ");
|
|
5758
6283
|
const flash = this.props.flashMessage;
|
|
5759
6284
|
if (flash !== void 0 && flash.length > 0) {
|
|
5760
6285
|
const flashStyled = chalk.hex(colors.warning)(` ${flash} `);
|
|
@@ -5793,6 +6318,7 @@ var TasksBrowserApp = class extends Container {
|
|
|
5793
6318
|
return lines;
|
|
5794
6319
|
}
|
|
5795
6320
|
renderListFrame(width, height) {
|
|
6321
|
+
if (this.props.filter === "agents") return this.renderAgentsFrame(width, height);
|
|
5796
6322
|
const title = `Tasks [${this.props.filter}]`;
|
|
5797
6323
|
const innerHeight = Math.max(0, height - 2);
|
|
5798
6324
|
if (this.sortedVisible.length === 0) {
|
|
@@ -5813,6 +6339,43 @@ var TasksBrowserApp = class extends Container {
|
|
|
5813
6339
|
while (lines.length < innerHeight) lines.push("");
|
|
5814
6340
|
return this.renderFrame(title, lines, width, height);
|
|
5815
6341
|
}
|
|
6342
|
+
/** Left pane, `agents` filter: one row per subagent type with history (live
|
|
6343
|
+
* ones first), including types whose slot the cap pushed out and that only
|
|
6344
|
+
* the instance registry knows; read-only — the detail pane carries the full
|
|
6345
|
+
* provenance. */
|
|
6346
|
+
renderAgentsFrame(width, height) {
|
|
6347
|
+
const colors = this.props.colors;
|
|
6348
|
+
const agents = this.props.agents;
|
|
6349
|
+
const innerHeight = Math.max(0, height - 2);
|
|
6350
|
+
const title = `${t("taskbrowser.agents_title")} [${String(agents.length)}]`;
|
|
6351
|
+
if (agents.length === 0) {
|
|
6352
|
+
const lines = [chalk.hex(colors.textMuted)(t("taskbrowser.agents_empty"))];
|
|
6353
|
+
while (lines.length < innerHeight) lines.push("");
|
|
6354
|
+
return this.renderFrame(title, lines, width, height);
|
|
6355
|
+
}
|
|
6356
|
+
this.agentScroll = clampListScroll(this.agentIndex, this.agentScroll, agents.length, innerHeight);
|
|
6357
|
+
const start = this.agentScroll;
|
|
6358
|
+
const window = agents.slice(start, start + innerHeight);
|
|
6359
|
+
const innerWidth = width - 2;
|
|
6360
|
+
const lines = [];
|
|
6361
|
+
for (const [vi, agent] of window.entries()) {
|
|
6362
|
+
const index = start + vi;
|
|
6363
|
+
lines.push(this.renderAgentRow(agent, index === this.agentIndex, innerWidth));
|
|
6364
|
+
}
|
|
6365
|
+
while (lines.length < innerHeight) lines.push("");
|
|
6366
|
+
return this.renderFrame(title, lines, width, height);
|
|
6367
|
+
}
|
|
6368
|
+
renderAgentRow(agent, selected, innerWidth) {
|
|
6369
|
+
const colors = this.props.colors;
|
|
6370
|
+
const pointer = selected ? "> " : " ";
|
|
6371
|
+
const head = `${chalk.hex(selected ? colors.primary : colors.textDim)(pointer)}${chalk.hex(agentStatusColor(colors, agent.status))(agent.status === "idle" ? "○" : "●")} ${chalk.hex(selected ? colors.primary : colors.textStrong)(padToWidth$1(truncateToWidth(agent.type, AGENT_NAME_COLS, ELLIPSIS$3), AGENT_NAME_COLS))} ${chalk.hex(agentStatusColor(colors, agent.status))(agentStatusText(agent.status))}${agent.count > 1 ? chalk.hex(colors.textMuted)(` ×${String(agent.count)}`) : ""}`;
|
|
6372
|
+
const budget = innerWidth - visibleWidth(head) - 1;
|
|
6373
|
+
if (budget < 6) return fitExactly$1(head, innerWidth);
|
|
6374
|
+
const hint = singleLine$2(agent.detail ?? agent.description ?? "");
|
|
6375
|
+
if (hint.length === 0) return fitExactly$1(head, innerWidth);
|
|
6376
|
+
const trimmed = truncateToWidth(hint, budget, ELLIPSIS$3);
|
|
6377
|
+
return fitExactly$1(`${head} ${chalk.hex(colors.textDim)(trimmed)}`, innerWidth);
|
|
6378
|
+
}
|
|
5816
6379
|
renderListRow(task, selected, innerWidth) {
|
|
5817
6380
|
const colors = this.props.colors;
|
|
5818
6381
|
const pointer = selected ? "> " : " ";
|
|
@@ -5829,15 +6392,7 @@ var TasksBrowserApp = class extends Container {
|
|
|
5829
6392
|
return fitExactly$1(`${prefix} ${chalk.hex(colors.text)(desc)}`, innerWidth);
|
|
5830
6393
|
}
|
|
5831
6394
|
adjustScroll(visibleRows) {
|
|
5832
|
-
|
|
5833
|
-
this.listScroll = 0;
|
|
5834
|
-
return;
|
|
5835
|
-
}
|
|
5836
|
-
if (this.selectedIndex < this.listScroll) this.listScroll = this.selectedIndex;
|
|
5837
|
-
else if (this.selectedIndex >= this.listScroll + visibleRows) this.listScroll = this.selectedIndex - visibleRows + 1;
|
|
5838
|
-
const maxScroll = Math.max(0, this.sortedVisible.length - visibleRows);
|
|
5839
|
-
if (this.listScroll < 0) this.listScroll = 0;
|
|
5840
|
-
if (this.listScroll > maxScroll) this.listScroll = maxScroll;
|
|
6395
|
+
this.listScroll = clampListScroll(this.selectedIndex, this.listScroll, this.sortedVisible.length, visibleRows);
|
|
5841
6396
|
}
|
|
5842
6397
|
renderRightStack(width, height) {
|
|
5843
6398
|
const detailHeight = Math.max(8, Math.min(Math.floor(height * .4), height - 5));
|
|
@@ -5845,6 +6400,7 @@ var TasksBrowserApp = class extends Container {
|
|
|
5845
6400
|
return [...this.renderDetailFrame(width, detailHeight), ...this.renderPreviewFrame(width, previewHeight)];
|
|
5846
6401
|
}
|
|
5847
6402
|
renderDetailFrame(width, height) {
|
|
6403
|
+
if (this.props.filter === "agents") return this.renderAgentDetailFrame(width, height);
|
|
5848
6404
|
const colors = this.props.colors;
|
|
5849
6405
|
const innerHeight = Math.max(0, height - 2);
|
|
5850
6406
|
const task = this.sortedVisible[this.selectedIndex];
|
|
@@ -5871,9 +6427,43 @@ var TasksBrowserApp = class extends Container {
|
|
|
5871
6427
|
while (lines.length < innerHeight) lines.push("");
|
|
5872
6428
|
return this.renderFrame(t("taskbrowser.detail"), lines, width, height);
|
|
5873
6429
|
}
|
|
6430
|
+
/** Right-top pane, `agents` filter: provenance of the selected agent —
|
|
6431
|
+
* type, live status, spawn description, trigger source, parent chain,
|
|
6432
|
+
* latest activity and the current instance id. */
|
|
6433
|
+
renderAgentDetailFrame(width, height) {
|
|
6434
|
+
const colors = this.props.colors;
|
|
6435
|
+
const innerHeight = Math.max(0, height - 2);
|
|
6436
|
+
const agent = this.props.agents[this.agentIndex];
|
|
6437
|
+
if (agent === void 0) {
|
|
6438
|
+
const lines = [chalk.hex(colors.textMuted)(t("taskbrowser.agents_empty"))];
|
|
6439
|
+
while (lines.length < innerHeight) lines.push("");
|
|
6440
|
+
return this.renderFrame(t("taskbrowser.agent_detail"), lines, width, height);
|
|
6441
|
+
}
|
|
6442
|
+
const label = (text) => chalk.hex(colors.textMuted)(text.padEnd(14));
|
|
6443
|
+
const value = (text) => chalk.hex(colors.text)(text);
|
|
6444
|
+
const countSuffix = agent.count > 1 ? chalk.hex(colors.textMuted)(` ×${String(agent.count)}`) : "";
|
|
6445
|
+
const lines = [
|
|
6446
|
+
`${label(t("taskbrowser.agent_type"))}${value(agent.type)}`,
|
|
6447
|
+
`${label(t("taskbrowser.status"))}${chalk.hex(agentStatusColor(colors, agent.status))(agentStatusText(agent.status))}${countSuffix}`,
|
|
6448
|
+
`${label(t("taskbrowser.agent_source"))}${value(singleLine$2(formatAgentSource(agent.source)))}`,
|
|
6449
|
+
`${label(t("taskbrowser.agent_chain"))}${chalk.hex(colors.textMuted)(formatAgentChain(agent))}`
|
|
6450
|
+
];
|
|
6451
|
+
if (agent.description !== void 0 && agent.description.length > 0) lines.push(`${label(t("taskbrowser.description"))}${value(singleLine$2(agent.description))}`);
|
|
6452
|
+
if (agent.detail !== void 0 && agent.detail.length > 0) lines.push(`${label(t("taskbrowser.agent_activity"))}${value(singleLine$2(agent.detail))}`);
|
|
6453
|
+
const when = formatRelativeTime$2(agent.lastActivityAt);
|
|
6454
|
+
if (when.length > 0) lines.push(`${label(t("taskbrowser.time"))}${chalk.hex(colors.textMuted)(when)}`);
|
|
6455
|
+
if (agent.instanceId !== void 0) lines.push(`${label(t("taskbrowser.agent_instance"))}${chalk.hex(colors.textMuted)(agent.instanceId)}`);
|
|
6456
|
+
while (lines.length < innerHeight) lines.push("");
|
|
6457
|
+
return this.renderFrame(t("taskbrowser.agent_detail"), lines, width, height);
|
|
6458
|
+
}
|
|
5874
6459
|
renderPreviewFrame(width, height) {
|
|
5875
6460
|
const colors = this.props.colors;
|
|
5876
6461
|
const innerHeight = Math.max(0, height - 2);
|
|
6462
|
+
if (this.props.filter === "agents") {
|
|
6463
|
+
const lines = [chalk.hex(colors.textMuted)(t("taskbrowser.agents_no_output"))];
|
|
6464
|
+
while (lines.length < innerHeight) lines.push("");
|
|
6465
|
+
return this.renderFrame("Preview Output", lines, width, height);
|
|
6466
|
+
}
|
|
5877
6467
|
if (this.sortedVisible[this.selectedIndex] === void 0) {
|
|
5878
6468
|
const lines = [chalk.hex(colors.textMuted)("No task selected.")];
|
|
5879
6469
|
while (lines.length < innerHeight) lines.push("");
|
|
@@ -5922,6 +6512,7 @@ var TasksBrowserController = class {
|
|
|
5922
6512
|
const selectedTaskId = this.pickInitialSelection(tasks, filter);
|
|
5923
6513
|
const component = new TasksBrowserApp({
|
|
5924
6514
|
tasks,
|
|
6515
|
+
agents: this.snapshotAgentRows(),
|
|
5925
6516
|
filter,
|
|
5926
6517
|
selectedTaskId,
|
|
5927
6518
|
tailOutput: void 0,
|
|
@@ -5959,6 +6550,7 @@ var TasksBrowserController = class {
|
|
|
5959
6550
|
if (browser.viewer !== void 0) this.closeOutputViewer();
|
|
5960
6551
|
if (browser.pollTimer !== void 0) clearInterval(browser.pollTimer);
|
|
5961
6552
|
if (browser.flashTimer !== void 0) clearTimeout(browser.flashTimer);
|
|
6553
|
+
browser.component.close();
|
|
5962
6554
|
state.ui.setLayoutRoot(browser.savedLayoutRoot);
|
|
5963
6555
|
this.host.setTasksBrowser(void 0);
|
|
5964
6556
|
state.ui.setFocus(state.editor);
|
|
@@ -6029,6 +6621,7 @@ var TasksBrowserController = class {
|
|
|
6029
6621
|
if (browser === void 0) return;
|
|
6030
6622
|
browser.component.setProps({
|
|
6031
6623
|
tasks,
|
|
6624
|
+
agents: this.snapshotAgentRows(),
|
|
6032
6625
|
filter: browser.filter,
|
|
6033
6626
|
selectedTaskId: browser.selectedTaskId,
|
|
6034
6627
|
tailOutput: browser.tailOutput,
|
|
@@ -6039,6 +6632,13 @@ var TasksBrowserController = class {
|
|
|
6039
6632
|
});
|
|
6040
6633
|
this.host.state.ui.requestRender();
|
|
6041
6634
|
}
|
|
6635
|
+
/** Agents view rows: the live slot state machine (authoritative status)
|
|
6636
|
+
* enriched with the per-instance provenance registry, plus the archive ring
|
|
6637
|
+
* for agents whose registry record has already been released. Rebuilt on
|
|
6638
|
+
* every refresh/repaint — all three sources are in-memory snapshots. */
|
|
6639
|
+
snapshotAgentRows() {
|
|
6640
|
+
return buildAgentRows(this.host.subagentSlots, this.host.subagentInstances, this.host.recentSubagentInstances);
|
|
6641
|
+
}
|
|
6042
6642
|
buildCallbacks() {
|
|
6043
6643
|
return {
|
|
6044
6644
|
onSelect: (taskId) => {
|
|
@@ -6077,7 +6677,7 @@ var TasksBrowserController = class {
|
|
|
6077
6677
|
handleToggleFilter() {
|
|
6078
6678
|
const browser = this.host.state.tasksBrowser;
|
|
6079
6679
|
if (browser === void 0) return;
|
|
6080
|
-
browser.filter = browser.filter === "all" ? "active" : "all";
|
|
6680
|
+
browser.filter = browser.filter === "all" ? "active" : browser.filter === "active" ? "agents" : "all";
|
|
6081
6681
|
this.repaint();
|
|
6082
6682
|
}
|
|
6083
6683
|
handleRefresh() {
|
|
@@ -6346,6 +6946,28 @@ function basenameLike(raw) {
|
|
|
6346
6946
|
return raw.split(/[\\/]/).filter((part) => part.length > 0).at(-1) ?? raw;
|
|
6347
6947
|
}
|
|
6348
6948
|
//#endregion
|
|
6949
|
+
//#region src/tui/utils/collapse-stub.ts
|
|
6950
|
+
/**
|
|
6951
|
+
* The transcript bounds its memory in two places, and both stand in for the
|
|
6952
|
+
* rows they fold away with the same summary line: the shadow entry array
|
|
6953
|
+
* (`transcript-controller`) folds its oldest entries into one stub entry, and
|
|
6954
|
+
* the committed render tree (`committed-transcript`) folds its oldest child
|
|
6955
|
+
* components into one stub row. One definition of the wording keeps the two
|
|
6956
|
+
* folds reading alike — and lets the array fold read its own count back when
|
|
6957
|
+
* it folds again.
|
|
6958
|
+
*/
|
|
6959
|
+
/** Matches {@link formatCollapsedEntries} output, capturing the count. */
|
|
6960
|
+
const COLLAPSE_STUB_PATTERN = /^…\((\d+) earlier entries collapsed\)$/;
|
|
6961
|
+
/** Summary line that stands in for `count` folded-away entries. */
|
|
6962
|
+
function formatCollapsedEntries(count) {
|
|
6963
|
+
return `…(${String(count)} earlier entries collapsed)`;
|
|
6964
|
+
}
|
|
6965
|
+
/** How many entries a summary line stands in for; 0 when `text` is not one. */
|
|
6966
|
+
function parseCollapsedEntries(text) {
|
|
6967
|
+
const digits = COLLAPSE_STUB_PATTERN.exec(text)?.[1];
|
|
6968
|
+
return digits === void 0 ? 0 : Number(digits);
|
|
6969
|
+
}
|
|
6970
|
+
//#endregion
|
|
6349
6971
|
//#region src/tui/components/transcript/committed-transcript.ts
|
|
6350
6972
|
var CommittedMessageComponent = class {
|
|
6351
6973
|
entry;
|
|
@@ -6415,6 +7037,9 @@ var CommittedTranscriptComponent = class extends CachedContainer {
|
|
|
6415
7037
|
header;
|
|
6416
7038
|
colors;
|
|
6417
7039
|
committedCount = 0;
|
|
7040
|
+
/** Rows the retention cap has folded into the summary stub. */
|
|
7041
|
+
collapsedCount = 0;
|
|
7042
|
+
collapseStub;
|
|
6418
7043
|
constructor(colors) {
|
|
6419
7044
|
super();
|
|
6420
7045
|
this.colors = colors;
|
|
@@ -6424,6 +7049,12 @@ var CommittedTranscriptComponent = class extends CachedContainer {
|
|
|
6424
7049
|
getCount() {
|
|
6425
7050
|
return this.committedCount;
|
|
6426
7051
|
}
|
|
7052
|
+
/** Rows the tree folded into its summary stub. They still count toward
|
|
7053
|
+
* {@link getCount()}: folding changes what is on screen, never what was
|
|
7054
|
+
* committed. */
|
|
7055
|
+
getCollapsedCount() {
|
|
7056
|
+
return this.collapsedCount;
|
|
7057
|
+
}
|
|
6427
7058
|
setCount(count) {
|
|
6428
7059
|
this.committedCount = count;
|
|
6429
7060
|
if (count === 0) this.header.setText("");
|
|
@@ -6432,11 +7063,46 @@ var CommittedTranscriptComponent = class extends CachedContainer {
|
|
|
6432
7063
|
}
|
|
6433
7064
|
appendEntry(entry, colors) {
|
|
6434
7065
|
this.addChild(new CommittedMessageComponent(entry, colors));
|
|
7066
|
+
this.enforceRetentionCap();
|
|
7067
|
+
}
|
|
7068
|
+
/**
|
|
7069
|
+
* Holds the tree at its retention point: the entry children it renders —
|
|
7070
|
+
* the summary stub included — never exceed MAX_COMMITTED_ENTRIES. Past that
|
|
7071
|
+
* the oldest rows fold into the stub, the same semantics as the entry-array
|
|
7072
|
+
* cap, so the child list (and the render cache every child carries) stops
|
|
7073
|
+
* growing with the session. The stub takes one slot of the budget, so the
|
|
7074
|
+
* fold lands on stub + (MAX - 1) rows: the cap is the actually retained set.
|
|
7075
|
+
*/
|
|
7076
|
+
enforceRetentionCap() {
|
|
7077
|
+
if (this.children.length - 1 <= 500) return;
|
|
7078
|
+
let stub = this.collapseStub;
|
|
7079
|
+
if (stub === void 0) {
|
|
7080
|
+
stub = new Text("", 0, 0);
|
|
7081
|
+
this.collapseStub = stub;
|
|
7082
|
+
this.children.splice(1, 0, stub);
|
|
7083
|
+
}
|
|
7084
|
+
const fold = this.children.length - 2 - 499;
|
|
7085
|
+
this.children.splice(2, fold);
|
|
7086
|
+
this.collapsedCount += fold;
|
|
7087
|
+
stub.setText(` ${chalk.hex(this.colors.textDim)(formatCollapsedEntries(this.collapsedCount))}`);
|
|
6435
7088
|
this.invalidate();
|
|
6436
7089
|
}
|
|
6437
7090
|
};
|
|
6438
|
-
|
|
6439
|
-
|
|
7091
|
+
/** Id of the aggregate stub that stands in for the folded oldest entries. */
|
|
7092
|
+
const COLLAPSE_STUB_ID = "transcript-collapse-stub";
|
|
7093
|
+
function makeCollapseStub(count) {
|
|
7094
|
+
return {
|
|
7095
|
+
id: COLLAPSE_STUB_ID,
|
|
7096
|
+
kind: "status",
|
|
7097
|
+
renderMode: "plain",
|
|
7098
|
+
content: formatCollapsedEntries(count)
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
/** Entries an existing stub already stands in for (0 when it is not one). */
|
|
7102
|
+
function stubCount(entry) {
|
|
7103
|
+
if (entry === void 0 || entry.id !== COLLAPSE_STUB_ID) return 0;
|
|
7104
|
+
return parseCollapsedEntries(entry.content);
|
|
7105
|
+
}
|
|
6440
7106
|
var TranscriptController = class TranscriptController {
|
|
6441
7107
|
host;
|
|
6442
7108
|
welcomeComponent;
|
|
@@ -6446,6 +7112,17 @@ var TranscriptController = class TranscriptController {
|
|
|
6446
7112
|
* ticker of the card that is still claiming the task is running. */
|
|
6447
7113
|
liveNoticesByTrackingId = /* @__PURE__ */ new Map();
|
|
6448
7114
|
pendingComponents = /* @__PURE__ */ new Set();
|
|
7115
|
+
/** Monotonic count of composition changes to `state.transcriptEntries`:
|
|
7116
|
+
* +1 per ingest and +1 per cap fold. Aggregates that memoize over the entry
|
|
7117
|
+
* list key on this — the fold holds the array length at the cap while
|
|
7118
|
+
* replacing the oldest rows, so a length-only key can go stale. */
|
|
7119
|
+
ingestCount = 0;
|
|
7120
|
+
/** Contributions of the rows the entry cap has already folded out of the
|
|
7121
|
+
* array. Kept separate from the rows still held, so aggregates that must
|
|
7122
|
+
* stay monotonic over a long session (the sidebar's session stats) can add
|
|
7123
|
+
* them back — see {@link getFoldedEntryCounts}. */
|
|
7124
|
+
foldedTurns = 0;
|
|
7125
|
+
foldedToolCalls = 0;
|
|
6449
7126
|
/** Max live transcript children before the oldest are folded into the
|
|
6450
7127
|
* committed single-line summary. Overridable via SCREAM_TRANSCRIPT_LIVE_LIMIT
|
|
6451
7128
|
* (mirrors a commit-fold approach for bounding ultra-long sessions). */
|
|
@@ -6578,12 +7255,55 @@ var TranscriptController = class TranscriptController {
|
|
|
6578
7255
|
default: return null;
|
|
6579
7256
|
}
|
|
6580
7257
|
}
|
|
7258
|
+
/**
|
|
7259
|
+
* The transcript's single bounded ingress: every entry reaches
|
|
7260
|
+
* `state.transcriptEntries` through here — `appendEntry` for rows that mount
|
|
7261
|
+
* their own component, and `ScreamTUI.pushTranscriptEntry` for the live
|
|
7262
|
+
* streaming rows that already own one. One place applies the UI preview
|
|
7263
|
+
* bound (`boundEntryForUi`) and the entry-count cap, so no producer can grow
|
|
7264
|
+
* the array without bound, whichever path it comes in on.
|
|
7265
|
+
*
|
|
7266
|
+
* Returns the entry as stored: a bounded copy when the bound rewrote it,
|
|
7267
|
+
* otherwise the very entry handed in — identity matters to callers that keep
|
|
7268
|
+
* mutating a live draft (assistant text, thinking) after pushing it.
|
|
7269
|
+
*/
|
|
7270
|
+
ingestEntry(entry) {
|
|
7271
|
+
const boundedEntry = boundEntryForUi(entry);
|
|
7272
|
+
this.ingestCount += 1;
|
|
7273
|
+
this.host.state.transcriptEntries.push(boundedEntry);
|
|
7274
|
+
this.enforceEntryCap();
|
|
7275
|
+
return boundedEntry;
|
|
7276
|
+
}
|
|
7277
|
+
/**
|
|
7278
|
+
* Monotonic count of transcript composition changes: one per ingest, plus
|
|
7279
|
+
* one per cap fold. Consumers that memoize aggregates over
|
|
7280
|
+
* `state.transcriptEntries` (the sidebar session stats) key on this instead
|
|
7281
|
+
* of `length` alone, because a fold lands the array back on the same length
|
|
7282
|
+
* while replacing its oldest rows.
|
|
7283
|
+
*/
|
|
7284
|
+
getIngestCount() {
|
|
7285
|
+
return this.ingestCount;
|
|
7286
|
+
}
|
|
7287
|
+
/**
|
|
7288
|
+
* What the entry cap has already folded out of `state.transcriptEntries`:
|
|
7289
|
+
* user turns and real tool calls (compression markers ride kind 'tool_call'
|
|
7290
|
+
* but are not invocations — same rule as the retained-row scan). Aggregates
|
|
7291
|
+
* that must be monotonic over a long session — the sidebar's session stats —
|
|
7292
|
+
* add these to the rows the array still holds, because the array stops
|
|
7293
|
+
* holding the oldest ones at the cap.
|
|
7294
|
+
*/
|
|
7295
|
+
getFoldedEntryCounts() {
|
|
7296
|
+
return {
|
|
7297
|
+
turns: this.foldedTurns,
|
|
7298
|
+
toolCalls: this.foldedToolCalls
|
|
7299
|
+
};
|
|
7300
|
+
}
|
|
6581
7301
|
appendEntry(entry) {
|
|
6582
|
-
this.
|
|
6583
|
-
const component = this.createComponent(
|
|
7302
|
+
const boundedEntry = this.ingestEntry(entry);
|
|
7303
|
+
const component = this.createComponent(boundedEntry);
|
|
6584
7304
|
if (component) {
|
|
6585
|
-
this.liveComponentToEntry.set(component,
|
|
6586
|
-
const status =
|
|
7305
|
+
this.liveComponentToEntry.set(component, boundedEntry);
|
|
7306
|
+
const status = boundedEntry.backgroundAgentStatus;
|
|
6587
7307
|
if (status !== void 0 && component instanceof BackgroundAgentStatusComponent) {
|
|
6588
7308
|
const trackingId = status.trackingId;
|
|
6589
7309
|
if (trackingId !== void 0) if (status.phase === "started") this.liveNoticesByTrackingId.set(trackingId, component);
|
|
@@ -6597,6 +7317,24 @@ var TranscriptController = class TranscriptController {
|
|
|
6597
7317
|
}
|
|
6598
7318
|
return component ?? null;
|
|
6599
7319
|
}
|
|
7320
|
+
/**
|
|
7321
|
+
* Keeps `state.transcriptEntries` within {@link MAX_TRANSCRIPT_ENTRIES} by
|
|
7322
|
+
* folding the oldest rows into one aggregate stub. A stub already at the head
|
|
7323
|
+
* keeps that slot and accumulates the count, so the array stays bounded no
|
|
7324
|
+
* matter how long the session runs.
|
|
7325
|
+
*/
|
|
7326
|
+
enforceEntryCap() {
|
|
7327
|
+
const entries = this.host.state.transcriptEntries;
|
|
7328
|
+
if (entries.length <= 500) return;
|
|
7329
|
+
const head = entries[0];
|
|
7330
|
+
const isStub = head !== void 0 && head.id === COLLAPSE_STUB_ID;
|
|
7331
|
+
const dropped = entries.splice(0, entries.length - 500 + 1);
|
|
7332
|
+
for (const entry of dropped) if (entry.kind === "user") this.foldedTurns += 1;
|
|
7333
|
+
else if (entry.kind === "tool_call" && entry.compactionData === void 0) this.foldedToolCalls += 1;
|
|
7334
|
+
const folded = dropped.length - (isStub ? 1 : 0);
|
|
7335
|
+
entries.unshift(makeCollapseStub(stubCount(head) + folded));
|
|
7336
|
+
this.ingestCount += 1;
|
|
7337
|
+
}
|
|
6600
7338
|
/** Append a suffix to the given turn's final assistant message last line.
|
|
6601
7339
|
* Used to place the turn elapsed marker flush against the last character
|
|
6602
7340
|
* of the assistant's reply. Turn-scoped so a tool-only turn never stamps
|
|
@@ -6651,6 +7389,8 @@ var TranscriptController = class TranscriptController {
|
|
|
6651
7389
|
streamingUI.endActivityGroup();
|
|
6652
7390
|
streamingUI.flushPendingApprovals();
|
|
6653
7391
|
state.transcriptEntries = [];
|
|
7392
|
+
this.foldedTurns = 0;
|
|
7393
|
+
this.foldedToolCalls = 0;
|
|
6654
7394
|
streamingUI.disposeActiveCompactionBlock();
|
|
6655
7395
|
streamingUI.resetLiveText();
|
|
6656
7396
|
streamingUI.resetToolUi();
|
|
@@ -7964,7 +8704,8 @@ var LifecycleController = class LifecycleController {
|
|
|
7964
8704
|
hubProbe = createHubProbe();
|
|
7965
8705
|
sidebarGitCacheWorkDir = "";
|
|
7966
8706
|
/** Memo for the session-stats aggregation: recomputed only when the
|
|
7967
|
-
* transcript length, compaction count or usage object
|
|
8707
|
+
* transcript's ingest counter/length, compaction count or usage object
|
|
8708
|
+
* changes. */
|
|
7968
8709
|
sidebarStatsMemo;
|
|
7969
8710
|
static MEMORY_IDLE_MS = 900 * 1e3;
|
|
7970
8711
|
static MEMORY_COUNTDOWN_MS = 15 * 1e3;
|
|
@@ -8231,16 +8972,20 @@ var LifecycleController = class LifecycleController {
|
|
|
8231
8972
|
}
|
|
8232
8973
|
readSidebarSessionStats(appState) {
|
|
8233
8974
|
const entries = this.host.state.transcriptEntries;
|
|
8975
|
+
const ingestCount = this.host.transcriptController.getIngestCount();
|
|
8234
8976
|
const usage = appState.sessionUsage;
|
|
8235
8977
|
const subagentUsage = appState.subagentUsage;
|
|
8236
8978
|
const memo = this.sidebarStatsMemo;
|
|
8237
|
-
if (memo !== void 0 && memo.length === entries.length && memo.compactions === appState.autoCompactionCount && memo.usage === usage && memo.subagentUsage === subagentUsage && memo.apiCalls === appState.sessionApiCalls) return memo.stats;
|
|
8979
|
+
if (memo !== void 0 && memo.ingestCount === ingestCount && memo.length === entries.length && memo.compactions === appState.autoCompactionCount && memo.usage === usage && memo.subagentUsage === subagentUsage && memo.apiCalls === appState.sessionApiCalls) return memo.stats;
|
|
8238
8980
|
let turns = 0;
|
|
8239
8981
|
let toolCalls = 0;
|
|
8240
8982
|
for (const entry of entries) {
|
|
8241
8983
|
if (entry.kind === "user") turns += 1;
|
|
8242
8984
|
if (entry.kind === "tool_call" && entry.compactionData === void 0) toolCalls += 1;
|
|
8243
8985
|
}
|
|
8986
|
+
const foldedAway = this.host.transcriptController.getFoldedEntryCounts();
|
|
8987
|
+
turns += foldedAway.turns;
|
|
8988
|
+
toolCalls += foldedAway.toolCalls;
|
|
8244
8989
|
let subagentOutput = 0;
|
|
8245
8990
|
for (const sub of Object.values(subagentUsage)) subagentOutput += sub.output;
|
|
8246
8991
|
const stats = {
|
|
@@ -8254,6 +8999,7 @@ var LifecycleController = class LifecycleController {
|
|
|
8254
8999
|
tokensOutput: usage.output + subagentOutput
|
|
8255
9000
|
};
|
|
8256
9001
|
this.sidebarStatsMemo = {
|
|
9002
|
+
ingestCount,
|
|
8257
9003
|
length: entries.length,
|
|
8258
9004
|
compactions: appState.autoCompactionCount,
|
|
8259
9005
|
usage,
|
|
@@ -11159,7 +11905,7 @@ var SessionManager$1 = class {
|
|
|
11159
11905
|
this.host = host;
|
|
11160
11906
|
}
|
|
11161
11907
|
async init(options) {
|
|
11162
|
-
const { startup, workDir } = options;
|
|
11908
|
+
const { startup, workDir, sessionsPrefetch } = options;
|
|
11163
11909
|
let session;
|
|
11164
11910
|
let shouldReplayHistory = false;
|
|
11165
11911
|
const isResumeStartup = startup.sessionFlag !== void 0 || startup.continueLast;
|
|
@@ -11175,12 +11921,9 @@ var SessionManager$1 = class {
|
|
|
11175
11921
|
throw new Error("picker");
|
|
11176
11922
|
}
|
|
11177
11923
|
if (startup.sessionFlag !== void 0) {
|
|
11178
|
-
const target =
|
|
11179
|
-
sessionId: startup.sessionFlag,
|
|
11180
|
-
workDir
|
|
11181
|
-
}))[0];
|
|
11924
|
+
const target = await this.findSessionById(startup.sessionFlag, workDir);
|
|
11182
11925
|
if (target === void 0) throw new Error(t("session.not_found", { sessionId: startup.sessionFlag }));
|
|
11183
|
-
if (target.workDir !== workDir) throw new Error(t("session.wrong_dir", {
|
|
11926
|
+
if (normalizeWorkDir(target.workDir) !== normalizeWorkDir(workDir)) throw new Error(t("session.wrong_dir", {
|
|
11184
11927
|
sessionId: startup.sessionFlag,
|
|
11185
11928
|
workDir: target.workDir
|
|
11186
11929
|
}));
|
|
@@ -11203,7 +11946,7 @@ var SessionManager$1 = class {
|
|
|
11203
11946
|
await this.syncRuntimeState(session);
|
|
11204
11947
|
if (startup.wolfpack && !isResumeStartup) await session.setWolfpackMode(true);
|
|
11205
11948
|
this.host.state.startupState = "ready";
|
|
11206
|
-
await this.pruneEmptySessions(workDir, session.id);
|
|
11949
|
+
await this.pruneEmptySessions(workDir, session.id, sessionsPrefetch);
|
|
11207
11950
|
this.host.sessionEventHandler.startSubscription();
|
|
11208
11951
|
return {
|
|
11209
11952
|
session,
|
|
@@ -11216,21 +11959,46 @@ var SessionManager$1 = class {
|
|
|
11216
11959
|
* one-off empty session shells. The session about to be used is skipped, as
|
|
11217
11960
|
* are archived sessions, any session that produced a prompt or a title, and
|
|
11218
11961
|
* any session touched within the grace window (protects a fresh empty
|
|
11219
|
-
* session being used from another terminal).
|
|
11220
|
-
*
|
|
11962
|
+
* session being used from another terminal). The prefetched listing, when
|
|
11963
|
+
* present, only enumerates candidates: the snapshot can lag the splash
|
|
11964
|
+
* duration, so every candidate is re-checked against a live id lookup and is
|
|
11965
|
+
* deleted only if it still looks prunable right now (a session that woke up
|
|
11966
|
+
* meanwhile — prompt, rename, new activity — stays). Best-effort: cleanup
|
|
11967
|
+
* failures never block startup.
|
|
11221
11968
|
*/
|
|
11222
|
-
async pruneEmptySessions(workDir, currentSessionId) {
|
|
11969
|
+
async pruneEmptySessions(workDir, currentSessionId, prefetch) {
|
|
11223
11970
|
try {
|
|
11224
11971
|
const now = Date.now();
|
|
11225
|
-
const
|
|
11226
|
-
|
|
11972
|
+
const normalizedWorkDir = normalizeWorkDir(workDir);
|
|
11973
|
+
const candidates = prefetch === void 0 ? await this.host.harness.listSessions({ workDir }) : await prefetch;
|
|
11974
|
+
for (const summary of candidates) {
|
|
11227
11975
|
if (summary.id === currentSessionId) continue;
|
|
11228
|
-
if (isPrunableEmptySession(summary, now))
|
|
11976
|
+
if (!isPrunableEmptySession(summary, now)) continue;
|
|
11977
|
+
const latest = (await this.host.harness.listSessions({
|
|
11978
|
+
sessionId: summary.id,
|
|
11979
|
+
workDir
|
|
11980
|
+
}))[0];
|
|
11981
|
+
if (latest === void 0) continue;
|
|
11982
|
+
if (normalizeWorkDir(latest.workDir) !== normalizedWorkDir) continue;
|
|
11983
|
+
if (!isPrunableEmptySession(latest, Date.now())) continue;
|
|
11984
|
+
await this.host.harness.deleteSession(summary.id).catch(() => {});
|
|
11229
11985
|
}
|
|
11230
11986
|
} catch (error) {
|
|
11231
11987
|
this.host.showStatus(`Session cleanup skipped: ${String(error)}`);
|
|
11232
11988
|
}
|
|
11233
11989
|
}
|
|
11990
|
+
/**
|
|
11991
|
+
* Resolve the `--session <id>` target from a live listing (resume decisions
|
|
11992
|
+
* never read the prefetched snapshot). The id-filtered form is the only call
|
|
11993
|
+
* that can tell "no such session" apart from "session lives in another
|
|
11994
|
+
* workDir".
|
|
11995
|
+
*/
|
|
11996
|
+
async findSessionById(sessionId, workDir) {
|
|
11997
|
+
return (await this.host.harness.listSessions({
|
|
11998
|
+
sessionId,
|
|
11999
|
+
workDir
|
|
12000
|
+
}))[0];
|
|
12001
|
+
}
|
|
11234
12002
|
async setSession(session) {
|
|
11235
12003
|
await this.unloadCurrentSession("switching session")?.close({ extractMemories: false });
|
|
11236
12004
|
this.host.session = session;
|
|
@@ -11247,6 +12015,7 @@ var SessionManager$1 = class {
|
|
|
11247
12015
|
planMode: status.planMode ? status.planStrategy === "fusion" ? "fusionplan" : "plan" : "off",
|
|
11248
12016
|
wolfpackMode: status.wolfpackMode,
|
|
11249
12017
|
rlmEnabled: status.rlmEnabled,
|
|
12018
|
+
rlmMaxDepth: status.rlmMaxDepth ?? null,
|
|
11250
12019
|
contextTokens: status.contextTokens,
|
|
11251
12020
|
maxContextTokens: status.maxContextTokens,
|
|
11252
12021
|
contextUsage: status.contextUsage,
|
|
@@ -13463,7 +14232,7 @@ var DialogManager = class {
|
|
|
13463
14232
|
}));
|
|
13464
14233
|
}
|
|
13465
14234
|
showMemoryPicker(preloadedMemos, preloadedTotal) {
|
|
13466
|
-
const store =
|
|
14235
|
+
const store = sharedMemoStore(getDataDir());
|
|
13467
14236
|
const hasData = preloadedMemos !== void 0;
|
|
13468
14237
|
const memos = preloadedMemos ?? [];
|
|
13469
14238
|
const total = preloadedTotal ?? 0;
|
|
@@ -13628,6 +14397,7 @@ function createInitialAppState(input) {
|
|
|
13628
14397
|
ccConnectActive: false,
|
|
13629
14398
|
wolfpackMode: input.cliOptions.wolfpack === true,
|
|
13630
14399
|
rlmEnabled: false,
|
|
14400
|
+
rlmMaxDepth: null,
|
|
13631
14401
|
reconnectAttempt: 0,
|
|
13632
14402
|
recentSessions: [],
|
|
13633
14403
|
subagentUsage: {}
|
|
@@ -13653,6 +14423,7 @@ var ScreamTUI = class {
|
|
|
13653
14423
|
/** Interval handle for the periodic provider-balance refresh. */
|
|
13654
14424
|
balancePollTimer;
|
|
13655
14425
|
updatePrefetched;
|
|
14426
|
+
sessionsPrefetch;
|
|
13656
14427
|
sessionManager;
|
|
13657
14428
|
dialogManager;
|
|
13658
14429
|
streamingUI;
|
|
@@ -13692,6 +14463,7 @@ var ScreamTUI = class {
|
|
|
13692
14463
|
this.options = tuiOptions;
|
|
13693
14464
|
this.startupNotice = startupInput.startupNotice;
|
|
13694
14465
|
this.updatePrefetched = startupInput.updatePrefetched === true;
|
|
14466
|
+
this.sessionsPrefetch = startupInput.sessionsPrefetch;
|
|
13695
14467
|
this.state = createTUIState(tuiOptions);
|
|
13696
14468
|
this.harness.setSubagentModelBindings(() => this.state.appState.subagentModels);
|
|
13697
14469
|
this.reverseRpcDisposers.push(...registerReverseRPCHandlers(this.approvalController, this.questionController, {
|
|
@@ -13827,7 +14599,8 @@ var ScreamTUI = class {
|
|
|
13827
14599
|
try {
|
|
13828
14600
|
const { shouldReplay } = await this.sessionManager.init({
|
|
13829
14601
|
startup: this.options.startup,
|
|
13830
|
-
workDir: this.state.appState.workDir
|
|
14602
|
+
workDir: this.state.appState.workDir,
|
|
14603
|
+
sessionsPrefetch: this.sessionsPrefetch
|
|
13831
14604
|
});
|
|
13832
14605
|
return shouldReplay;
|
|
13833
14606
|
} catch (error) {
|
|
@@ -13956,7 +14729,7 @@ var ScreamTUI = class {
|
|
|
13956
14729
|
return first;
|
|
13957
14730
|
}
|
|
13958
14731
|
pushTranscriptEntry(entry) {
|
|
13959
|
-
this.
|
|
14732
|
+
this.transcriptController.ingestEntry(entry);
|
|
13960
14733
|
}
|
|
13961
14734
|
setExternalEditorRunning(running) {
|
|
13962
14735
|
this.state.externalEditorRunning = running;
|
|
@@ -13973,6 +14746,21 @@ var ScreamTUI = class {
|
|
|
13973
14746
|
get backgroundTasks() {
|
|
13974
14747
|
return this.sessionEventHandler.backgroundTasks;
|
|
13975
14748
|
}
|
|
14749
|
+
/** Live per-type subagent slots (sidebar state machine) — source of the
|
|
14750
|
+
* /tasks browser's Agents view. */
|
|
14751
|
+
get subagentSlots() {
|
|
14752
|
+
return this.sessionEventHandler.getSubagentSlots();
|
|
14753
|
+
}
|
|
14754
|
+
/** Per-instance subagent provenance (spawn parent / outcome / description),
|
|
14755
|
+
* keyed by agentId — source of the /tasks browser's Agents view. */
|
|
14756
|
+
get subagentInstances() {
|
|
14757
|
+
return this.sessionEventHandler.getSubagentInstances();
|
|
14758
|
+
}
|
|
14759
|
+
/** Archive ring for the Agents view: closed instances' last derived rows
|
|
14760
|
+
* (oldest first), the bounded home of agents the live map has released. */
|
|
14761
|
+
get recentSubagentInstances() {
|
|
14762
|
+
return this.sessionEventHandler.getRecentSubagentInstances();
|
|
14763
|
+
}
|
|
13976
14764
|
getCurrentSessionId() {
|
|
13977
14765
|
return this.state.appState.sessionId;
|
|
13978
14766
|
}
|
|
@@ -14142,7 +14930,7 @@ var ScreamTUI = class {
|
|
|
14142
14930
|
const card = new OnboardingCardComponent({
|
|
14143
14931
|
colors: this.state.theme.colors,
|
|
14144
14932
|
onConfigure: () => {
|
|
14145
|
-
import("./dispatch-
|
|
14933
|
+
import("./dispatch-DoaBG--u.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
|
|
14146
14934
|
this.restoreEditor();
|
|
14147
14935
|
onDone?.();
|
|
14148
14936
|
});
|
|
@@ -14514,7 +15302,7 @@ function supportsAnsi() {
|
|
|
14514
15302
|
ansiSupported = false;
|
|
14515
15303
|
return false;
|
|
14516
15304
|
}
|
|
14517
|
-
async function runLoadingAnimation(theme
|
|
15305
|
+
async function runLoadingAnimation(theme, tuiConfig) {
|
|
14518
15306
|
if (!supportsAnsi()) {
|
|
14519
15307
|
const { cols } = getTerminalSize();
|
|
14520
15308
|
if (cols >= FULL_LOGO_MIN_COLS) for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
|
|
@@ -14522,10 +15310,7 @@ async function runLoadingAnimation(theme = "dark") {
|
|
|
14522
15310
|
stdout.write(`${BOLD}${fg(...THEME_PRIMARY[theme])}${t("loading.waking")}${RESET}\n`);
|
|
14523
15311
|
return;
|
|
14524
15312
|
}
|
|
14525
|
-
let autoStart =
|
|
14526
|
-
try {
|
|
14527
|
-
autoStart = (await loadTuiConfig()).autoStart;
|
|
14528
|
-
} catch {}
|
|
15313
|
+
let autoStart = tuiConfig.autoStart;
|
|
14529
15314
|
const autoStartAtBoot = autoStart;
|
|
14530
15315
|
let toggled = false;
|
|
14531
15316
|
return new Promise((resolve) => {
|
|
@@ -14679,10 +15464,12 @@ async function runShell(opts, version) {
|
|
|
14679
15464
|
});
|
|
14680
15465
|
await harness.ensureConfigFile();
|
|
14681
15466
|
await harness.preflight();
|
|
15467
|
+
const sessionsPrefetch = harness.listSessions({ workDir });
|
|
15468
|
+
sessionsPrefetch.catch(() => {});
|
|
14682
15469
|
refreshUpdateCache().catch((error) => {
|
|
14683
15470
|
log.warn("update cache refresh failed", { error });
|
|
14684
15471
|
});
|
|
14685
|
-
await runLoadingAnimation(resolvedTheme);
|
|
15472
|
+
await runLoadingAnimation(resolvedTheme, tuiConfig);
|
|
14686
15473
|
const tui = new ScreamTUI(harness, {
|
|
14687
15474
|
cliOptions: opts,
|
|
14688
15475
|
tuiConfig,
|
|
@@ -14690,7 +15477,8 @@ async function runShell(opts, version) {
|
|
|
14690
15477
|
workDir,
|
|
14691
15478
|
startupNotice: configWarning,
|
|
14692
15479
|
resolvedTheme,
|
|
14693
|
-
updatePrefetched: true
|
|
15480
|
+
updatePrefetched: true,
|
|
15481
|
+
sessionsPrefetch
|
|
14694
15482
|
});
|
|
14695
15483
|
tui.onExit = async (exitCode = 0) => {
|
|
14696
15484
|
const sessionId = tui.getCurrentSessionId();
|
|
@@ -16149,6 +16937,15 @@ var FileGate = class {
|
|
|
16149
16937
|
* consumer of the same SDK (alongside run-shell TUI and run-stream-json),
|
|
16150
16938
|
* not a separate engine. Zero changes to agent-core or node-sdk.
|
|
16151
16939
|
*/
|
|
16940
|
+
/**
|
|
16941
|
+
* In-memory FIFO caps per web session. The on-disk journal stays complete;
|
|
16942
|
+
* these bounds only stop a long-lived session from growing without limit in
|
|
16943
|
+
* RAM. Dropped rows are tallied so the elision stays observable (see
|
|
16944
|
+
* `getMemoryBounds`) — the same `[…N … elided…]` accounting the transcript
|
|
16945
|
+
* uses, without re-deriving it from a cursor gap.
|
|
16946
|
+
*/
|
|
16947
|
+
const MAX_JOURNAL_ENTRIES = 200;
|
|
16948
|
+
const MAX_SESSION_MESSAGES = 200;
|
|
16152
16949
|
/** Thinking bodies longer than this are delivered as a tail excerpt + on-demand full text. */
|
|
16153
16950
|
const TRUNCATE_THINKING_LEN = 8 * 1024;
|
|
16154
16951
|
function truncateThinking(messages) {
|
|
@@ -16499,6 +17296,50 @@ const MAIN_TRANSCRIPT_EVENTS = new Set([
|
|
|
16499
17296
|
"tool.result"
|
|
16500
17297
|
]);
|
|
16501
17298
|
/**
|
|
17299
|
+
* Identity of a persisted user-message row; used by the fork copy to drop the
|
|
17300
|
+
* memory copy of a row the disk already carries.
|
|
17301
|
+
*/
|
|
17302
|
+
function persistedUserMessageKey(entry) {
|
|
17303
|
+
return JSON.stringify([
|
|
17304
|
+
entry.beforeSeq,
|
|
17305
|
+
entry.clientMessageId ?? null,
|
|
17306
|
+
entry.text
|
|
17307
|
+
]);
|
|
17308
|
+
}
|
|
17309
|
+
/**
|
|
17310
|
+
* Union of the two views of one session's durable journal, for the fork copy.
|
|
17311
|
+
*
|
|
17312
|
+
* `diskEntries` (read back from the source's journal file) is the complete
|
|
17313
|
+
* history and forms the base: the in-memory journal is FIFO-capped
|
|
17314
|
+
* (MAX_JOURNAL_ENTRIES), so a long conversation's memory only holds its tail
|
|
17315
|
+
* while the file keeps every row. `memoryEntries` fills the rows a
|
|
17316
|
+
* fire-and-forget write has not flushed yet; rows the disk already carries are
|
|
17317
|
+
* dropped by seq (journal rows) or identity (user messages), so no line is
|
|
17318
|
+
* doubled.
|
|
17319
|
+
*/
|
|
17320
|
+
function mergeForkJournalEntries(diskEntries, memoryEntries) {
|
|
17321
|
+
const journalSeqs = /* @__PURE__ */ new Set();
|
|
17322
|
+
const userMessageKeys = /* @__PURE__ */ new Set();
|
|
17323
|
+
const journalEntries = [];
|
|
17324
|
+
const userMessages = [];
|
|
17325
|
+
const push = (entry) => {
|
|
17326
|
+
if (entry.type === "user_message") {
|
|
17327
|
+
const key = persistedUserMessageKey(entry);
|
|
17328
|
+
if (userMessageKeys.has(key)) return;
|
|
17329
|
+
userMessageKeys.add(key);
|
|
17330
|
+
userMessages.push(entry);
|
|
17331
|
+
return;
|
|
17332
|
+
}
|
|
17333
|
+
if (journalSeqs.has(entry.seq)) return;
|
|
17334
|
+
journalSeqs.add(entry.seq);
|
|
17335
|
+
journalEntries.push(entry);
|
|
17336
|
+
};
|
|
17337
|
+
for (const entry of diskEntries) push(entry);
|
|
17338
|
+
for (const entry of memoryEntries) push(entry);
|
|
17339
|
+
journalEntries.sort((a, b) => a.seq - b.seq);
|
|
17340
|
+
return [...journalEntries, ...userMessages];
|
|
17341
|
+
}
|
|
17342
|
+
/**
|
|
16502
17343
|
* Whether a journal entry already carries a message (if so it needs no seeding
|
|
16503
17344
|
* from core history).
|
|
16504
17345
|
*/
|
|
@@ -16880,6 +17721,10 @@ var WebSession = class {
|
|
|
16880
17721
|
connections = /* @__PURE__ */ new Map();
|
|
16881
17722
|
journal = [];
|
|
16882
17723
|
userMessages = [];
|
|
17724
|
+
/** Aggregate count of journal entries dropped by the FIFO cap. */
|
|
17725
|
+
droppedJournalCount = 0;
|
|
17726
|
+
/** Aggregate count of message rows dropped by the FIFO cap. */
|
|
17727
|
+
droppedMessageCount = 0;
|
|
16883
17728
|
nextSeq = 1;
|
|
16884
17729
|
epoch = 1;
|
|
16885
17730
|
cachedStatus = null;
|
|
@@ -16971,7 +17816,11 @@ var WebSession = class {
|
|
|
16971
17816
|
getTitle() {
|
|
16972
17817
|
return this.title;
|
|
16973
17818
|
}
|
|
16974
|
-
/**
|
|
17819
|
+
/**
|
|
17820
|
+
* Return the in-memory durable journal entries (capped tail). The fork copy
|
|
17821
|
+
* takes these only for rows the source's journal file does not carry yet;
|
|
17822
|
+
* the file itself is the complete history.
|
|
17823
|
+
*/
|
|
16975
17824
|
getDurableJournalEntries() {
|
|
16976
17825
|
const entries = [];
|
|
16977
17826
|
for (const e of this.journal) if (!e.volatile) entries.push({
|
|
@@ -16986,6 +17835,15 @@ var WebSession = class {
|
|
|
16986
17835
|
});
|
|
16987
17836
|
return entries;
|
|
16988
17837
|
}
|
|
17838
|
+
/**
|
|
17839
|
+
* Drain in-flight persistence writes (journal rows, user messages, metadata).
|
|
17840
|
+
* A fork reads the source's journal file afterwards and must not race a write
|
|
17841
|
+
* still queued behind the serialization gate — the reader would otherwise
|
|
17842
|
+
* mistake a flushed row for unsaved state.
|
|
17843
|
+
*/
|
|
17844
|
+
async flushPendingWrites() {
|
|
17845
|
+
await Promise.allSettled(this.pendingWrites);
|
|
17846
|
+
}
|
|
16989
17847
|
async persistEntry(entry, throwOnError = false) {
|
|
16990
17848
|
if (!this.homeDir) return;
|
|
16991
17849
|
const line = JSON.stringify({
|
|
@@ -17004,6 +17862,7 @@ var WebSession = class {
|
|
|
17004
17862
|
},
|
|
17005
17863
|
beforeSeq
|
|
17006
17864
|
});
|
|
17865
|
+
this.trimMessages();
|
|
17007
17866
|
if (!this.homeDir) return;
|
|
17008
17867
|
const line = JSON.stringify({
|
|
17009
17868
|
type: "user_message",
|
|
@@ -17034,9 +17893,11 @@ var WebSession = class {
|
|
|
17034
17893
|
},
|
|
17035
17894
|
beforeSeq: um.beforeSeq
|
|
17036
17895
|
});
|
|
17896
|
+
this.trimMessages();
|
|
17037
17897
|
} else {
|
|
17038
17898
|
const je = entry;
|
|
17039
17899
|
this.journal.push(je);
|
|
17900
|
+
this.trimJournal();
|
|
17040
17901
|
this.nextSeq = Math.max(this.nextSeq, je.seq + 1);
|
|
17041
17902
|
if (this.session === null && je.payload.agentId === "main" && je.payload.type === "goal.updated") this.cachedGoal = cloneSnapshot(je.payload.snapshot);
|
|
17042
17903
|
else if (this.session === null && je.payload.agentId === "main" && je.payload.type === "todo.updated") this.cachedTodos = cloneSnapshot(je.payload.todos);
|
|
@@ -17103,6 +17964,7 @@ var WebSession = class {
|
|
|
17103
17964
|
...message,
|
|
17104
17965
|
seq
|
|
17105
17966
|
});
|
|
17967
|
+
this.trimMessages();
|
|
17106
17968
|
seq += 1;
|
|
17107
17969
|
}
|
|
17108
17970
|
if (seq - 1 >= this.nextSeq) this.nextSeq = seq;
|
|
@@ -17126,6 +17988,44 @@ var WebSession = class {
|
|
|
17126
17988
|
permission: this.permission
|
|
17127
17989
|
};
|
|
17128
17990
|
}
|
|
17991
|
+
/** FIFO-cap `journal` to `MAX_JOURNAL_ENTRIES`, tallying elided rows. */
|
|
17992
|
+
trimJournal() {
|
|
17993
|
+
while (this.journal.length > MAX_JOURNAL_ENTRIES) {
|
|
17994
|
+
this.journal.shift();
|
|
17995
|
+
this.droppedJournalCount += 1;
|
|
17996
|
+
}
|
|
17997
|
+
}
|
|
17998
|
+
/**
|
|
17999
|
+
* FIFO-cap message stores to `MAX_SESSION_MESSAGES`, tallying elided rows.
|
|
18000
|
+
*
|
|
18001
|
+
* Drop order follows the rendered order (`buildMessages` puts the seeded
|
|
18002
|
+
* core-history rows at the head, live user rows after them), so the eviction
|
|
18003
|
+
* is genuinely oldest-first and the visible tail of the conversation is what
|
|
18004
|
+
* survives.
|
|
18005
|
+
*/
|
|
18006
|
+
trimMessages() {
|
|
18007
|
+
while (this.userMessages.length + this.seededMessages.length > MAX_SESSION_MESSAGES) {
|
|
18008
|
+
if (this.seededMessages.length > 0) this.seededMessages.shift();
|
|
18009
|
+
else if (this.userMessages.length > 0) this.userMessages.shift();
|
|
18010
|
+
else break;
|
|
18011
|
+
this.droppedMessageCount += 1;
|
|
18012
|
+
}
|
|
18013
|
+
}
|
|
18014
|
+
/**
|
|
18015
|
+
* Observable surface of the in-memory FIFO caps: current occupancy, the
|
|
18016
|
+
* limits, and the aggregate elided-row tallies (`[…N … elided…]` accounting,
|
|
18017
|
+
* shared with the transcript).
|
|
18018
|
+
*/
|
|
18019
|
+
getMemoryBounds() {
|
|
18020
|
+
return {
|
|
18021
|
+
journal: this.journal.length,
|
|
18022
|
+
journalLimit: MAX_JOURNAL_ENTRIES,
|
|
18023
|
+
journalElided: this.droppedJournalCount,
|
|
18024
|
+
messages: this.userMessages.length + this.seededMessages.length,
|
|
18025
|
+
messageLimit: MAX_SESSION_MESSAGES,
|
|
18026
|
+
messagesElided: this.droppedMessageCount
|
|
18027
|
+
};
|
|
18028
|
+
}
|
|
17129
18029
|
appendEvent(event) {
|
|
17130
18030
|
const payload = event.type === "goal.updated" || event.type === "todo.updated" ? cloneSnapshot(event) : event;
|
|
17131
18031
|
const entry = {
|
|
@@ -17135,6 +18035,7 @@ var WebSession = class {
|
|
|
17135
18035
|
payload
|
|
17136
18036
|
};
|
|
17137
18037
|
this.journal.push(entry);
|
|
18038
|
+
this.trimJournal();
|
|
17138
18039
|
if (!entry.volatile) this.trackPending(this.persistEntry(entry));
|
|
17139
18040
|
return entry;
|
|
17140
18041
|
}
|
|
@@ -17151,6 +18052,7 @@ var WebSession = class {
|
|
|
17151
18052
|
payload
|
|
17152
18053
|
};
|
|
17153
18054
|
this.journal.push(entry);
|
|
18055
|
+
this.trimJournal();
|
|
17154
18056
|
const persisted = this.persistEntry(entry, throwOnPersistenceError);
|
|
17155
18057
|
this.trackPending(persisted);
|
|
17156
18058
|
return {
|
|
@@ -18458,8 +19360,9 @@ var SessionManager = class {
|
|
|
18458
19360
|
}
|
|
18459
19361
|
/**
|
|
18460
19362
|
* Fork an active session: harness.forkSession copies the agent state into a
|
|
18461
|
-
* new session, and we additionally copy the source's durable journal
|
|
18462
|
-
*
|
|
19363
|
+
* new session, and we additionally copy the source's durable journal from its
|
|
19364
|
+
* journal file — the complete history, not the FIFO-capped in-memory tail —
|
|
19365
|
+
* so the forked web session displays the same prior conversation.
|
|
18463
19366
|
*/
|
|
18464
19367
|
async forkSession(sourceId) {
|
|
18465
19368
|
const source = this.sessions.get(sourceId);
|
|
@@ -18479,13 +19382,18 @@ var SessionManager = class {
|
|
|
18479
19382
|
title,
|
|
18480
19383
|
onFork: (id) => this.forkSession(id)
|
|
18481
19384
|
});
|
|
18482
|
-
|
|
18483
|
-
|
|
19385
|
+
await source.flushPendingWrites();
|
|
19386
|
+
const { entries: diskEntries, corrupt } = await loadJournal(this.homeDir, sourceId);
|
|
19387
|
+
const entries = mergeForkJournalEntries(diskEntries, source.getDurableJournalEntries());
|
|
19388
|
+
webSession.loadFromPersisted(entries);
|
|
19389
|
+
const journalHasContent = webSession.hasAnyAssistantContent();
|
|
19390
|
+
const journalDamaged = corrupt > 0 && !journalHasContent;
|
|
19391
|
+
if (journalDamaged) webSession.markDamaged(`journal has ${corrupt} corrupt lines and no parsed message carries a body`);
|
|
19392
|
+
else if (entries.length > 0) {
|
|
18484
19393
|
const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
18485
19394
|
await writeFile(getJournalPath(this.homeDir, newId), lines);
|
|
18486
19395
|
}
|
|
18487
|
-
|
|
18488
|
-
if (!entries.some(isMessageBearingEntry)) try {
|
|
19396
|
+
if (!entries.some(isMessageBearingEntry) || journalDamaged) try {
|
|
18489
19397
|
const context = await session.getContext();
|
|
18490
19398
|
webSession.seedHistory(contextHistoryToChatMessages(context.history));
|
|
18491
19399
|
} catch (error) {
|
|
@@ -18963,6 +19871,19 @@ function startWebIdleExit(opts) {
|
|
|
18963
19871
|
}
|
|
18964
19872
|
};
|
|
18965
19873
|
}
|
|
19874
|
+
/**
|
|
19875
|
+
* Validates the optional `maxDepth` body field of the /rlm route with the same
|
|
19876
|
+
* grammar as /rlm-max-depth (a non-negative integer; 0 = unlimited) via the
|
|
19877
|
+
* shared agent-core helper. Absent / null leaves the cap untouched; any other
|
|
19878
|
+
* invalid value is a 400 so a client cannot silently send a cap that never
|
|
19879
|
+
* lands.
|
|
19880
|
+
*/
|
|
19881
|
+
function optionalRlmMaxDepth(raw) {
|
|
19882
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
19883
|
+
const parsed = parseRlmMaxDepthArg(typeof raw === "string" ? raw : String(raw));
|
|
19884
|
+
if (!parsed.ok) throw new HttpError(400, `Invalid rlm max depth: ${parsed.reason}`);
|
|
19885
|
+
return parsed.value;
|
|
19886
|
+
}
|
|
18966
19887
|
async function handleSessionControlRoutes(req, res, url, method, manager) {
|
|
18967
19888
|
const statusMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/status$`).exec(url);
|
|
18968
19889
|
if (statusMatch && method === "GET") {
|
|
@@ -19050,7 +19971,7 @@ async function handleSessionControlRoutes(req, res, url, method, manager) {
|
|
|
19050
19971
|
try {
|
|
19051
19972
|
const body = await readJsonBody(req);
|
|
19052
19973
|
const enabled = optionalBoolean(body, "enabled", true);
|
|
19053
|
-
const maxDepth =
|
|
19974
|
+
const maxDepth = optionalRlmMaxDepth(body["maxDepth"]);
|
|
19054
19975
|
await manager.setRlm(decodeURIComponent(rlmMatch[1]), enabled, maxDepth);
|
|
19055
19976
|
sendJson(res, 200, { ok: true });
|
|
19056
19977
|
} catch (error) {
|