scream-code 0.15.8 → 0.16.0
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 +1 -1
- package/dist/{app-Bbg_nJTR.mjs → app-N_PRvDIk.mjs} +702 -101
- package/dist/{dispatch-CEK-syAX.mjs → dispatch-5S52okPl.mjs} +1 -1
- package/dist/{dispatch-BaAsj0v5.mjs → dispatch-CsrteWRw.mjs} +40 -10
- package/dist/main.mjs +1 -1
- package/dist/public/assets/index-DwX1jGaf.js +96 -0
- package/dist/public/assets/index-DwX1jGaf.js.map +1 -0
- package/dist/public/assets/index-zIKfLBMX.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/{text-input-dialog-BBSjCM5z.mjs → text-input-dialog-B0PROY20.mjs} +1 -1
- package/dist/{text-input-dialog-C5pDKZ-c.mjs → text-input-dialog-BVQaKy1Z.mjs} +30 -6
- package/package.json +1 -1
- package/dist/public/assets/index-DjlCHEg8.js +0 -96
- package/dist/public/assets/index-DjlCHEg8.js.map +0 -1
- package/dist/public/assets/index-yCBo3yth.css +0 -1
|
@@ -4,8 +4,8 @@ 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 { $t as
|
|
8
|
-
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-
|
|
7
|
+
import { $n as isOrphanedToolCallError, $t as appendStreamingArgsPreview, A as renderDiffLinesClustered, An as saveTuiConfig, At as DISABLE_TERMINAL_THEME_REPORTING, B as BackgroundAgentStatusComponent, Bn as DEFAULT_CATALOG_URL, Bt as TERMINAL_THEME_DARK, Cn as isEmbeddingModelCached, Ct as createMarkdownTheme, D as toggleEmptySessionHint, Dn as TuiConfigParseError, Dt as detectTerminalTheme, E as isTurnElapsedEnabled, En as PULSE_WAVE_FRAMES, Et as getColorPalette, F as estimateTokens, Fn as detectInstallSource, Ft as OSC11_RESPONSE_PREFIX, G as getBreathingFrame, Gn as appendSessionIndexEntry, Gt as lerpGradient, H as AgentGroupComponent, Hn as saveCatalogCache, Ht as isBusy, I as getSharedSpeedTracker, In as CLI_COMMAND_NAME, It as OSC11_RESPONSE_PREFIX_NO_ESC, J as refineGoal, Jn as MemoryMemoStore, K as resetBreathingClock, L as SkillActivationComponent, Ln as CLI_UI_MODE, Lt as QUERY_TERMINAL_THEME, M as langFromPath, Mn as getDataDir, Mt as ENABLE_TERMINAL_THEME_REPORTING, N as CachedContainer, Nn as getInputHistoryFile, Nt as OSC11_QUERY, O as ToolCallComponent, On as TuiLikePreferencesSchema, Ot as parseOsc11BackgroundTheme, P as ThinkingComponent, Pn as getLogDir, Pt as OSC11_RESPONSE, Qn as isScreamError, Qt as STATUS_BULLET, R as ReadGroupComponent, Rn as CLI_USER_AGENT_PRODUCT, Rt as TERMINAL_FOCUS_IN, S as getDaemonInstructions, Sn as getKnowledgeStore, St as createEditorTheme, T as isEmptySessionHintDismissed, Tn as PIXEL_PULSE_FRAMES, Tt as contrastTextHex, U as WelcomeComponent, Un as ScreamHarness, Ut as isStreaming, V as AssistantMessageComponent, Vn as fetchCatalog, Vt as TERMINAL_THEME_LIGHT, W as BREATHE_CYCLE_MS, Wn as encodeWorkDirKey, Wt as FooterComponent, Xn as log, Xt as printableChar, Yn as flushDiagnosticLogs, Zn as resolveGlobalLogPath, Zt as INTERJECTION_BULLET, _ as refreshUpdateCache, _n as buildSkillSlashCommands, _t as supportsBalance, an as stringValue, b as readJsonlFile, bn as isExperimentalFlagEnabled, c as hasDispose, cn as EMPTY_SESSION_HINT_URL, d as formatMemoryMemoForInjection, dn as SESSION_TIPS, en as argsRecord, er as ErrorCodes, et as toTerminalHyperlink, fn as TIP_ROTATION_INTERVAL_MS, g as selectUpdateTarget, gn as getNoActiveSessionMessage, gt as refreshProviderBalance, hn as getLlmNotSetMessage, i as buildRoleAdditionalText, in as serializeToolResultOutput, j as highlightLines, jn as detectShellEnvironment, jt as ENABLE_TERMINAL_FOCUS_REPORTING, k as renderDiffLines, kn as loadTuiConfig, kt as DISABLE_TERMINAL_FOCUS_REPORTING, l as isPlanExpandable, ln as EXIT_CONFIRM_WINDOW_MS, lt as handlePlanCommand, mn as getCtrlDHint, n as clearEvalPanelState, nn as isTodoItemShape, nt as getModelCycleLevel, on as truncateErrorMessage, pn as getCtrlCHint, q as clearGoalState, qn as resolveScreamHome, r as openUrl, rn as parseStreamingArgs, s as disposeChildren, sn as CHARS_PER_TOKEN, st as handleFusionPlanCommand, t as dispatchInput, tn as formatErrorMessage, tr as SCREAM_ERROR_INFO, tt as changeThinkingLevel, u as MoonLoader, un as MAIN_AGENT_ID, v as readUpdateCache, vn as BUILTIN_SLASH_COMMANDS, vt as clearInfoPanelState, w as UserMessageComponent, wn as startManualEmbeddingDownload, wt as createThemeStyles, xn as setExperimentalFlags, xt as resolveThemeSync, y as appendJsonlLine, yn as sortSlashCommands, z as parseReadGroupOutput, zn as PRODUCT_NAME, zt as TERMINAL_FOCUS_OUT } from "./dispatch-CsrteWRw.mjs";
|
|
8
|
+
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-BVQaKy1Z.mjs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
11
11
|
import { access, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -32,7 +32,7 @@ function optionalBuildString(value) {
|
|
|
32
32
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
33
33
|
}
|
|
34
34
|
const SCREAM_BUILD_INFO = {
|
|
35
|
-
version: optionalBuildString("0.
|
|
35
|
+
version: optionalBuildString("0.16.0"),
|
|
36
36
|
channel: optionalBuildString(""),
|
|
37
37
|
commit: optionalBuildString(""),
|
|
38
38
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -1995,10 +1995,11 @@ var StatusMessageComponent = class extends Container {
|
|
|
1995
1995
|
}
|
|
1996
1996
|
};
|
|
1997
1997
|
var NoticeMessageComponent = class extends Container {
|
|
1998
|
-
constructor(title, detail, colors) {
|
|
1998
|
+
constructor(title, detail, colors, markerColor) {
|
|
1999
1999
|
super();
|
|
2000
|
+
const marker = markerColor === void 0 ? " " : chalk.hex(markerColor)(INTERJECTION_BULLET);
|
|
2000
2001
|
this.addChild(new Spacer(1));
|
|
2001
|
-
this.addChild(new Text(
|
|
2002
|
+
this.addChild(new Text(`${marker}${chalk.hex(colors.textStrong)(replaceTabs(title))}`, 0, 0));
|
|
2002
2003
|
if (detail !== void 0 && detail.length > 0) this.addChild(new Text(` ${chalk.hex(colors.textDim)(replaceTabs(detail))}`, 0, 0));
|
|
2003
2004
|
}
|
|
2004
2005
|
};
|
|
@@ -2025,6 +2026,158 @@ function formatBackgroundAgentTranscript(phase, meta, extras = void 0) {
|
|
|
2025
2026
|
};
|
|
2026
2027
|
}
|
|
2027
2028
|
//#endregion
|
|
2029
|
+
//#region src/tui/utils/child-request-notice.ts
|
|
2030
|
+
/**
|
|
2031
|
+
* Child→parent collaboration requests (`ContactParent`) shown in the main
|
|
2032
|
+
* transcript as a two-line notice: a bold title naming the requester, then a
|
|
2033
|
+
* dim detail line carrying the request itself.
|
|
2034
|
+
*
|
|
2035
|
+
* Two producers share this formatter so live and replay never disagree:
|
|
2036
|
+
* - the live path reads the subagent's `tool.call.started` args;
|
|
2037
|
+
* - session replay recovers the same fields from the `<notification>` XML the
|
|
2038
|
+
* kernel injects into the parent's context.
|
|
2039
|
+
*
|
|
2040
|
+
* Replay cannot recover the subagent's display name (spawn events are not part
|
|
2041
|
+
* of the persisted context), so it renders the name-less title variant.
|
|
2042
|
+
*/
|
|
2043
|
+
const CHILD_REQUEST_KINDS = [
|
|
2044
|
+
"info",
|
|
2045
|
+
"handoff",
|
|
2046
|
+
"escalate"
|
|
2047
|
+
];
|
|
2048
|
+
/** Cap each payload list so a 20-entry artifact array cannot explode the transcript. */
|
|
2049
|
+
const LIST_PREVIEW = 3;
|
|
2050
|
+
/**
|
|
2051
|
+
* Longest previewed value kept on a transcript row — request text, `needs`, or
|
|
2052
|
+
* a payload list. The kernel allows 8192 chars (`contact-parent.ts`), and
|
|
2053
|
+
* transcript folding counts components rather than lines, so an uncapped row
|
|
2054
|
+
* can never collapse. The full text stays available: it is in the parent's
|
|
2055
|
+
* context and in the agent card's sub-tool-call args.
|
|
2056
|
+
*/
|
|
2057
|
+
const TEXT_PREVIEW = 200;
|
|
2058
|
+
/** Collapse the request onto previewable single-paragraph form. */
|
|
2059
|
+
function previewText(value) {
|
|
2060
|
+
const flattened = value.replaceAll(/\s*\n\s*/g, " ").trim();
|
|
2061
|
+
return flattened.length > TEXT_PREVIEW ? `${flattened.slice(0, TEXT_PREVIEW)}…` : flattened;
|
|
2062
|
+
}
|
|
2063
|
+
const TYPE_KEY = {
|
|
2064
|
+
info: "transcript.child_request_type_info",
|
|
2065
|
+
handoff: "transcript.child_request_type_handoff",
|
|
2066
|
+
escalate: "transcript.child_request_type_escalate"
|
|
2067
|
+
};
|
|
2068
|
+
const LIST_KEY = {
|
|
2069
|
+
artifacts: "transcript.child_request_artifacts",
|
|
2070
|
+
evidence: "transcript.child_request_evidence",
|
|
2071
|
+
missing: "transcript.child_request_missing"
|
|
2072
|
+
};
|
|
2073
|
+
function kindOf(raw) {
|
|
2074
|
+
return typeof raw === "string" && CHILD_REQUEST_KINDS.includes(raw) ? raw : "info";
|
|
2075
|
+
}
|
|
2076
|
+
function textOf(raw) {
|
|
2077
|
+
if (typeof raw !== "string") return void 0;
|
|
2078
|
+
const trimmed = raw.trim();
|
|
2079
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2080
|
+
}
|
|
2081
|
+
function stringifyList(raw) {
|
|
2082
|
+
const shown = previewText(raw.slice(0, LIST_PREVIEW).join(", "));
|
|
2083
|
+
const hidden = raw.length - Math.min(raw.length, LIST_PREVIEW);
|
|
2084
|
+
return hidden > 0 ? `${shown} +${hidden}` : shown;
|
|
2085
|
+
}
|
|
2086
|
+
function previewedList(field, items) {
|
|
2087
|
+
if (items === void 0 || items.length === 0) return void 0;
|
|
2088
|
+
return t(LIST_KEY[field], { value: stringifyList(items) });
|
|
2089
|
+
}
|
|
2090
|
+
/** Live producer: the args the model handed to `ContactParent`. */
|
|
2091
|
+
function childRequestFieldsFromArgs(args) {
|
|
2092
|
+
const message = textOf(args["message"]);
|
|
2093
|
+
if (message === void 0) return null;
|
|
2094
|
+
const payload = typeof args["payload"] === "object" && args["payload"] !== null && !Array.isArray(args["payload"]) ? args["payload"] : {};
|
|
2095
|
+
const listOf = (key) => {
|
|
2096
|
+
const raw = payload[key];
|
|
2097
|
+
if (!Array.isArray(raw)) return void 0;
|
|
2098
|
+
const items = raw.map((item) => textOf(item)).filter((item) => item !== void 0);
|
|
2099
|
+
return items.length > 0 ? items : void 0;
|
|
2100
|
+
};
|
|
2101
|
+
return {
|
|
2102
|
+
requestType: kindOf(args["request_type"]),
|
|
2103
|
+
message,
|
|
2104
|
+
needs: textOf(args["needs"]),
|
|
2105
|
+
artifacts: listOf("artifacts"),
|
|
2106
|
+
evidence: listOf("evidence"),
|
|
2107
|
+
missing: listOf("missing")
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
/** Parse `artifacts: [a, b]` back into its items. */
|
|
2111
|
+
function bracketedListOf(value) {
|
|
2112
|
+
return (value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value).split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Replay producer: recover the fields from the injected notification. The body
|
|
2116
|
+
* shape is owned by `submitChildRequest` in agent-core:
|
|
2117
|
+
* `<type>: <message>` then optional `needs:` / `artifacts:` / `evidence:` /
|
|
2118
|
+
* `missing:` lines. A multi-line message keeps its extra lines attached.
|
|
2119
|
+
*/
|
|
2120
|
+
function childRequestFieldsFromNotification(text) {
|
|
2121
|
+
const lines = text.split("\n");
|
|
2122
|
+
const open = lines.findIndex((line) => line.startsWith("<notification ") && line.includes("type=\"child_request\""));
|
|
2123
|
+
if (open < 0) return null;
|
|
2124
|
+
const body = [];
|
|
2125
|
+
for (let index = open + 1; index < lines.length; index += 1) {
|
|
2126
|
+
const line = lines[index] ?? "";
|
|
2127
|
+
if (line === "</notification>") break;
|
|
2128
|
+
if (line.startsWith("Title: ") || line.startsWith("Severity: ")) continue;
|
|
2129
|
+
body.push(line);
|
|
2130
|
+
}
|
|
2131
|
+
const first = body[0];
|
|
2132
|
+
if (first === void 0) return null;
|
|
2133
|
+
const head = /^(info|handoff|escalate):\s([\s\S]*)$/.exec(first);
|
|
2134
|
+
const requestType = head !== null ? kindOf(head[1]) : "info";
|
|
2135
|
+
const messageParts = [head !== null && head[2] !== void 0 ? head[2] : first];
|
|
2136
|
+
let needs;
|
|
2137
|
+
const lists = {};
|
|
2138
|
+
for (const line of body.slice(1)) {
|
|
2139
|
+
const separator = line.indexOf(": ");
|
|
2140
|
+
const key = separator < 0 ? "" : line.slice(0, separator);
|
|
2141
|
+
const value = separator < 0 ? "" : line.slice(separator + 2).trim();
|
|
2142
|
+
const listKey = (key === "artifacts" || key === "evidence" || key === "missing") && value.startsWith("[") && value.endsWith("]") ? key : void 0;
|
|
2143
|
+
if (key === "needs" && value.length > 0) needs = value;
|
|
2144
|
+
else if (listKey !== void 0) lists[listKey] = bracketedListOf(value);
|
|
2145
|
+
else messageParts.push(line);
|
|
2146
|
+
}
|
|
2147
|
+
const message = messageParts.join("\n").trim();
|
|
2148
|
+
if (message.length === 0) return null;
|
|
2149
|
+
return {
|
|
2150
|
+
requestType,
|
|
2151
|
+
message,
|
|
2152
|
+
needs,
|
|
2153
|
+
artifacts: lists["artifacts"],
|
|
2154
|
+
evidence: lists["evidence"],
|
|
2155
|
+
missing: lists["missing"]
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
/** Build the transcript notice. `name` is absent on replay. */
|
|
2159
|
+
function renderChildRequestNotice(fields, name) {
|
|
2160
|
+
const type = t(TYPE_KEY[fields.requestType]);
|
|
2161
|
+
const title = name === void 0 ? t("transcript.child_request_anon", { type }) : t("transcript.child_request", {
|
|
2162
|
+
name,
|
|
2163
|
+
type
|
|
2164
|
+
});
|
|
2165
|
+
const parts = [previewText(fields.message)];
|
|
2166
|
+
if (fields.needs !== void 0) parts.push(t("transcript.child_request_needs", { value: previewText(fields.needs) }));
|
|
2167
|
+
for (const entry of [
|
|
2168
|
+
["artifacts", fields.artifacts],
|
|
2169
|
+
["evidence", fields.evidence],
|
|
2170
|
+
["missing", fields.missing]
|
|
2171
|
+
]) {
|
|
2172
|
+
const preview = previewedList(entry[0], entry[1]);
|
|
2173
|
+
if (preview !== void 0) parts.push(preview);
|
|
2174
|
+
}
|
|
2175
|
+
return {
|
|
2176
|
+
title,
|
|
2177
|
+
detail: parts.join(" · ")
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
//#endregion
|
|
2028
2181
|
//#region src/tui/utils/subagent-slots.ts
|
|
2029
2182
|
/** Default subagent types, in the fixed display order of the slots. */
|
|
2030
2183
|
const DEFAULT_SUBAGENT_TYPES = [
|
|
@@ -2037,6 +2190,10 @@ const DEFAULT_SUBAGENT_TYPES = [
|
|
|
2037
2190
|
"worker",
|
|
2038
2191
|
"writer"
|
|
2039
2192
|
];
|
|
2193
|
+
/** How long a `requesting` overlay stays visible after a subagent asks the
|
|
2194
|
+
* parent for help. The request itself returns at once and the agent keeps
|
|
2195
|
+
* working, so this is a display window, not a blocked state. */
|
|
2196
|
+
const REQUESTING_WINDOW_MS = 5e3;
|
|
2040
2197
|
var SubagentSlots = class {
|
|
2041
2198
|
slots = /* @__PURE__ */ new Map();
|
|
2042
2199
|
instances = /* @__PURE__ */ new Map();
|
|
@@ -2050,6 +2207,10 @@ var SubagentSlots = class {
|
|
|
2050
2207
|
* the first open window; concurrent windows on the same type keep counting
|
|
2051
2208
|
* and the slot only exits messaging when the last window closes. */
|
|
2052
2209
|
messagingPrev = /* @__PURE__ */ new Map();
|
|
2210
|
+
/** Slot type → open `requesting` overlay. Keyed by type so several live
|
|
2211
|
+
* instances of one type share a single marker; the real activity state stays
|
|
2212
|
+
* untouched in the slot and shows back up once the overlay expires. */
|
|
2213
|
+
requesting = /* @__PURE__ */ new Map();
|
|
2053
2214
|
/** Count of open messaging windows per type (handles same-type siblings). */
|
|
2054
2215
|
messagingCount = /* @__PURE__ */ new Map();
|
|
2055
2216
|
reset() {
|
|
@@ -2059,6 +2220,7 @@ var SubagentSlots = class {
|
|
|
2059
2220
|
this.messagingByCall.clear();
|
|
2060
2221
|
this.messagingPrev.clear();
|
|
2061
2222
|
this.messagingCount.clear();
|
|
2223
|
+
this.requesting.clear();
|
|
2062
2224
|
}
|
|
2063
2225
|
/**
|
|
2064
2226
|
* subagent.spawned. A first spawn starts the slot as working; a second
|
|
@@ -2098,6 +2260,7 @@ var SubagentSlots = class {
|
|
|
2098
2260
|
onTerminated(agentId) {
|
|
2099
2261
|
const instance = this.instances.get(agentId);
|
|
2100
2262
|
if (instance === void 0) return;
|
|
2263
|
+
if (this.requesting.get(instance.type)?.agentId === agentId) this.requesting.delete(instance.type);
|
|
2101
2264
|
const remaining = (this.runningByType.get(instance.type) ?? 1) - 1;
|
|
2102
2265
|
if (remaining > 0) {
|
|
2103
2266
|
this.runningByType.set(instance.type, remaining);
|
|
@@ -2126,6 +2289,21 @@ var SubagentSlots = class {
|
|
|
2126
2289
|
}
|
|
2127
2290
|
this.setState(instance.type, kind === "tool" ? "working" : "outputting", agentId, detail);
|
|
2128
2291
|
}
|
|
2292
|
+
/** A subagent asked the parent for help. The request returns immediately and
|
|
2293
|
+
* the agent keeps working, so this only raises a transient overlay — the real
|
|
2294
|
+
* activity state stays in the slot and shows back up when it expires. While
|
|
2295
|
+
* `messaging` is on screen the overlay loses (see visibleSlot). */
|
|
2296
|
+
onRequesting(agentId, at = Date.now()) {
|
|
2297
|
+
const instance = this.instances.get(agentId);
|
|
2298
|
+
if (instance === void 0) return;
|
|
2299
|
+
this.requesting.set(instance.type, {
|
|
2300
|
+
until: at + REQUESTING_WINDOW_MS,
|
|
2301
|
+
agentId
|
|
2302
|
+
});
|
|
2303
|
+
const slot = this.ensureSlot(instance.type);
|
|
2304
|
+
slot.agentId = agentId;
|
|
2305
|
+
slot.lastActivityAt = at;
|
|
2306
|
+
}
|
|
2129
2307
|
/**
|
|
2130
2308
|
* Main-side SendSubagentMessage tool call started: the target subagent's
|
|
2131
2309
|
* slot shows messaging until the tool call returns.
|
|
@@ -2135,6 +2313,7 @@ var SubagentSlots = class {
|
|
|
2135
2313
|
const instance = this.instances.get(targetAgentId);
|
|
2136
2314
|
if (instance === void 0) return;
|
|
2137
2315
|
this.messagingByCall.set(toolCallId, targetAgentId);
|
|
2316
|
+
this.requesting.delete(instance.type);
|
|
2138
2317
|
const slot = this.ensureSlot(instance.type);
|
|
2139
2318
|
if (slot.status !== "messaging") this.messagingPrev.set(instance.type, slot.status);
|
|
2140
2319
|
this.messagingCount.set(instance.type, (this.messagingCount.get(instance.type) ?? 0) + 1);
|
|
@@ -2166,24 +2345,31 @@ var SubagentSlots = class {
|
|
|
2166
2345
|
* All slots in fixed display order: the eight default types first (always
|
|
2167
2346
|
* present, idle when never used), then extra types in spawn order.
|
|
2168
2347
|
*/
|
|
2169
|
-
getSlots() {
|
|
2348
|
+
getSlots(at = Date.now()) {
|
|
2349
|
+
this.dropExpiredOverlays(at);
|
|
2170
2350
|
const ordered = [];
|
|
2171
|
-
for (const type of DEFAULT_SUBAGENT_TYPES)
|
|
2172
|
-
const slot = this.ensureSlot(type);
|
|
2173
|
-
ordered.push({
|
|
2174
|
-
...slot,
|
|
2175
|
-
count: this.runningByType.get(type) ?? 0
|
|
2176
|
-
});
|
|
2177
|
-
}
|
|
2351
|
+
for (const type of DEFAULT_SUBAGENT_TYPES) ordered.push(this.visibleSlot(type));
|
|
2178
2352
|
for (const slot of this.slots.values()) {
|
|
2179
2353
|
if (DEFAULT_SUBAGENT_TYPES.includes(slot.type)) continue;
|
|
2180
|
-
ordered.push(
|
|
2181
|
-
...slot,
|
|
2182
|
-
count: this.runningByType.get(slot.type) ?? 0
|
|
2183
|
-
});
|
|
2354
|
+
ordered.push(this.visibleSlot(slot.type));
|
|
2184
2355
|
}
|
|
2185
2356
|
return ordered;
|
|
2186
2357
|
}
|
|
2358
|
+
/** Display view of one slot: the real state with an unexpired `requesting`
|
|
2359
|
+
* overlay on top. Read-path only — it never mutates. Callers must run
|
|
2360
|
+
* {@link dropExpiredOverlays} first, and messaging clears the overlay in
|
|
2361
|
+
* {@link onMessagingStart}; the `messaging` guard only covers same-tick ordering. */
|
|
2362
|
+
visibleSlot(type) {
|
|
2363
|
+
const copy = {
|
|
2364
|
+
...this.ensureSlot(type),
|
|
2365
|
+
count: this.runningByType.get(type) ?? 0
|
|
2366
|
+
};
|
|
2367
|
+
if (this.requesting.has(type) && copy.status !== "messaging") copy.status = "requesting";
|
|
2368
|
+
return copy;
|
|
2369
|
+
}
|
|
2370
|
+
dropExpiredOverlays(at) {
|
|
2371
|
+
for (const [type, overlay] of this.requesting) if (at >= overlay.until) this.requesting.delete(type);
|
|
2372
|
+
}
|
|
2187
2373
|
ensureSlot(type) {
|
|
2188
2374
|
let slot = this.slots.get(type);
|
|
2189
2375
|
if (slot === void 0) {
|
|
@@ -2580,6 +2766,15 @@ var SessionEventHandler = class {
|
|
|
2580
2766
|
subagentSlots = new SubagentSlots();
|
|
2581
2767
|
/** Tool callId of the in-flight UpdateGoal(complete) (judging), if any. */
|
|
2582
2768
|
goalJudgeCallId;
|
|
2769
|
+
/** Rolling first-token latencies of real requests, feeding the sidebar Hub
|
|
2770
|
+
* provider row. In-process only, and dropped whenever the model changes —
|
|
2771
|
+
* a different provider is a different network path. */
|
|
2772
|
+
providerLatencySamples = [];
|
|
2773
|
+
providerLatencyModel;
|
|
2774
|
+
providerLatencyAt;
|
|
2775
|
+
/** Enough samples to smooth one slow request, few enough that a recovered
|
|
2776
|
+
* endpoint reads as recovered again quickly. */
|
|
2777
|
+
providerLatencyWindow = 3;
|
|
2583
2778
|
renderedSkillActivationIds = /* @__PURE__ */ new Set();
|
|
2584
2779
|
renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
|
|
2585
2780
|
mcpServerStatusSpinners = /* @__PURE__ */ new Map();
|
|
@@ -2595,6 +2790,9 @@ var SessionEventHandler = class {
|
|
|
2595
2790
|
this.pendingSkillCandidates = [];
|
|
2596
2791
|
this.promptedSkillCandidates.clear();
|
|
2597
2792
|
this.goalJudgeCallId = void 0;
|
|
2793
|
+
this.providerLatencySamples = [];
|
|
2794
|
+
this.providerLatencyModel = void 0;
|
|
2795
|
+
this.providerLatencyAt = void 0;
|
|
2598
2796
|
this.subagentUsageSeen.clear();
|
|
2599
2797
|
this.host.setAppState({
|
|
2600
2798
|
subagentUsage: {},
|
|
@@ -2768,6 +2966,41 @@ var SessionEventHandler = class {
|
|
|
2768
2966
|
getSubagentSlots() {
|
|
2769
2967
|
return this.subagentSlots.getSlots();
|
|
2770
2968
|
}
|
|
2969
|
+
/** Keep the last few first-token latencies, per provider. Steps without a
|
|
2970
|
+
* measured latency (no stream, cached turn) are ignored rather than counted
|
|
2971
|
+
* as a zero. */
|
|
2972
|
+
recordProviderLatency(event) {
|
|
2973
|
+
const latency = event.llmFirstTokenLatencyMs;
|
|
2974
|
+
if (latency === void 0) return;
|
|
2975
|
+
const model = this.host.state.appState.model;
|
|
2976
|
+
if (this.providerLatencyModel !== void 0 && model !== this.providerLatencyModel) this.providerLatencySamples = [];
|
|
2977
|
+
this.providerLatencyModel = model;
|
|
2978
|
+
this.providerLatencySamples.push(latency);
|
|
2979
|
+
if (this.providerLatencySamples.length > this.providerLatencyWindow) this.providerLatencySamples.shift();
|
|
2980
|
+
this.providerLatencyAt = Date.now();
|
|
2981
|
+
}
|
|
2982
|
+
/** Sidebar Hub: average measured response time for the provider in use. The
|
|
2983
|
+
* caller owns tone and staleness — this stays a plain reading. */
|
|
2984
|
+
getProviderLatency() {
|
|
2985
|
+
if (this.providerLatencySamples.length === 0) return {
|
|
2986
|
+
ms: void 0,
|
|
2987
|
+
sampledAt: void 0
|
|
2988
|
+
};
|
|
2989
|
+
if (this.host.state.appState.model !== this.providerLatencyModel) {
|
|
2990
|
+
this.providerLatencySamples = [];
|
|
2991
|
+
this.providerLatencyModel = void 0;
|
|
2992
|
+
this.providerLatencyAt = void 0;
|
|
2993
|
+
return {
|
|
2994
|
+
ms: void 0,
|
|
2995
|
+
sampledAt: void 0
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
const total = this.providerLatencySamples.reduce((sum, value) => sum + value, 0);
|
|
2999
|
+
return {
|
|
3000
|
+
ms: Math.round(total / this.providerLatencySamples.length),
|
|
3001
|
+
sampledAt: this.providerLatencyAt
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
2771
3004
|
/** Feed routed subagent activity into the slot state machine. */
|
|
2772
3005
|
feedSubagentSlots(event, agentId) {
|
|
2773
3006
|
switch (event.type) {
|
|
@@ -2782,7 +3015,10 @@ var SessionEventHandler = class {
|
|
|
2782
3015
|
this.subagentSlots.onTerminated(event.subagentId);
|
|
2783
3016
|
break;
|
|
2784
3017
|
case "tool.call.started":
|
|
2785
|
-
|
|
3018
|
+
if (event.name === "ContactParent") {
|
|
3019
|
+
this.subagentSlots.onRequesting(agentId);
|
|
3020
|
+
this.appendChildRequestNotice(agentId, event.args);
|
|
3021
|
+
} else this.subagentSlots.onActivity(agentId, "tool", `tool: ${event.name}`);
|
|
2786
3022
|
break;
|
|
2787
3023
|
case "tool.call.delta": break;
|
|
2788
3024
|
case "tool.result":
|
|
@@ -2795,6 +3031,26 @@ var SessionEventHandler = class {
|
|
|
2795
3031
|
default: break;
|
|
2796
3032
|
}
|
|
2797
3033
|
}
|
|
3034
|
+
/**
|
|
3035
|
+
* Surface a child→parent collaboration request in the transcript the moment
|
|
3036
|
+
* the subagent asks, so the human sees it without expanding the agent card.
|
|
3037
|
+
* The request always targets the main agent (one subagent host per session),
|
|
3038
|
+
* which is why a top-level transcript entry is the right home for it.
|
|
3039
|
+
*/
|
|
3040
|
+
appendChildRequestNotice(agentId, args) {
|
|
3041
|
+
const fields = childRequestFieldsFromArgs(argsRecord(args));
|
|
3042
|
+
if (fields === null) return;
|
|
3043
|
+
const notice = renderChildRequestNotice(fields, this.subagentInfo.get(agentId)?.name);
|
|
3044
|
+
this.host.appendTranscriptEntry({
|
|
3045
|
+
id: nextTranscriptId(),
|
|
3046
|
+
kind: "status",
|
|
3047
|
+
turnId: this.host.streamingUI.getTurnContext().turnId,
|
|
3048
|
+
renderMode: "notice",
|
|
3049
|
+
content: notice.title,
|
|
3050
|
+
detail: notice.detail,
|
|
3051
|
+
noticeMarkerColor: this.host.state.theme.colors.warning
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
2798
3054
|
routeSubagentEvent(event) {
|
|
2799
3055
|
const subagentId = event.agentId;
|
|
2800
3056
|
if (subagentId === "main") return false;
|
|
@@ -2912,6 +3168,7 @@ var SessionEventHandler = class {
|
|
|
2912
3168
|
handleStepCompleted(event) {
|
|
2913
3169
|
this.host.streamingUI.flushNow();
|
|
2914
3170
|
this.maybeShowDebugTiming(event);
|
|
3171
|
+
this.recordProviderLatency(event);
|
|
2915
3172
|
this.drainQueuedMessagesIntoSteer();
|
|
2916
3173
|
const prev = this.host.state.appState.sessionUsage;
|
|
2917
3174
|
const apiCalls = this.host.state.appState.sessionApiCalls + 1;
|
|
@@ -4018,6 +4275,10 @@ var SessionReplayRenderer = class {
|
|
|
4018
4275
|
this.renderCronMissed(context, message.origin);
|
|
4019
4276
|
return;
|
|
4020
4277
|
}
|
|
4278
|
+
if (message.origin?.kind === "system_trigger") {
|
|
4279
|
+
this.renderSystemTrigger(context, message);
|
|
4280
|
+
return;
|
|
4281
|
+
}
|
|
4021
4282
|
this.flushAssistant(context);
|
|
4022
4283
|
const skill = skillActivationFromOrigin(message.origin);
|
|
4023
4284
|
if (skill !== void 0) {
|
|
@@ -4028,6 +4289,26 @@ var SessionReplayRenderer = class {
|
|
|
4028
4289
|
this.advanceTurn(context);
|
|
4029
4290
|
this.host.appendTranscriptEntry(replayEntry(context, "user", contentPartsToText(message.content), "plain"));
|
|
4030
4291
|
}
|
|
4292
|
+
/**
|
|
4293
|
+
* `system_trigger` messages are internal injections, not user speech: the
|
|
4294
|
+
* live transcript never prints them and markdown export filters them out, so
|
|
4295
|
+
* replay must not surface them as a user row either. The single exception is
|
|
4296
|
+
* a child→parent collaboration request, which gets the same notice the live
|
|
4297
|
+
* path shows. Replay has no spawn events, so the subagent's display name is
|
|
4298
|
+
* gone and the name-less title variant keeps the notice honest.
|
|
4299
|
+
*/
|
|
4300
|
+
renderSystemTrigger(context, message) {
|
|
4301
|
+
const origin = message.origin;
|
|
4302
|
+
if (origin?.kind !== "system_trigger" || origin.name !== "child_request") return;
|
|
4303
|
+
const fields = childRequestFieldsFromNotification(contentPartsToText(message.content));
|
|
4304
|
+
if (fields === null) return;
|
|
4305
|
+
const notice = renderChildRequestNotice(fields);
|
|
4306
|
+
this.flushAssistant(context);
|
|
4307
|
+
this.host.appendTranscriptEntry({
|
|
4308
|
+
...replayEntry(context, "status", notice.title, "notice", { detail: notice.detail }),
|
|
4309
|
+
noticeMarkerColor: this.host.state.theme.colors.warning
|
|
4310
|
+
});
|
|
4311
|
+
}
|
|
4031
4312
|
renderToolCalls(context, toolCalls) {
|
|
4032
4313
|
if (toolCalls.length === 0) return;
|
|
4033
4314
|
const { streamingUI } = this.host;
|
|
@@ -6267,7 +6548,7 @@ var CommittedMessageComponent = class {
|
|
|
6267
6548
|
return new Text(` ${chalk.hex(colors.textDim)(t("transcript.tool_name", { name }))}${summary}`, 0, 0).render(width);
|
|
6268
6549
|
}
|
|
6269
6550
|
case "status":
|
|
6270
|
-
if (entry.renderMode === "notice") return new NoticeMessageComponent(entry.content, entry.detail, colors).render(width);
|
|
6551
|
+
if (entry.renderMode === "notice") return new NoticeMessageComponent(entry.content, entry.detail, colors, entry.noticeMarkerColor).render(width);
|
|
6271
6552
|
return new StatusMessageComponent(entry.content, colors, entry.color).render(width);
|
|
6272
6553
|
case "skill_activation": {
|
|
6273
6554
|
const text = entry.skillName ?? entry.content;
|
|
@@ -6416,10 +6697,10 @@ var TranscriptController = class TranscriptController {
|
|
|
6416
6697
|
return tc;
|
|
6417
6698
|
}
|
|
6418
6699
|
if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
|
|
6419
|
-
return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
|
|
6700
|
+
return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors, entry.noticeMarkerColor) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
|
|
6420
6701
|
case "status":
|
|
6421
6702
|
if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
|
|
6422
|
-
return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
|
|
6703
|
+
return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors, entry.noticeMarkerColor) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
|
|
6423
6704
|
case "cron":
|
|
6424
6705
|
if (entry.cronData === void 0) return null;
|
|
6425
6706
|
return new CronMessageComponent(entry.content, entry.cronData, state.theme.colors);
|
|
@@ -6668,6 +6949,55 @@ function clampSidebarWidth(cols) {
|
|
|
6668
6949
|
return Math.min(60, Math.max(24, Math.round(cols)));
|
|
6669
6950
|
}
|
|
6670
6951
|
//#endregion
|
|
6952
|
+
//#region src/tui/utils/display-width.ts
|
|
6953
|
+
/** Display-column width of a string, matching the terminal layout engine
|
|
6954
|
+
* (ANSI sequences stripped, CJK/wide chars counted as 2 columns). */
|
|
6955
|
+
function displayWidth(text) {
|
|
6956
|
+
return visibleWidth(text);
|
|
6957
|
+
}
|
|
6958
|
+
/** Pad `label` to `target` display columns with spaces (CJK-aware). */
|
|
6959
|
+
function padLabel(label, target) {
|
|
6960
|
+
return label + " ".repeat(Math.max(0, target - displayWidth(label)));
|
|
6961
|
+
}
|
|
6962
|
+
function layoutSidebarGrid(rows, width) {
|
|
6963
|
+
const valueCols = Math.max(0, ...rows.map((row) => displayWidth(row.value ?? "")));
|
|
6964
|
+
const labelBudget = Math.max(1, width - 2 - Math.max(valueCols, 1) - 1);
|
|
6965
|
+
const labelCols = Math.min(12, labelBudget);
|
|
6966
|
+
const valueStart = Math.max(2 + labelCols + 1, width - valueCols);
|
|
6967
|
+
return rows.map((row) => {
|
|
6968
|
+
const marker = padLabel(row.marker ?? "", 2);
|
|
6969
|
+
const label = truncateToWidth(row.label, labelCols);
|
|
6970
|
+
const used = 2 + displayWidth(label);
|
|
6971
|
+
if (row.value !== void 0) {
|
|
6972
|
+
const gap = Math.max(1, width - used - displayWidth(row.value));
|
|
6973
|
+
const room = Math.max(1, width - used - gap);
|
|
6974
|
+
return {
|
|
6975
|
+
marker,
|
|
6976
|
+
label,
|
|
6977
|
+
gap,
|
|
6978
|
+
value: truncateToWidth(row.value, room)
|
|
6979
|
+
};
|
|
6980
|
+
}
|
|
6981
|
+
if (row.note !== void 0) {
|
|
6982
|
+
const noteCols = displayWidth(row.note);
|
|
6983
|
+
const aligned = Math.max(1, valueStart - used);
|
|
6984
|
+
const fits = Math.max(1, width - used - noteCols);
|
|
6985
|
+
const gap = Math.min(aligned, fits);
|
|
6986
|
+
return {
|
|
6987
|
+
marker,
|
|
6988
|
+
label,
|
|
6989
|
+
gap,
|
|
6990
|
+
note: truncateToWidth(row.note, Math.max(1, width - used - gap))
|
|
6991
|
+
};
|
|
6992
|
+
}
|
|
6993
|
+
return {
|
|
6994
|
+
marker,
|
|
6995
|
+
label,
|
|
6996
|
+
gap: 1
|
|
6997
|
+
};
|
|
6998
|
+
});
|
|
6999
|
+
}
|
|
7000
|
+
//#endregion
|
|
6671
7001
|
//#region src/tui/components/sidebar/panels/git-panel.ts
|
|
6672
7002
|
/**
|
|
6673
7003
|
* Summary-only Git panel (摘要式): the workdir name plus added (green) /
|
|
@@ -6683,12 +7013,13 @@ var GitPanelContent = class {
|
|
|
6683
7013
|
}
|
|
6684
7014
|
invalidate() {}
|
|
6685
7015
|
render(width) {
|
|
7016
|
+
const indent = " ".repeat(2);
|
|
6686
7017
|
const git = this.ctx.getData().git;
|
|
6687
|
-
if (git === void 0) return [chalk.hex(this.colors.textDim)(t("sidebar.git_no_repo"))];
|
|
7018
|
+
if (git === void 0) return [`${indent}${chalk.hex(this.colors.textDim)(t("sidebar.git_no_repo"))}`];
|
|
6688
7019
|
const dim = (s) => chalk.hex(this.colors.textDim)(s);
|
|
6689
|
-
const workDir = truncateToWidth(chalk.hex(this.colors.text)(basename(git.workDir))
|
|
6690
|
-
if (git.diffAdded === 0 && git.diffDeleted === 0 && git.filesCount === 0) return [workDir, dim(t("sidebar.git_clean"))];
|
|
6691
|
-
return [workDir, truncateToWidth(`${chalk.hex(this.colors.success)(`+${git.diffAdded}`)} ${chalk.hex(this.colors.error)(`−${git.diffDeleted}`)} ${dim(t("sidebar.git_files", { count: git.filesCount }))}`, width)];
|
|
7020
|
+
const workDir = truncateToWidth(`${indent}${chalk.hex(this.colors.text)(basename(git.workDir))}`, width);
|
|
7021
|
+
if (git.diffAdded === 0 && git.diffDeleted === 0 && git.filesCount === 0) return [workDir, dim(`${indent}${t("sidebar.git_clean")}`)];
|
|
7022
|
+
return [workDir, truncateToWidth(`${indent}${chalk.hex(this.colors.success)(`+${git.diffAdded}`)} ${chalk.hex(this.colors.error)(`−${git.diffDeleted}`)} ${dim(t("sidebar.git_files", { count: git.filesCount }))}`, width)];
|
|
6692
7023
|
}
|
|
6693
7024
|
};
|
|
6694
7025
|
const gitPanel = {
|
|
@@ -6702,35 +7033,6 @@ const gitPanel = {
|
|
|
6702
7033
|
}
|
|
6703
7034
|
};
|
|
6704
7035
|
//#endregion
|
|
6705
|
-
//#region src/tui/utils/display-width.ts
|
|
6706
|
-
/** Display-column width of a string, matching the terminal layout engine
|
|
6707
|
-
* (ANSI sequences stripped, CJK/wide chars counted as 2 columns). */
|
|
6708
|
-
function displayWidth(text) {
|
|
6709
|
-
return visibleWidth(text);
|
|
6710
|
-
}
|
|
6711
|
-
/** Pad `label` to `target` display columns with spaces (CJK-aware). */
|
|
6712
|
-
function padLabel(label, target) {
|
|
6713
|
-
return label + " ".repeat(Math.max(0, target - displayWidth(label)));
|
|
6714
|
-
}
|
|
6715
|
-
/** Pad `value` on the LEFT to `target` display columns (CJK-aware), which is
|
|
6716
|
-
* what right-aligning a numeric column means in terminal space. */
|
|
6717
|
-
function padValue(value, target) {
|
|
6718
|
-
return " ".repeat(Math.max(0, target - displayWidth(value))) + value;
|
|
6719
|
-
}
|
|
6720
|
-
function alignMetricRows(rows, opts = {}) {
|
|
6721
|
-
const gap = opts.gap ?? 2;
|
|
6722
|
-
const labels = [...rows.map(([label]) => label), ...opts.extraLabels ?? []];
|
|
6723
|
-
const labelCols = labels.length === 0 ? 0 : Math.max(...labels.map((label) => displayWidth(label)));
|
|
6724
|
-
const measured = rows.length === 0 ? 0 : Math.max(...rows.map(([, value]) => displayWidth(value)));
|
|
6725
|
-
const valueCols = opts.maxValueCols === void 0 ? measured : Math.min(measured, Math.max(0, opts.maxValueCols));
|
|
6726
|
-
const separator = " ".repeat(Math.max(0, gap));
|
|
6727
|
-
return {
|
|
6728
|
-
labelCols,
|
|
6729
|
-
valueCols,
|
|
6730
|
-
rows: rows.map(([label, value]) => [padLabel(label, labelCols) + separator, padValue(truncateToWidth(value, valueCols), valueCols)])
|
|
6731
|
-
};
|
|
6732
|
-
}
|
|
6733
|
-
//#endregion
|
|
6734
7036
|
//#region src/tui/components/sidebar/panels/session-panel.ts
|
|
6735
7037
|
function formatDuration(ms) {
|
|
6736
7038
|
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
@@ -6766,10 +7068,10 @@ function formatCompact(n) {
|
|
|
6766
7068
|
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
6767
7069
|
return String(n);
|
|
6768
7070
|
}
|
|
6769
|
-
/** True when
|
|
7071
|
+
/** True when the shared grid can show these values at full width: the label
|
|
7072
|
+
* column is fixed across panels, so only the value column decides the fit. */
|
|
6770
7073
|
function gridFits(rows, width) {
|
|
6771
|
-
|
|
6772
|
-
return grid.labelCols + 2 + grid.valueCols <= width;
|
|
7074
|
+
return 15 + Math.max(0, ...rows.map(([, value]) => displayWidth(value))) <= width;
|
|
6773
7075
|
}
|
|
6774
7076
|
/**
|
|
6775
7077
|
* Session summary, live-rendered each frame as a strict two-column table:
|
|
@@ -6806,10 +7108,11 @@ var SessionPanelContent = class {
|
|
|
6806
7108
|
];
|
|
6807
7109
|
const withTokens = (compact) => [...tokenRows.map(([name, n]) => [name, compact ? formatCompact(n) : formatCount$1(n)]), ...countRows];
|
|
6808
7110
|
const full = withTokens(false);
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
7111
|
+
return layoutSidebarGrid((gridFits(full, width) ? full : withTokens(true)).map(([name, reading]) => ({
|
|
7112
|
+
marker: "●",
|
|
7113
|
+
label: name,
|
|
7114
|
+
value: reading
|
|
7115
|
+
})), width).map((cell) => `${label(cell.marker)}${label(cell.label)}${" ".repeat(cell.gap)}${value(cell.value ?? "")}`);
|
|
6813
7116
|
}
|
|
6814
7117
|
};
|
|
6815
7118
|
const sessionPanel = {
|
|
@@ -6836,6 +7139,7 @@ function statusText(status) {
|
|
|
6836
7139
|
case "outputting": return t("sidebar.agent_outputting");
|
|
6837
7140
|
case "messaging": return t("sidebar.agent_messaging");
|
|
6838
7141
|
case "reworking": return t("sidebar.agent_reworking");
|
|
7142
|
+
case "requesting": return t("sidebar.agent_requesting");
|
|
6839
7143
|
}
|
|
6840
7144
|
}
|
|
6841
7145
|
/**
|
|
@@ -6854,17 +7158,22 @@ var AgentsPanelContent = class {
|
|
|
6854
7158
|
render(width) {
|
|
6855
7159
|
const agents = this.ctx.getData().agents;
|
|
6856
7160
|
if (agents === void 0 || agents.length === 0) return [];
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
7161
|
+
return layoutSidebarGrid(agents.map((slot) => ({
|
|
7162
|
+
marker: slot.status === "idle" ? "○" : "●",
|
|
7163
|
+
label: slot.type,
|
|
7164
|
+
value: statusText(slot.status) + (slot.count > 1 ? ` ×${slot.count}` : "")
|
|
7165
|
+
})), width).map((cell, index) => this.renderSlot(cell, agents[index], index, width));
|
|
6861
7166
|
}
|
|
6862
|
-
renderSlot(slot, index,
|
|
7167
|
+
renderSlot(cell, slot, index, width) {
|
|
6863
7168
|
const dim = (s) => chalk.hex(this.colors.textDim)(s);
|
|
6864
|
-
|
|
7169
|
+
const idle = slot.status === "idle";
|
|
6865
7170
|
const phase = (Date.now() % AGENT_GRADIENT_CYCLE_MS / AGENT_GRADIENT_CYCLE_MS + index * .125) % 1;
|
|
6866
7171
|
const active = slot.status === "working" || slot.status === "outputting" ? chalk.hex(lerpGradient(phase)).bold : void 0;
|
|
6867
|
-
|
|
7172
|
+
const marker = padLabel((active ?? chalk.hex(this.statusColor(slot.status)))(idle ? "○" : "●"), 2);
|
|
7173
|
+
const label = (idle ? dim : chalk.hex(this.colors.textStrong))(cell.label);
|
|
7174
|
+
const paint = active ?? (idle ? dim : slot.status === "requesting" ? chalk.hex(this.colors.warning) : chalk.hex(this.colors.text));
|
|
7175
|
+
const count = slot.count > 1 ? dim(` ×${slot.count}`) : "";
|
|
7176
|
+
return truncateToWidth(`${marker}${label}${" ".repeat(cell.gap)}${paint(statusText(slot.status))}${count}`, width);
|
|
6868
7177
|
}
|
|
6869
7178
|
statusColor(status) {
|
|
6870
7179
|
switch (status) {
|
|
@@ -6872,6 +7181,7 @@ var AgentsPanelContent = class {
|
|
|
6872
7181
|
case "outputting": return this.colors.primary;
|
|
6873
7182
|
case "messaging": return this.colors.accent;
|
|
6874
7183
|
case "reworking": return this.colors.warning;
|
|
7184
|
+
case "requesting": return this.colors.warning;
|
|
6875
7185
|
case "idle": return this.colors.textDim;
|
|
6876
7186
|
}
|
|
6877
7187
|
}
|
|
@@ -6929,26 +7239,48 @@ var GoalPanelContent = class {
|
|
|
6929
7239
|
const wallMs = goal.status === "active" ? goal.wallClockMs + Math.max(0, now - goal.wallClockBaseAt) : goal.wallClockMs;
|
|
6930
7240
|
const lines = [];
|
|
6931
7241
|
lines.push(this.renderBadge(goal.status, goal.judge, dim));
|
|
6932
|
-
lines.push(truncateToWidth(dim(goal.objective), width));
|
|
7242
|
+
lines.push(truncateToWidth(dim(`● ${goal.objective}`), width));
|
|
6933
7243
|
const rows = [
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
7244
|
+
{
|
|
7245
|
+
marker: "●",
|
|
7246
|
+
label: t("sidebar.goal_tokens"),
|
|
7247
|
+
value: formatCount(goal.tokensUsed)
|
|
7248
|
+
},
|
|
7249
|
+
{
|
|
7250
|
+
marker: "●",
|
|
7251
|
+
label: t("sidebar.goal_tokens_input"),
|
|
7252
|
+
value: goal.inputTokens === null ? "—" : formatCount(goal.inputTokens)
|
|
7253
|
+
},
|
|
7254
|
+
{
|
|
7255
|
+
marker: "●",
|
|
7256
|
+
label: t("sidebar.goal_tokens_output"),
|
|
7257
|
+
value: goal.outputTokens === null ? "—" : formatCount(goal.outputTokens)
|
|
7258
|
+
},
|
|
7259
|
+
{
|
|
7260
|
+
marker: "●",
|
|
7261
|
+
label: t("sidebar.goal_turns"),
|
|
7262
|
+
value: String(goal.turnsUsed)
|
|
7263
|
+
},
|
|
7264
|
+
{
|
|
7265
|
+
marker: "●",
|
|
7266
|
+
label: t("sidebar.goal_wall"),
|
|
7267
|
+
value: formatWallClock(wallMs)
|
|
7268
|
+
},
|
|
7269
|
+
{
|
|
7270
|
+
marker: "●",
|
|
7271
|
+
label: t("sidebar.goal_judge"),
|
|
7272
|
+
value: judgeText(goal.judge)
|
|
7273
|
+
}
|
|
6940
7274
|
];
|
|
6941
|
-
const
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
const
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
}
|
|
6950
|
-
for (const [label, value] of grid.rows) lines.push(dim(label) + text(value));
|
|
6951
|
-
if (hasCriterion) lines.push(dim(padLabel(criterionLabel, grid.labelCols) + " ".repeat(2)) + truncateToWidth(text(goal.completionCriterion), budget));
|
|
7275
|
+
const gridRows = goal.completionCriterion !== null ? [...rows, {
|
|
7276
|
+
marker: "●",
|
|
7277
|
+
label: t("sidebar.goal_criterion"),
|
|
7278
|
+
note: goal.completionCriterion ?? ""
|
|
7279
|
+
}] : rows;
|
|
7280
|
+
for (const cell of layoutSidebarGrid(gridRows, width)) {
|
|
7281
|
+
const reading = cell.value ?? cell.note ?? "";
|
|
7282
|
+
lines.push(`${dim(`${cell.marker}${cell.label}`)}${" ".repeat(cell.gap)}${text(reading)}`);
|
|
7283
|
+
}
|
|
6952
7284
|
return lines;
|
|
6953
7285
|
}
|
|
6954
7286
|
/** State badge: judging wins over everything; otherwise map the snapshot
|
|
@@ -6974,6 +7306,212 @@ const goalPanel = {
|
|
|
6974
7306
|
return new GoalPanelContent(ctx, ctx.colors);
|
|
6975
7307
|
}
|
|
6976
7308
|
};
|
|
7309
|
+
/** Per-request budget: long enough for a slow CDN, short enough to never hang. */
|
|
7310
|
+
const HUB_PROBE_TIMEOUT_MS = 1500;
|
|
7311
|
+
/** Row id for the model provider, whose latency is measured, never probed. */
|
|
7312
|
+
const HUB_MODEL_ROW_ID = "model";
|
|
7313
|
+
/** A measured provider reading older than this is dimmed: nothing was asked lately. */
|
|
7314
|
+
const HUB_MODEL_STALE_MS = 5 * 6e4;
|
|
7315
|
+
/** Default probe targets: the endpoints day-to-day development tends to need. */
|
|
7316
|
+
const HUB_ENDPOINTS = Object.freeze([
|
|
7317
|
+
{
|
|
7318
|
+
id: "github",
|
|
7319
|
+
url: "https://github.com/"
|
|
7320
|
+
},
|
|
7321
|
+
{
|
|
7322
|
+
id: "google",
|
|
7323
|
+
url: "https://www.google.com/"
|
|
7324
|
+
},
|
|
7325
|
+
{
|
|
7326
|
+
id: "x",
|
|
7327
|
+
url: "https://x.com/",
|
|
7328
|
+
label: "x (twitter)"
|
|
7329
|
+
},
|
|
7330
|
+
{
|
|
7331
|
+
id: "huggingface",
|
|
7332
|
+
url: "https://huggingface.co/"
|
|
7333
|
+
},
|
|
7334
|
+
{
|
|
7335
|
+
id: "alibaba",
|
|
7336
|
+
url: "https://www.aliyun.com/",
|
|
7337
|
+
label: "Alibaba"
|
|
7338
|
+
},
|
|
7339
|
+
{
|
|
7340
|
+
id: "tencent",
|
|
7341
|
+
url: "https://cloud.tencent.com/",
|
|
7342
|
+
label: "Tencent"
|
|
7343
|
+
}
|
|
7344
|
+
]);
|
|
7345
|
+
function defaultFetch(url, init) {
|
|
7346
|
+
return globalThis.fetch(url, init);
|
|
7347
|
+
}
|
|
7348
|
+
function createHubProbe(options = {}) {
|
|
7349
|
+
const endpoints = options.endpoints ?? HUB_ENDPOINTS;
|
|
7350
|
+
const now = options.now ?? Date.now;
|
|
7351
|
+
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
7352
|
+
const sites = /* @__PURE__ */ new Map();
|
|
7353
|
+
for (const endpoint of endpoints) sites.set(endpoint.id, {
|
|
7354
|
+
ms: void 0,
|
|
7355
|
+
failures: 0
|
|
7356
|
+
});
|
|
7357
|
+
let roundAt;
|
|
7358
|
+
let inFlight = false;
|
|
7359
|
+
const runRound = async () => {
|
|
7360
|
+
const startedAt = now();
|
|
7361
|
+
const results = await Promise.allSettled(endpoints.map(async (endpoint) => {
|
|
7362
|
+
const response = await fetchImpl(endpoint.url, {
|
|
7363
|
+
method: "HEAD",
|
|
7364
|
+
redirect: "manual",
|
|
7365
|
+
signal: AbortSignal.timeout(HUB_PROBE_TIMEOUT_MS)
|
|
7366
|
+
});
|
|
7367
|
+
return {
|
|
7368
|
+
id: endpoint.id,
|
|
7369
|
+
ms: Math.max(0, now() - startedAt),
|
|
7370
|
+
status: response.status
|
|
7371
|
+
};
|
|
7372
|
+
}));
|
|
7373
|
+
for (const [index, result] of results.entries()) {
|
|
7374
|
+
const site = sites.get(endpoints[index].id);
|
|
7375
|
+
if (site === void 0) continue;
|
|
7376
|
+
if (result.status === "fulfilled") {
|
|
7377
|
+
site.ms = result.value.ms;
|
|
7378
|
+
site.failures = 0;
|
|
7379
|
+
} else {
|
|
7380
|
+
site.failures += 1;
|
|
7381
|
+
if (site.failures >= 2) site.ms = void 0;
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
roundAt = now();
|
|
7385
|
+
inFlight = false;
|
|
7386
|
+
};
|
|
7387
|
+
return {
|
|
7388
|
+
sampleIfStale: () => {
|
|
7389
|
+
if (inFlight) return;
|
|
7390
|
+
const at = now();
|
|
7391
|
+
if (roundAt !== void 0 && at - roundAt < 3e4) return;
|
|
7392
|
+
inFlight = true;
|
|
7393
|
+
runRound().catch(() => {
|
|
7394
|
+
inFlight = false;
|
|
7395
|
+
});
|
|
7396
|
+
},
|
|
7397
|
+
snapshot: () => {
|
|
7398
|
+
const at = now();
|
|
7399
|
+
const stale = roundAt === void 0 || at - roundAt >= 3e4 * 2;
|
|
7400
|
+
const samples = [];
|
|
7401
|
+
for (const endpoint of endpoints) {
|
|
7402
|
+
const site = sites.get(endpoint.id);
|
|
7403
|
+
samples.push({
|
|
7404
|
+
id: endpoint.id,
|
|
7405
|
+
label: endpoint.label ?? endpoint.id,
|
|
7406
|
+
ms: site.ms,
|
|
7407
|
+
tone: toneFor(site, stale)
|
|
7408
|
+
});
|
|
7409
|
+
}
|
|
7410
|
+
return {
|
|
7411
|
+
samples,
|
|
7412
|
+
roundAt,
|
|
7413
|
+
stale,
|
|
7414
|
+
pending: inFlight
|
|
7415
|
+
};
|
|
7416
|
+
}
|
|
7417
|
+
};
|
|
7418
|
+
}
|
|
7419
|
+
function toneFor(site, stale) {
|
|
7420
|
+
if (stale) return "dim";
|
|
7421
|
+
if (site.failures >= 2) return "down";
|
|
7422
|
+
return hubLatencyTone(site.ms, site.failures > 0);
|
|
7423
|
+
}
|
|
7424
|
+
/**
|
|
7425
|
+
* Merge the measured provider reading with the probe samples into Hub rows.
|
|
7426
|
+
* The provider row always leads (it answers "is the model I'm using reachable"),
|
|
7427
|
+
* and it staleness-checks against its own budget: a long idle stretch means
|
|
7428
|
+
* nothing was asked, not that the network died.
|
|
7429
|
+
*/
|
|
7430
|
+
function buildHubSamples(snapshot, provider, now = Date.now()) {
|
|
7431
|
+
const ageMs = provider.sampledAt === void 0 ? 0 : now - provider.sampledAt;
|
|
7432
|
+
return [{
|
|
7433
|
+
id: HUB_MODEL_ROW_ID,
|
|
7434
|
+
ms: provider.ms,
|
|
7435
|
+
tone: hubLatencyTone(provider.ms, ageMs >= HUB_MODEL_STALE_MS)
|
|
7436
|
+
}, ...snapshot.samples];
|
|
7437
|
+
}
|
|
7438
|
+
/** Shared tone rule for every Hub row (probe sites and the measured provider). */
|
|
7439
|
+
function hubLatencyTone(ms, stale = false) {
|
|
7440
|
+
if (ms === void 0 || stale) return "dim";
|
|
7441
|
+
return ms <= 100 ? "ok" : "warn";
|
|
7442
|
+
}
|
|
7443
|
+
/**
|
|
7444
|
+
* Compact latency reading: whole milliseconds below a second, one-decimal
|
|
7445
|
+
* seconds above — a probe that never answers must never read as `65400ms`.
|
|
7446
|
+
*/
|
|
7447
|
+
function formatHubLatency(ms) {
|
|
7448
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
7449
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
7450
|
+
}
|
|
7451
|
+
//#endregion
|
|
7452
|
+
//#region src/tui/components/sidebar/panels/hub-panel.ts
|
|
7453
|
+
/** Pending marker: three middle dots (Latin-1, so terminal width is stable). */
|
|
7454
|
+
const PENDING_MARK = "···";
|
|
7455
|
+
/** No reading at all — a site that stopped answering, or nothing measured yet. */
|
|
7456
|
+
const EMPTY_MARK = "--";
|
|
7457
|
+
/**
|
|
7458
|
+
* Hub panel: network health at a glance. One row per reading — the measured
|
|
7459
|
+
* model-provider response time first, then a probe round trip for each
|
|
7460
|
+
* well-known endpoint.
|
|
7461
|
+
*
|
|
7462
|
+
* Values share one right edge through the same two-column engine the Session
|
|
7463
|
+
* panel uses, so rows never jitter as numbers change width, and only the value
|
|
7464
|
+
* carries colour (green ≤100ms, amber above it, red when a site stopped
|
|
7465
|
+
* answering). The panel is a pure view: probing happens in the data provider,
|
|
7466
|
+
* which is what keeps a slow endpoint from ever touching a frame.
|
|
7467
|
+
*/
|
|
7468
|
+
var HubPanelContent = class {
|
|
7469
|
+
ctx;
|
|
7470
|
+
colors;
|
|
7471
|
+
constructor(ctx, colors) {
|
|
7472
|
+
this.ctx = ctx;
|
|
7473
|
+
this.colors = colors;
|
|
7474
|
+
}
|
|
7475
|
+
invalidate() {}
|
|
7476
|
+
render(width) {
|
|
7477
|
+
const hub = this.ctx.getData().hub;
|
|
7478
|
+
if (hub === void 0 || hub.samples.length === 0) return [];
|
|
7479
|
+
return layoutSidebarGrid(hub.samples.map((sample) => ({
|
|
7480
|
+
marker: "●",
|
|
7481
|
+
label: this.labelOf(sample),
|
|
7482
|
+
value: this.reading(sample, hub)
|
|
7483
|
+
})), width).map((cell, index) => {
|
|
7484
|
+
const tone = hub.samples[index]?.tone ?? "dim";
|
|
7485
|
+
return `${chalk.hex(this.colors.textDim)(`${cell.marker}${cell.label}`)}${" ".repeat(cell.gap)}${chalk.hex(this.toneColor(tone))(cell.value ?? "")}`;
|
|
7486
|
+
});
|
|
7487
|
+
}
|
|
7488
|
+
labelOf(sample) {
|
|
7489
|
+
return sample.id === "model" ? t("sidebar.hub_model") : sample.label ?? sample.id;
|
|
7490
|
+
}
|
|
7491
|
+
reading(sample, hub) {
|
|
7492
|
+
if (sample.ms !== void 0) return formatHubLatency(sample.ms);
|
|
7493
|
+
if (hub.pending && sample.tone === "dim" && sample.id !== "model") return PENDING_MARK;
|
|
7494
|
+
return EMPTY_MARK;
|
|
7495
|
+
}
|
|
7496
|
+
toneColor(tone) {
|
|
7497
|
+
switch (tone) {
|
|
7498
|
+
case "ok": return this.colors.success;
|
|
7499
|
+
case "warn": return this.colors.warning;
|
|
7500
|
+
case "down": return this.colors.error;
|
|
7501
|
+
case "dim": return this.colors.textDim;
|
|
7502
|
+
}
|
|
7503
|
+
}
|
|
7504
|
+
};
|
|
7505
|
+
const hubPanel = {
|
|
7506
|
+
id: "hub",
|
|
7507
|
+
get title() {
|
|
7508
|
+
return t("sidebar.hub");
|
|
7509
|
+
},
|
|
7510
|
+
width: 30,
|
|
7511
|
+
build(ctx) {
|
|
7512
|
+
return new HubPanelContent(ctx, ctx.colors);
|
|
7513
|
+
}
|
|
7514
|
+
};
|
|
6977
7515
|
//#endregion
|
|
6978
7516
|
//#region src/utils/git/git-status.ts
|
|
6979
7517
|
/**
|
|
@@ -7533,6 +8071,9 @@ var LifecycleController = class LifecycleController {
|
|
|
7533
8071
|
/** Sidebar-only git cache (the footer owns its own). Rebuilt when the
|
|
7534
8072
|
* workDir changes; `getStatus()` is TTL-cached so polling is cheap. */
|
|
7535
8073
|
sidebarGitCache;
|
|
8074
|
+
/** Sidebar Hub probe: read-through TTL, no timer — probing happens because a
|
|
8075
|
+
* frame asked, so a closed sidebar produces no traffic. */
|
|
8076
|
+
hubProbe = createHubProbe();
|
|
7536
8077
|
sidebarGitCacheWorkDir = "";
|
|
7537
8078
|
/** Session start time for the sidebar uptime counter. Reset on session
|
|
7538
8079
|
* switch so a resumed old session doesn't show the TUI process age. */
|
|
@@ -7729,6 +8270,7 @@ var LifecycleController = class LifecycleController {
|
|
|
7729
8270
|
if (!m.allPanels.some((p) => p.id === "session")) m.register(sessionPanel);
|
|
7730
8271
|
if (!m.allPanels.some((p) => p.id === "agents")) m.register(agentsPanel);
|
|
7731
8272
|
if (!m.allPanels.some((p) => p.id === "goal")) m.register(goalPanel);
|
|
8273
|
+
if (!m.allPanels.some((p) => p.id === "hub")) m.register(hubPanel);
|
|
7732
8274
|
m.setDataProvider(() => this.buildSidebarData());
|
|
7733
8275
|
}
|
|
7734
8276
|
const sidebar = this.host.state.sidebarPane;
|
|
@@ -7770,9 +8312,25 @@ var LifecycleController = class LifecycleController {
|
|
|
7770
8312
|
git: this.readSidebarGit(appState.workDir),
|
|
7771
8313
|
sessionStats: this.readSidebarSessionStats(appState),
|
|
7772
8314
|
agents: this.host.sessionEventHandler.getSubagentSlots(),
|
|
8315
|
+
hub: this.readSidebarHub(),
|
|
7773
8316
|
goal: this.readSidebarGoal(appState)
|
|
7774
8317
|
};
|
|
7775
8318
|
}
|
|
8319
|
+
/**
|
|
8320
|
+
* Hub block. Probe rounds are kicked off from the render path only (the
|
|
8321
|
+
* sidebar container stops rendering while closed), and the round is always
|
|
8322
|
+
* fire-and-forget — a dead endpoint can never hold up a frame. The model row
|
|
8323
|
+
* is different: it reads the first-token latency of real requests, so it is
|
|
8324
|
+
* measured rather than probed, and it goes stale on its own budget.
|
|
8325
|
+
*/
|
|
8326
|
+
readSidebarHub() {
|
|
8327
|
+
if (this.host.state.sidebarManager?.isOpen === true) this.hubProbe.sampleIfStale();
|
|
8328
|
+
const snapshot = this.hubProbe.snapshot();
|
|
8329
|
+
return {
|
|
8330
|
+
samples: buildHubSamples(snapshot, this.host.sessionEventHandler.getProviderLatency()),
|
|
8331
|
+
pending: snapshot.pending
|
|
8332
|
+
};
|
|
8333
|
+
}
|
|
7776
8334
|
readSidebarGit(workDir) {
|
|
7777
8335
|
if (this.sidebarGitCache === void 0 || this.sidebarGitCacheWorkDir !== workDir) {
|
|
7778
8336
|
this.sidebarGitCache = createGitStatusCache(workDir);
|
|
@@ -13632,7 +14190,7 @@ var ScreamTUI = class {
|
|
|
13632
14190
|
const card = new OnboardingCardComponent({
|
|
13633
14191
|
colors: this.state.theme.colors,
|
|
13634
14192
|
onConfigure: () => {
|
|
13635
|
-
import("./dispatch-
|
|
14193
|
+
import("./dispatch-5S52okPl.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
|
|
13636
14194
|
this.restoreEditor();
|
|
13637
14195
|
onDone?.();
|
|
13638
14196
|
});
|
|
@@ -14459,15 +15017,13 @@ async function runChannelSetup() {
|
|
|
14459
15017
|
//#endregion
|
|
14460
15018
|
//#region src/cli/run-stream-json.ts
|
|
14461
15019
|
/**
|
|
14462
|
-
*
|
|
14463
|
-
*
|
|
14464
|
-
* Reads stdin in Claude Code stream-json dialect, processes messages through
|
|
14465
|
-
* the ScreamCode agent, and writes stdout in Claude Code dialect. This lets
|
|
14466
|
-
* cc-connect (and any tool that speaks the Claude Code stdio protocol) use
|
|
14467
|
-
* ScreamCode as a drop-in agent backend.
|
|
15020
|
+
* Stream-json stdio adapter (cc-connect compat).
|
|
14468
15021
|
*
|
|
14469
|
-
*
|
|
14470
|
-
*
|
|
15022
|
+
* Reads stdin as line-based stream-json messages, processes them through the
|
|
15023
|
+
* ScreamCode agent, and writes stdout as stream-json events. This is the
|
|
15024
|
+
* integration surface cc-connect drives when it spawns `scream stream-json`,
|
|
15025
|
+
* and any tool that speaks the same stdio protocol can use ScreamCode as an
|
|
15026
|
+
* agent backend through it.
|
|
14471
15027
|
*/
|
|
14472
15028
|
function buildStreamJsonRuntimePrompt(input) {
|
|
14473
15029
|
const appendParts = [
|
|
@@ -14480,7 +15036,7 @@ function buildStreamJsonRuntimePrompt(input) {
|
|
|
14480
15036
|
append: appendParts.join("\n\n")
|
|
14481
15037
|
};
|
|
14482
15038
|
}
|
|
14483
|
-
var
|
|
15039
|
+
var StreamJsonWriter = class {
|
|
14484
15040
|
writeLine;
|
|
14485
15041
|
sessionId = "";
|
|
14486
15042
|
msgCounter = 0;
|
|
@@ -14611,7 +15167,7 @@ var ClaudeStreamJsonWriter = class {
|
|
|
14611
15167
|
output: 0
|
|
14612
15168
|
};
|
|
14613
15169
|
}
|
|
14614
|
-
/** Emit a resume hint as a meta message
|
|
15170
|
+
/** Emit a resume hint as a stream-json meta message. */
|
|
14615
15171
|
emitResumeHint(sessionId) {
|
|
14616
15172
|
this.writeJson({
|
|
14617
15173
|
role: "meta",
|
|
@@ -14686,7 +15242,7 @@ async function* readStdinMessages() {
|
|
|
14686
15242
|
else log.debug("stream-json: ignoring unknown stdin message type", { type: msg.type });
|
|
14687
15243
|
}
|
|
14688
15244
|
}
|
|
14689
|
-
/** Extract plain text from a
|
|
15245
|
+
/** Extract plain text from a stream-json user message. */
|
|
14690
15246
|
function extractUserText(msg) {
|
|
14691
15247
|
const content = msg.message.content;
|
|
14692
15248
|
if (typeof content === "string") return content;
|
|
@@ -14727,7 +15283,7 @@ function mapCcConnectMode(mode) {
|
|
|
14727
15283
|
}
|
|
14728
15284
|
/**
|
|
14729
15285
|
* Install the stdout EPIPE guard and return the line writer handed to
|
|
14730
|
-
* {@link
|
|
15286
|
+
* {@link StreamJsonWriter}.
|
|
14731
15287
|
*
|
|
14732
15288
|
* cc-connect closes our stdout when it kills/restarts the agent (idle
|
|
14733
15289
|
* timeout, /restart, config change). Writing after that raises EPIPE as an
|
|
@@ -14783,7 +15339,7 @@ async function runStreamJson(opts) {
|
|
|
14783
15339
|
}
|
|
14784
15340
|
const streamSubagentModels = streamTuiConfig.subagentModels;
|
|
14785
15341
|
harness.setSubagentModelBindings(() => streamSubagentModels);
|
|
14786
|
-
const writer = new
|
|
15342
|
+
const writer = new StreamJsonWriter(installStdoutEpipeGuard());
|
|
14787
15343
|
let session;
|
|
14788
15344
|
let currentSessionId;
|
|
14789
15345
|
let sessionKey = "cc-connect-main";
|
|
@@ -15643,6 +16199,14 @@ const contentTypes = {
|
|
|
15643
16199
|
function cloneSnapshot(value) {
|
|
15644
16200
|
return structuredClone(value);
|
|
15645
16201
|
}
|
|
16202
|
+
/**
|
|
16203
|
+
* Duck-typed SESSION_NOT_FOUND check: covers both same-realm ScreamError
|
|
16204
|
+
* instances and errors that crossed a serialization boundary carrying `code`.
|
|
16205
|
+
*/
|
|
16206
|
+
function isSessionNotFoundError(error) {
|
|
16207
|
+
if (isScreamError(error)) return error.code === ErrorCodes.SESSION_NOT_FOUND;
|
|
16208
|
+
return typeof error === "object" && error !== null && error.code === ErrorCodes.SESSION_NOT_FOUND;
|
|
16209
|
+
}
|
|
15646
16210
|
function errorMessage(error) {
|
|
15647
16211
|
return error instanceof Error ? error.message : String(error);
|
|
15648
16212
|
}
|
|
@@ -17388,7 +17952,20 @@ var SessionManager = class {
|
|
|
17388
17952
|
if (!existing) return null;
|
|
17389
17953
|
if (existing.isActive) return existing;
|
|
17390
17954
|
const meta = existing.getMetadata();
|
|
17391
|
-
const
|
|
17955
|
+
const coreId = meta.coreSessionId ?? meta.sessionId;
|
|
17956
|
+
let session;
|
|
17957
|
+
try {
|
|
17958
|
+
session = await this.harness.resumeSession({ id: coreId });
|
|
17959
|
+
} catch (error) {
|
|
17960
|
+
if (!isSessionNotFoundError(error)) throw error;
|
|
17961
|
+
if (!await this.reindexCoreSession(coreId, meta.workDir)) return null;
|
|
17962
|
+
try {
|
|
17963
|
+
session = await this.harness.resumeSession({ id: coreId });
|
|
17964
|
+
} catch (retryError) {
|
|
17965
|
+
if (isSessionNotFoundError(retryError)) return null;
|
|
17966
|
+
throw retryError;
|
|
17967
|
+
}
|
|
17968
|
+
}
|
|
17392
17969
|
const reactivated = new WebSession(session, {
|
|
17393
17970
|
sessionId,
|
|
17394
17971
|
workDir: meta.workDir,
|
|
@@ -17414,6 +17991,30 @@ var SessionManager = class {
|
|
|
17414
17991
|
log.info("web: session reactivated", { sessionId });
|
|
17415
17992
|
return reactivated;
|
|
17416
17993
|
}
|
|
17994
|
+
/**
|
|
17995
|
+
* Self-heal for "meta exists but core session missing from the global
|
|
17996
|
+
* index": re-append the index entry when the session directory is still on
|
|
17997
|
+
* disk. Returns false when the directory is truly gone.
|
|
17998
|
+
*/
|
|
17999
|
+
async reindexCoreSession(coreId, workDir) {
|
|
18000
|
+
const sessionDir = join(this.homeDir, "sessions", encodeWorkDirKey(workDir), coreId);
|
|
18001
|
+
if (!existsSync(sessionDir)) return false;
|
|
18002
|
+
try {
|
|
18003
|
+
await appendSessionIndexEntry(this.homeDir, {
|
|
18004
|
+
sessionId: coreId,
|
|
18005
|
+
sessionDir,
|
|
18006
|
+
workDir
|
|
18007
|
+
});
|
|
18008
|
+
log.info("web: re-indexed core session from web metadata", { sessionId: coreId });
|
|
18009
|
+
return true;
|
|
18010
|
+
} catch (error) {
|
|
18011
|
+
log.warn("web: core session reindex failed", {
|
|
18012
|
+
sessionId: coreId,
|
|
18013
|
+
error: errorMessage(error)
|
|
18014
|
+
});
|
|
18015
|
+
return false;
|
|
18016
|
+
}
|
|
18017
|
+
}
|
|
17417
18018
|
get(sessionId) {
|
|
17418
18019
|
return this.sessions.get(sessionId);
|
|
17419
18020
|
}
|