scream-code 0.15.9 → 0.16.1
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-DQdNroOu.mjs → app-lSa6fM8z.mjs} +661 -100
- package/dist/{dispatch-CxGKRLcZ.mjs → dispatch-B6jbxY0D.mjs} +1 -1
- package/dist/{dispatch-D8ot3OUd.mjs → dispatch-DqeJdQZQ.mjs} +136 -46
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-Dx7LJTG6.mjs → text-input-dialog-BABWY9vy.mjs} +26 -0
- package/dist/{text-input-dialog-2cG5VK1G.mjs → text-input-dialog-rSkH3JTV.mjs} +1 -1
- package/package.json +1 -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 { $n 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-DqeJdQZQ.mjs";
|
|
8
|
+
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-BABWY9vy.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.1"),
|
|
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,163 @@ 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
|
+
expecting: textOf(payload["expecting"]),
|
|
2106
|
+
artifacts: listOf("artifacts"),
|
|
2107
|
+
evidence: listOf("evidence"),
|
|
2108
|
+
missing: listOf("missing")
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
/** Parse `artifacts: [a, b]` back into its items. */
|
|
2112
|
+
function bracketedListOf(value) {
|
|
2113
|
+
return (value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value).split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Replay producer: recover the fields from the injected notification. The body
|
|
2117
|
+
* shape is owned by `submitChildRequest` in agent-core:
|
|
2118
|
+
* `<type>: <message>` then optional `needs:` / `artifacts:` / `evidence:` /
|
|
2119
|
+
* `missing:` lines. A multi-line message keeps its extra lines attached.
|
|
2120
|
+
*/
|
|
2121
|
+
function childRequestFieldsFromNotification(text) {
|
|
2122
|
+
const lines = text.split("\n");
|
|
2123
|
+
const open = lines.findIndex((line) => line.startsWith("<notification ") && line.includes("type=\"child_request\""));
|
|
2124
|
+
if (open < 0) return null;
|
|
2125
|
+
const body = [];
|
|
2126
|
+
for (let index = open + 1; index < lines.length; index += 1) {
|
|
2127
|
+
const line = lines[index] ?? "";
|
|
2128
|
+
if (line === "</notification>") break;
|
|
2129
|
+
if (line.startsWith("Title: ") || line.startsWith("Severity: ")) continue;
|
|
2130
|
+
body.push(line);
|
|
2131
|
+
}
|
|
2132
|
+
const first = body[0];
|
|
2133
|
+
if (first === void 0) return null;
|
|
2134
|
+
const head = /^(info|handoff|escalate):\s([\s\S]*)$/.exec(first);
|
|
2135
|
+
const requestType = head !== null ? kindOf(head[1]) : "info";
|
|
2136
|
+
const messageParts = [head !== null && head[2] !== void 0 ? head[2] : first];
|
|
2137
|
+
let needs;
|
|
2138
|
+
let expecting;
|
|
2139
|
+
const lists = {};
|
|
2140
|
+
for (const line of body.slice(1)) {
|
|
2141
|
+
const separator = line.indexOf(": ");
|
|
2142
|
+
const key = separator < 0 ? "" : line.slice(0, separator);
|
|
2143
|
+
const value = separator < 0 ? "" : line.slice(separator + 2).trim();
|
|
2144
|
+
const listKey = (key === "artifacts" || key === "evidence" || key === "missing") && value.startsWith("[") && value.endsWith("]") ? key : void 0;
|
|
2145
|
+
if (key === "needs" && value.length > 0) needs = value;
|
|
2146
|
+
else if (key === "expecting" && value.length > 0) expecting = value;
|
|
2147
|
+
else if (listKey !== void 0) lists[listKey] = bracketedListOf(value);
|
|
2148
|
+
else messageParts.push(line);
|
|
2149
|
+
}
|
|
2150
|
+
const message = messageParts.join("\n").trim();
|
|
2151
|
+
if (message.length === 0) return null;
|
|
2152
|
+
return {
|
|
2153
|
+
requestType,
|
|
2154
|
+
message,
|
|
2155
|
+
needs,
|
|
2156
|
+
expecting,
|
|
2157
|
+
artifacts: lists["artifacts"],
|
|
2158
|
+
evidence: lists["evidence"],
|
|
2159
|
+
missing: lists["missing"]
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
/** Build the transcript notice. `name` is absent on replay. */
|
|
2163
|
+
function renderChildRequestNotice(fields, name) {
|
|
2164
|
+
const type = t(TYPE_KEY[fields.requestType]);
|
|
2165
|
+
const title = name === void 0 ? t("transcript.child_request_anon", { type }) : t("transcript.child_request", {
|
|
2166
|
+
name,
|
|
2167
|
+
type
|
|
2168
|
+
});
|
|
2169
|
+
const parts = [previewText(fields.message)];
|
|
2170
|
+
if (fields.needs !== void 0) parts.push(t("transcript.child_request_needs", { value: previewText(fields.needs) }));
|
|
2171
|
+
if (fields.expecting !== void 0) parts.push(t("transcript.child_request_expecting", { value: previewText(fields.expecting) }));
|
|
2172
|
+
for (const entry of [
|
|
2173
|
+
["artifacts", fields.artifacts],
|
|
2174
|
+
["evidence", fields.evidence],
|
|
2175
|
+
["missing", fields.missing]
|
|
2176
|
+
]) {
|
|
2177
|
+
const preview = previewedList(entry[0], entry[1]);
|
|
2178
|
+
if (preview !== void 0) parts.push(preview);
|
|
2179
|
+
}
|
|
2180
|
+
return {
|
|
2181
|
+
title,
|
|
2182
|
+
detail: parts.join(" · ")
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
//#endregion
|
|
2028
2186
|
//#region src/tui/utils/subagent-slots.ts
|
|
2029
2187
|
/** Default subagent types, in the fixed display order of the slots. */
|
|
2030
2188
|
const DEFAULT_SUBAGENT_TYPES = [
|
|
@@ -2037,6 +2195,10 @@ const DEFAULT_SUBAGENT_TYPES = [
|
|
|
2037
2195
|
"worker",
|
|
2038
2196
|
"writer"
|
|
2039
2197
|
];
|
|
2198
|
+
/** How long a `requesting` overlay stays visible after a subagent asks the
|
|
2199
|
+
* parent for help. The request itself returns at once and the agent keeps
|
|
2200
|
+
* working, so this is a display window, not a blocked state. */
|
|
2201
|
+
const REQUESTING_WINDOW_MS = 5e3;
|
|
2040
2202
|
var SubagentSlots = class {
|
|
2041
2203
|
slots = /* @__PURE__ */ new Map();
|
|
2042
2204
|
instances = /* @__PURE__ */ new Map();
|
|
@@ -2050,6 +2212,10 @@ var SubagentSlots = class {
|
|
|
2050
2212
|
* the first open window; concurrent windows on the same type keep counting
|
|
2051
2213
|
* and the slot only exits messaging when the last window closes. */
|
|
2052
2214
|
messagingPrev = /* @__PURE__ */ new Map();
|
|
2215
|
+
/** Slot type → open `requesting` overlay. Keyed by type so several live
|
|
2216
|
+
* instances of one type share a single marker; the real activity state stays
|
|
2217
|
+
* untouched in the slot and shows back up once the overlay expires. */
|
|
2218
|
+
requesting = /* @__PURE__ */ new Map();
|
|
2053
2219
|
/** Count of open messaging windows per type (handles same-type siblings). */
|
|
2054
2220
|
messagingCount = /* @__PURE__ */ new Map();
|
|
2055
2221
|
reset() {
|
|
@@ -2059,6 +2225,7 @@ var SubagentSlots = class {
|
|
|
2059
2225
|
this.messagingByCall.clear();
|
|
2060
2226
|
this.messagingPrev.clear();
|
|
2061
2227
|
this.messagingCount.clear();
|
|
2228
|
+
this.requesting.clear();
|
|
2062
2229
|
}
|
|
2063
2230
|
/**
|
|
2064
2231
|
* subagent.spawned. A first spawn starts the slot as working; a second
|
|
@@ -2098,6 +2265,7 @@ var SubagentSlots = class {
|
|
|
2098
2265
|
onTerminated(agentId) {
|
|
2099
2266
|
const instance = this.instances.get(agentId);
|
|
2100
2267
|
if (instance === void 0) return;
|
|
2268
|
+
if (this.requesting.get(instance.type)?.agentId === agentId) this.requesting.delete(instance.type);
|
|
2101
2269
|
const remaining = (this.runningByType.get(instance.type) ?? 1) - 1;
|
|
2102
2270
|
if (remaining > 0) {
|
|
2103
2271
|
this.runningByType.set(instance.type, remaining);
|
|
@@ -2126,6 +2294,21 @@ var SubagentSlots = class {
|
|
|
2126
2294
|
}
|
|
2127
2295
|
this.setState(instance.type, kind === "tool" ? "working" : "outputting", agentId, detail);
|
|
2128
2296
|
}
|
|
2297
|
+
/** A subagent asked the parent for help. The request returns immediately and
|
|
2298
|
+
* the agent keeps working, so this only raises a transient overlay — the real
|
|
2299
|
+
* activity state stays in the slot and shows back up when it expires. While
|
|
2300
|
+
* `messaging` is on screen the overlay loses (see visibleSlot). */
|
|
2301
|
+
onRequesting(agentId, at = Date.now()) {
|
|
2302
|
+
const instance = this.instances.get(agentId);
|
|
2303
|
+
if (instance === void 0) return;
|
|
2304
|
+
this.requesting.set(instance.type, {
|
|
2305
|
+
until: at + REQUESTING_WINDOW_MS,
|
|
2306
|
+
agentId
|
|
2307
|
+
});
|
|
2308
|
+
const slot = this.ensureSlot(instance.type);
|
|
2309
|
+
slot.agentId = agentId;
|
|
2310
|
+
slot.lastActivityAt = at;
|
|
2311
|
+
}
|
|
2129
2312
|
/**
|
|
2130
2313
|
* Main-side SendSubagentMessage tool call started: the target subagent's
|
|
2131
2314
|
* slot shows messaging until the tool call returns.
|
|
@@ -2135,6 +2318,7 @@ var SubagentSlots = class {
|
|
|
2135
2318
|
const instance = this.instances.get(targetAgentId);
|
|
2136
2319
|
if (instance === void 0) return;
|
|
2137
2320
|
this.messagingByCall.set(toolCallId, targetAgentId);
|
|
2321
|
+
this.requesting.delete(instance.type);
|
|
2138
2322
|
const slot = this.ensureSlot(instance.type);
|
|
2139
2323
|
if (slot.status !== "messaging") this.messagingPrev.set(instance.type, slot.status);
|
|
2140
2324
|
this.messagingCount.set(instance.type, (this.messagingCount.get(instance.type) ?? 0) + 1);
|
|
@@ -2166,24 +2350,31 @@ var SubagentSlots = class {
|
|
|
2166
2350
|
* All slots in fixed display order: the eight default types first (always
|
|
2167
2351
|
* present, idle when never used), then extra types in spawn order.
|
|
2168
2352
|
*/
|
|
2169
|
-
getSlots() {
|
|
2353
|
+
getSlots(at = Date.now()) {
|
|
2354
|
+
this.dropExpiredOverlays(at);
|
|
2170
2355
|
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
|
-
}
|
|
2356
|
+
for (const type of DEFAULT_SUBAGENT_TYPES) ordered.push(this.visibleSlot(type));
|
|
2178
2357
|
for (const slot of this.slots.values()) {
|
|
2179
2358
|
if (DEFAULT_SUBAGENT_TYPES.includes(slot.type)) continue;
|
|
2180
|
-
ordered.push(
|
|
2181
|
-
...slot,
|
|
2182
|
-
count: this.runningByType.get(slot.type) ?? 0
|
|
2183
|
-
});
|
|
2359
|
+
ordered.push(this.visibleSlot(slot.type));
|
|
2184
2360
|
}
|
|
2185
2361
|
return ordered;
|
|
2186
2362
|
}
|
|
2363
|
+
/** Display view of one slot: the real state with an unexpired `requesting`
|
|
2364
|
+
* overlay on top. Read-path only — it never mutates. Callers must run
|
|
2365
|
+
* {@link dropExpiredOverlays} first, and messaging clears the overlay in
|
|
2366
|
+
* {@link onMessagingStart}; the `messaging` guard only covers same-tick ordering. */
|
|
2367
|
+
visibleSlot(type) {
|
|
2368
|
+
const copy = {
|
|
2369
|
+
...this.ensureSlot(type),
|
|
2370
|
+
count: this.runningByType.get(type) ?? 0
|
|
2371
|
+
};
|
|
2372
|
+
if (this.requesting.has(type) && copy.status !== "messaging") copy.status = "requesting";
|
|
2373
|
+
return copy;
|
|
2374
|
+
}
|
|
2375
|
+
dropExpiredOverlays(at) {
|
|
2376
|
+
for (const [type, overlay] of this.requesting) if (at >= overlay.until) this.requesting.delete(type);
|
|
2377
|
+
}
|
|
2187
2378
|
ensureSlot(type) {
|
|
2188
2379
|
let slot = this.slots.get(type);
|
|
2189
2380
|
if (slot === void 0) {
|
|
@@ -2580,6 +2771,15 @@ var SessionEventHandler = class {
|
|
|
2580
2771
|
subagentSlots = new SubagentSlots();
|
|
2581
2772
|
/** Tool callId of the in-flight UpdateGoal(complete) (judging), if any. */
|
|
2582
2773
|
goalJudgeCallId;
|
|
2774
|
+
/** Rolling first-token latencies of real requests, feeding the sidebar Hub
|
|
2775
|
+
* provider row. In-process only, and dropped whenever the model changes —
|
|
2776
|
+
* a different provider is a different network path. */
|
|
2777
|
+
providerLatencySamples = [];
|
|
2778
|
+
providerLatencyModel;
|
|
2779
|
+
providerLatencyAt;
|
|
2780
|
+
/** Enough samples to smooth one slow request, few enough that a recovered
|
|
2781
|
+
* endpoint reads as recovered again quickly. */
|
|
2782
|
+
providerLatencyWindow = 3;
|
|
2583
2783
|
renderedSkillActivationIds = /* @__PURE__ */ new Set();
|
|
2584
2784
|
renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
|
|
2585
2785
|
mcpServerStatusSpinners = /* @__PURE__ */ new Map();
|
|
@@ -2595,6 +2795,9 @@ var SessionEventHandler = class {
|
|
|
2595
2795
|
this.pendingSkillCandidates = [];
|
|
2596
2796
|
this.promptedSkillCandidates.clear();
|
|
2597
2797
|
this.goalJudgeCallId = void 0;
|
|
2798
|
+
this.providerLatencySamples = [];
|
|
2799
|
+
this.providerLatencyModel = void 0;
|
|
2800
|
+
this.providerLatencyAt = void 0;
|
|
2598
2801
|
this.subagentUsageSeen.clear();
|
|
2599
2802
|
this.host.setAppState({
|
|
2600
2803
|
subagentUsage: {},
|
|
@@ -2768,6 +2971,41 @@ var SessionEventHandler = class {
|
|
|
2768
2971
|
getSubagentSlots() {
|
|
2769
2972
|
return this.subagentSlots.getSlots();
|
|
2770
2973
|
}
|
|
2974
|
+
/** Keep the last few first-token latencies, per provider. Steps without a
|
|
2975
|
+
* measured latency (no stream, cached turn) are ignored rather than counted
|
|
2976
|
+
* as a zero. */
|
|
2977
|
+
recordProviderLatency(event) {
|
|
2978
|
+
const latency = event.llmFirstTokenLatencyMs;
|
|
2979
|
+
if (latency === void 0) return;
|
|
2980
|
+
const model = this.host.state.appState.model;
|
|
2981
|
+
if (this.providerLatencyModel !== void 0 && model !== this.providerLatencyModel) this.providerLatencySamples = [];
|
|
2982
|
+
this.providerLatencyModel = model;
|
|
2983
|
+
this.providerLatencySamples.push(latency);
|
|
2984
|
+
if (this.providerLatencySamples.length > this.providerLatencyWindow) this.providerLatencySamples.shift();
|
|
2985
|
+
this.providerLatencyAt = Date.now();
|
|
2986
|
+
}
|
|
2987
|
+
/** Sidebar Hub: average measured response time for the provider in use. The
|
|
2988
|
+
* caller owns tone and staleness — this stays a plain reading. */
|
|
2989
|
+
getProviderLatency() {
|
|
2990
|
+
if (this.providerLatencySamples.length === 0) return {
|
|
2991
|
+
ms: void 0,
|
|
2992
|
+
sampledAt: void 0
|
|
2993
|
+
};
|
|
2994
|
+
if (this.host.state.appState.model !== this.providerLatencyModel) {
|
|
2995
|
+
this.providerLatencySamples = [];
|
|
2996
|
+
this.providerLatencyModel = void 0;
|
|
2997
|
+
this.providerLatencyAt = void 0;
|
|
2998
|
+
return {
|
|
2999
|
+
ms: void 0,
|
|
3000
|
+
sampledAt: void 0
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
const total = this.providerLatencySamples.reduce((sum, value) => sum + value, 0);
|
|
3004
|
+
return {
|
|
3005
|
+
ms: Math.round(total / this.providerLatencySamples.length),
|
|
3006
|
+
sampledAt: this.providerLatencyAt
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
2771
3009
|
/** Feed routed subagent activity into the slot state machine. */
|
|
2772
3010
|
feedSubagentSlots(event, agentId) {
|
|
2773
3011
|
switch (event.type) {
|
|
@@ -2782,7 +3020,10 @@ var SessionEventHandler = class {
|
|
|
2782
3020
|
this.subagentSlots.onTerminated(event.subagentId);
|
|
2783
3021
|
break;
|
|
2784
3022
|
case "tool.call.started":
|
|
2785
|
-
|
|
3023
|
+
if (event.name === "ContactParent") {
|
|
3024
|
+
this.subagentSlots.onRequesting(agentId);
|
|
3025
|
+
this.appendChildRequestNotice(agentId, event.args);
|
|
3026
|
+
} else this.subagentSlots.onActivity(agentId, "tool", `tool: ${event.name}`);
|
|
2786
3027
|
break;
|
|
2787
3028
|
case "tool.call.delta": break;
|
|
2788
3029
|
case "tool.result":
|
|
@@ -2795,6 +3036,26 @@ var SessionEventHandler = class {
|
|
|
2795
3036
|
default: break;
|
|
2796
3037
|
}
|
|
2797
3038
|
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Surface a child→parent collaboration request in the transcript the moment
|
|
3041
|
+
* the subagent asks, so the human sees it without expanding the agent card.
|
|
3042
|
+
* The request always targets the main agent (one subagent host per session),
|
|
3043
|
+
* which is why a top-level transcript entry is the right home for it.
|
|
3044
|
+
*/
|
|
3045
|
+
appendChildRequestNotice(agentId, args) {
|
|
3046
|
+
const fields = childRequestFieldsFromArgs(argsRecord(args));
|
|
3047
|
+
if (fields === null) return;
|
|
3048
|
+
const notice = renderChildRequestNotice(fields, this.subagentInfo.get(agentId)?.name);
|
|
3049
|
+
this.host.appendTranscriptEntry({
|
|
3050
|
+
id: nextTranscriptId(),
|
|
3051
|
+
kind: "status",
|
|
3052
|
+
turnId: this.host.streamingUI.getTurnContext().turnId,
|
|
3053
|
+
renderMode: "notice",
|
|
3054
|
+
content: notice.title,
|
|
3055
|
+
detail: notice.detail,
|
|
3056
|
+
noticeMarkerColor: this.host.state.theme.colors.warning
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
2798
3059
|
routeSubagentEvent(event) {
|
|
2799
3060
|
const subagentId = event.agentId;
|
|
2800
3061
|
if (subagentId === "main") return false;
|
|
@@ -2912,6 +3173,7 @@ var SessionEventHandler = class {
|
|
|
2912
3173
|
handleStepCompleted(event) {
|
|
2913
3174
|
this.host.streamingUI.flushNow();
|
|
2914
3175
|
this.maybeShowDebugTiming(event);
|
|
3176
|
+
this.recordProviderLatency(event);
|
|
2915
3177
|
this.drainQueuedMessagesIntoSteer();
|
|
2916
3178
|
const prev = this.host.state.appState.sessionUsage;
|
|
2917
3179
|
const apiCalls = this.host.state.appState.sessionApiCalls + 1;
|
|
@@ -4018,6 +4280,10 @@ var SessionReplayRenderer = class {
|
|
|
4018
4280
|
this.renderCronMissed(context, message.origin);
|
|
4019
4281
|
return;
|
|
4020
4282
|
}
|
|
4283
|
+
if (message.origin?.kind === "system_trigger") {
|
|
4284
|
+
this.renderSystemTrigger(context, message);
|
|
4285
|
+
return;
|
|
4286
|
+
}
|
|
4021
4287
|
this.flushAssistant(context);
|
|
4022
4288
|
const skill = skillActivationFromOrigin(message.origin);
|
|
4023
4289
|
if (skill !== void 0) {
|
|
@@ -4028,6 +4294,26 @@ var SessionReplayRenderer = class {
|
|
|
4028
4294
|
this.advanceTurn(context);
|
|
4029
4295
|
this.host.appendTranscriptEntry(replayEntry(context, "user", contentPartsToText(message.content), "plain"));
|
|
4030
4296
|
}
|
|
4297
|
+
/**
|
|
4298
|
+
* `system_trigger` messages are internal injections, not user speech: the
|
|
4299
|
+
* live transcript never prints them and markdown export filters them out, so
|
|
4300
|
+
* replay must not surface them as a user row either. The single exception is
|
|
4301
|
+
* a child→parent collaboration request, which gets the same notice the live
|
|
4302
|
+
* path shows. Replay has no spawn events, so the subagent's display name is
|
|
4303
|
+
* gone and the name-less title variant keeps the notice honest.
|
|
4304
|
+
*/
|
|
4305
|
+
renderSystemTrigger(context, message) {
|
|
4306
|
+
const origin = message.origin;
|
|
4307
|
+
if (origin?.kind !== "system_trigger" || origin.name !== "child_request") return;
|
|
4308
|
+
const fields = childRequestFieldsFromNotification(contentPartsToText(message.content));
|
|
4309
|
+
if (fields === null) return;
|
|
4310
|
+
const notice = renderChildRequestNotice(fields);
|
|
4311
|
+
this.flushAssistant(context);
|
|
4312
|
+
this.host.appendTranscriptEntry({
|
|
4313
|
+
...replayEntry(context, "status", notice.title, "notice", { detail: notice.detail }),
|
|
4314
|
+
noticeMarkerColor: this.host.state.theme.colors.warning
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4031
4317
|
renderToolCalls(context, toolCalls) {
|
|
4032
4318
|
if (toolCalls.length === 0) return;
|
|
4033
4319
|
const { streamingUI } = this.host;
|
|
@@ -6267,7 +6553,7 @@ var CommittedMessageComponent = class {
|
|
|
6267
6553
|
return new Text(` ${chalk.hex(colors.textDim)(t("transcript.tool_name", { name }))}${summary}`, 0, 0).render(width);
|
|
6268
6554
|
}
|
|
6269
6555
|
case "status":
|
|
6270
|
-
if (entry.renderMode === "notice") return new NoticeMessageComponent(entry.content, entry.detail, colors).render(width);
|
|
6556
|
+
if (entry.renderMode === "notice") return new NoticeMessageComponent(entry.content, entry.detail, colors, entry.noticeMarkerColor).render(width);
|
|
6271
6557
|
return new StatusMessageComponent(entry.content, colors, entry.color).render(width);
|
|
6272
6558
|
case "skill_activation": {
|
|
6273
6559
|
const text = entry.skillName ?? entry.content;
|
|
@@ -6416,10 +6702,10 @@ var TranscriptController = class TranscriptController {
|
|
|
6416
6702
|
return tc;
|
|
6417
6703
|
}
|
|
6418
6704
|
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);
|
|
6705
|
+
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
6706
|
case "status":
|
|
6421
6707
|
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);
|
|
6708
|
+
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
6709
|
case "cron":
|
|
6424
6710
|
if (entry.cronData === void 0) return null;
|
|
6425
6711
|
return new CronMessageComponent(entry.content, entry.cronData, state.theme.colors);
|
|
@@ -6668,6 +6954,55 @@ function clampSidebarWidth(cols) {
|
|
|
6668
6954
|
return Math.min(60, Math.max(24, Math.round(cols)));
|
|
6669
6955
|
}
|
|
6670
6956
|
//#endregion
|
|
6957
|
+
//#region src/tui/utils/display-width.ts
|
|
6958
|
+
/** Display-column width of a string, matching the terminal layout engine
|
|
6959
|
+
* (ANSI sequences stripped, CJK/wide chars counted as 2 columns). */
|
|
6960
|
+
function displayWidth(text) {
|
|
6961
|
+
return visibleWidth(text);
|
|
6962
|
+
}
|
|
6963
|
+
/** Pad `label` to `target` display columns with spaces (CJK-aware). */
|
|
6964
|
+
function padLabel(label, target) {
|
|
6965
|
+
return label + " ".repeat(Math.max(0, target - displayWidth(label)));
|
|
6966
|
+
}
|
|
6967
|
+
function layoutSidebarGrid(rows, width) {
|
|
6968
|
+
const valueCols = Math.max(0, ...rows.map((row) => displayWidth(row.value ?? "")));
|
|
6969
|
+
const labelBudget = Math.max(1, width - 2 - Math.max(valueCols, 1) - 1);
|
|
6970
|
+
const labelCols = Math.min(12, labelBudget);
|
|
6971
|
+
const valueStart = Math.max(2 + labelCols + 1, width - valueCols);
|
|
6972
|
+
return rows.map((row) => {
|
|
6973
|
+
const marker = padLabel(row.marker ?? "", 2);
|
|
6974
|
+
const label = truncateToWidth(row.label, labelCols);
|
|
6975
|
+
const used = 2 + displayWidth(label);
|
|
6976
|
+
if (row.value !== void 0) {
|
|
6977
|
+
const gap = Math.max(1, width - used - displayWidth(row.value));
|
|
6978
|
+
const room = Math.max(1, width - used - gap);
|
|
6979
|
+
return {
|
|
6980
|
+
marker,
|
|
6981
|
+
label,
|
|
6982
|
+
gap,
|
|
6983
|
+
value: truncateToWidth(row.value, room)
|
|
6984
|
+
};
|
|
6985
|
+
}
|
|
6986
|
+
if (row.note !== void 0) {
|
|
6987
|
+
const noteCols = displayWidth(row.note);
|
|
6988
|
+
const aligned = Math.max(1, valueStart - used);
|
|
6989
|
+
const fits = Math.max(1, width - used - noteCols);
|
|
6990
|
+
const gap = Math.min(aligned, fits);
|
|
6991
|
+
return {
|
|
6992
|
+
marker,
|
|
6993
|
+
label,
|
|
6994
|
+
gap,
|
|
6995
|
+
note: truncateToWidth(row.note, Math.max(1, width - used - gap))
|
|
6996
|
+
};
|
|
6997
|
+
}
|
|
6998
|
+
return {
|
|
6999
|
+
marker,
|
|
7000
|
+
label,
|
|
7001
|
+
gap: 1
|
|
7002
|
+
};
|
|
7003
|
+
});
|
|
7004
|
+
}
|
|
7005
|
+
//#endregion
|
|
6671
7006
|
//#region src/tui/components/sidebar/panels/git-panel.ts
|
|
6672
7007
|
/**
|
|
6673
7008
|
* Summary-only Git panel (摘要式): the workdir name plus added (green) /
|
|
@@ -6683,12 +7018,13 @@ var GitPanelContent = class {
|
|
|
6683
7018
|
}
|
|
6684
7019
|
invalidate() {}
|
|
6685
7020
|
render(width) {
|
|
7021
|
+
const indent = " ".repeat(2);
|
|
6686
7022
|
const git = this.ctx.getData().git;
|
|
6687
|
-
if (git === void 0) return [chalk.hex(this.colors.textDim)(t("sidebar.git_no_repo"))];
|
|
7023
|
+
if (git === void 0) return [`${indent}${chalk.hex(this.colors.textDim)(t("sidebar.git_no_repo"))}`];
|
|
6688
7024
|
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)];
|
|
7025
|
+
const workDir = truncateToWidth(`${indent}${chalk.hex(this.colors.text)(basename(git.workDir))}`, width);
|
|
7026
|
+
if (git.diffAdded === 0 && git.diffDeleted === 0 && git.filesCount === 0) return [workDir, dim(`${indent}${t("sidebar.git_clean")}`)];
|
|
7027
|
+
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
7028
|
}
|
|
6693
7029
|
};
|
|
6694
7030
|
const gitPanel = {
|
|
@@ -6702,35 +7038,6 @@ const gitPanel = {
|
|
|
6702
7038
|
}
|
|
6703
7039
|
};
|
|
6704
7040
|
//#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
7041
|
//#region src/tui/components/sidebar/panels/session-panel.ts
|
|
6735
7042
|
function formatDuration(ms) {
|
|
6736
7043
|
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
@@ -6766,10 +7073,10 @@ function formatCompact(n) {
|
|
|
6766
7073
|
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
6767
7074
|
return String(n);
|
|
6768
7075
|
}
|
|
6769
|
-
/** True when
|
|
7076
|
+
/** True when the shared grid can show these values at full width: the label
|
|
7077
|
+
* column is fixed across panels, so only the value column decides the fit. */
|
|
6770
7078
|
function gridFits(rows, width) {
|
|
6771
|
-
|
|
6772
|
-
return grid.labelCols + 2 + grid.valueCols <= width;
|
|
7079
|
+
return 15 + Math.max(0, ...rows.map(([, value]) => displayWidth(value))) <= width;
|
|
6773
7080
|
}
|
|
6774
7081
|
/**
|
|
6775
7082
|
* Session summary, live-rendered each frame as a strict two-column table:
|
|
@@ -6806,10 +7113,11 @@ var SessionPanelContent = class {
|
|
|
6806
7113
|
];
|
|
6807
7114
|
const withTokens = (compact) => [...tokenRows.map(([name, n]) => [name, compact ? formatCompact(n) : formatCount$1(n)]), ...countRows];
|
|
6808
7115
|
const full = withTokens(false);
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
7116
|
+
return layoutSidebarGrid((gridFits(full, width) ? full : withTokens(true)).map(([name, reading]) => ({
|
|
7117
|
+
marker: "●",
|
|
7118
|
+
label: name,
|
|
7119
|
+
value: reading
|
|
7120
|
+
})), width).map((cell) => `${label(cell.marker)}${label(cell.label)}${" ".repeat(cell.gap)}${value(cell.value ?? "")}`);
|
|
6813
7121
|
}
|
|
6814
7122
|
};
|
|
6815
7123
|
const sessionPanel = {
|
|
@@ -6836,6 +7144,7 @@ function statusText(status) {
|
|
|
6836
7144
|
case "outputting": return t("sidebar.agent_outputting");
|
|
6837
7145
|
case "messaging": return t("sidebar.agent_messaging");
|
|
6838
7146
|
case "reworking": return t("sidebar.agent_reworking");
|
|
7147
|
+
case "requesting": return t("sidebar.agent_requesting");
|
|
6839
7148
|
}
|
|
6840
7149
|
}
|
|
6841
7150
|
/**
|
|
@@ -6854,17 +7163,22 @@ var AgentsPanelContent = class {
|
|
|
6854
7163
|
render(width) {
|
|
6855
7164
|
const agents = this.ctx.getData().agents;
|
|
6856
7165
|
if (agents === void 0 || agents.length === 0) return [];
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
7166
|
+
return layoutSidebarGrid(agents.map((slot) => ({
|
|
7167
|
+
marker: slot.status === "idle" ? "○" : "●",
|
|
7168
|
+
label: slot.type,
|
|
7169
|
+
value: statusText(slot.status) + (slot.count > 1 ? ` ×${slot.count}` : "")
|
|
7170
|
+
})), width).map((cell, index) => this.renderSlot(cell, agents[index], index, width));
|
|
6861
7171
|
}
|
|
6862
|
-
renderSlot(slot, index,
|
|
7172
|
+
renderSlot(cell, slot, index, width) {
|
|
6863
7173
|
const dim = (s) => chalk.hex(this.colors.textDim)(s);
|
|
6864
|
-
|
|
7174
|
+
const idle = slot.status === "idle";
|
|
6865
7175
|
const phase = (Date.now() % AGENT_GRADIENT_CYCLE_MS / AGENT_GRADIENT_CYCLE_MS + index * .125) % 1;
|
|
6866
7176
|
const active = slot.status === "working" || slot.status === "outputting" ? chalk.hex(lerpGradient(phase)).bold : void 0;
|
|
6867
|
-
|
|
7177
|
+
const marker = padLabel((active ?? chalk.hex(this.statusColor(slot.status)))(idle ? "○" : "●"), 2);
|
|
7178
|
+
const label = (idle ? dim : chalk.hex(this.colors.textStrong))(cell.label);
|
|
7179
|
+
const paint = active ?? (idle ? dim : slot.status === "requesting" ? chalk.hex(this.colors.warning) : chalk.hex(this.colors.text));
|
|
7180
|
+
const count = slot.count > 1 ? dim(` ×${slot.count}`) : "";
|
|
7181
|
+
return truncateToWidth(`${marker}${label}${" ".repeat(cell.gap)}${paint(statusText(slot.status))}${count}`, width);
|
|
6868
7182
|
}
|
|
6869
7183
|
statusColor(status) {
|
|
6870
7184
|
switch (status) {
|
|
@@ -6872,6 +7186,7 @@ var AgentsPanelContent = class {
|
|
|
6872
7186
|
case "outputting": return this.colors.primary;
|
|
6873
7187
|
case "messaging": return this.colors.accent;
|
|
6874
7188
|
case "reworking": return this.colors.warning;
|
|
7189
|
+
case "requesting": return this.colors.warning;
|
|
6875
7190
|
case "idle": return this.colors.textDim;
|
|
6876
7191
|
}
|
|
6877
7192
|
}
|
|
@@ -6929,26 +7244,48 @@ var GoalPanelContent = class {
|
|
|
6929
7244
|
const wallMs = goal.status === "active" ? goal.wallClockMs + Math.max(0, now - goal.wallClockBaseAt) : goal.wallClockMs;
|
|
6930
7245
|
const lines = [];
|
|
6931
7246
|
lines.push(this.renderBadge(goal.status, goal.judge, dim));
|
|
6932
|
-
lines.push(truncateToWidth(dim(goal.objective), width));
|
|
7247
|
+
lines.push(truncateToWidth(dim(`● ${goal.objective}`), width));
|
|
6933
7248
|
const rows = [
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
7249
|
+
{
|
|
7250
|
+
marker: "●",
|
|
7251
|
+
label: t("sidebar.goal_tokens"),
|
|
7252
|
+
value: formatCount(goal.tokensUsed)
|
|
7253
|
+
},
|
|
7254
|
+
{
|
|
7255
|
+
marker: "●",
|
|
7256
|
+
label: t("sidebar.goal_tokens_input"),
|
|
7257
|
+
value: goal.inputTokens === null ? "—" : formatCount(goal.inputTokens)
|
|
7258
|
+
},
|
|
7259
|
+
{
|
|
7260
|
+
marker: "●",
|
|
7261
|
+
label: t("sidebar.goal_tokens_output"),
|
|
7262
|
+
value: goal.outputTokens === null ? "—" : formatCount(goal.outputTokens)
|
|
7263
|
+
},
|
|
7264
|
+
{
|
|
7265
|
+
marker: "●",
|
|
7266
|
+
label: t("sidebar.goal_turns"),
|
|
7267
|
+
value: String(goal.turnsUsed)
|
|
7268
|
+
},
|
|
7269
|
+
{
|
|
7270
|
+
marker: "●",
|
|
7271
|
+
label: t("sidebar.goal_wall"),
|
|
7272
|
+
value: formatWallClock(wallMs)
|
|
7273
|
+
},
|
|
7274
|
+
{
|
|
7275
|
+
marker: "●",
|
|
7276
|
+
label: t("sidebar.goal_judge"),
|
|
7277
|
+
value: judgeText(goal.judge)
|
|
7278
|
+
}
|
|
6940
7279
|
];
|
|
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));
|
|
7280
|
+
const gridRows = goal.completionCriterion !== null ? [...rows, {
|
|
7281
|
+
marker: "●",
|
|
7282
|
+
label: t("sidebar.goal_criterion"),
|
|
7283
|
+
note: goal.completionCriterion ?? ""
|
|
7284
|
+
}] : rows;
|
|
7285
|
+
for (const cell of layoutSidebarGrid(gridRows, width)) {
|
|
7286
|
+
const reading = cell.value ?? cell.note ?? "";
|
|
7287
|
+
lines.push(`${dim(`${cell.marker}${cell.label}`)}${" ".repeat(cell.gap)}${text(reading)}`);
|
|
7288
|
+
}
|
|
6952
7289
|
return lines;
|
|
6953
7290
|
}
|
|
6954
7291
|
/** State badge: judging wins over everything; otherwise map the snapshot
|
|
@@ -6974,6 +7311,212 @@ const goalPanel = {
|
|
|
6974
7311
|
return new GoalPanelContent(ctx, ctx.colors);
|
|
6975
7312
|
}
|
|
6976
7313
|
};
|
|
7314
|
+
/** Per-request budget: long enough for a slow CDN, short enough to never hang. */
|
|
7315
|
+
const HUB_PROBE_TIMEOUT_MS = 1500;
|
|
7316
|
+
/** Row id for the model provider, whose latency is measured, never probed. */
|
|
7317
|
+
const HUB_MODEL_ROW_ID = "model";
|
|
7318
|
+
/** A measured provider reading older than this is dimmed: nothing was asked lately. */
|
|
7319
|
+
const HUB_MODEL_STALE_MS = 5 * 6e4;
|
|
7320
|
+
/** Default probe targets: the endpoints day-to-day development tends to need. */
|
|
7321
|
+
const HUB_ENDPOINTS = Object.freeze([
|
|
7322
|
+
{
|
|
7323
|
+
id: "github",
|
|
7324
|
+
url: "https://github.com/"
|
|
7325
|
+
},
|
|
7326
|
+
{
|
|
7327
|
+
id: "google",
|
|
7328
|
+
url: "https://www.google.com/"
|
|
7329
|
+
},
|
|
7330
|
+
{
|
|
7331
|
+
id: "x",
|
|
7332
|
+
url: "https://x.com/",
|
|
7333
|
+
label: "x (twitter)"
|
|
7334
|
+
},
|
|
7335
|
+
{
|
|
7336
|
+
id: "huggingface",
|
|
7337
|
+
url: "https://huggingface.co/"
|
|
7338
|
+
},
|
|
7339
|
+
{
|
|
7340
|
+
id: "alibaba",
|
|
7341
|
+
url: "https://www.aliyun.com/",
|
|
7342
|
+
label: "Alibaba"
|
|
7343
|
+
},
|
|
7344
|
+
{
|
|
7345
|
+
id: "tencent",
|
|
7346
|
+
url: "https://cloud.tencent.com/",
|
|
7347
|
+
label: "Tencent"
|
|
7348
|
+
}
|
|
7349
|
+
]);
|
|
7350
|
+
function defaultFetch(url, init) {
|
|
7351
|
+
return globalThis.fetch(url, init);
|
|
7352
|
+
}
|
|
7353
|
+
function createHubProbe(options = {}) {
|
|
7354
|
+
const endpoints = options.endpoints ?? HUB_ENDPOINTS;
|
|
7355
|
+
const now = options.now ?? Date.now;
|
|
7356
|
+
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
7357
|
+
const sites = /* @__PURE__ */ new Map();
|
|
7358
|
+
for (const endpoint of endpoints) sites.set(endpoint.id, {
|
|
7359
|
+
ms: void 0,
|
|
7360
|
+
failures: 0
|
|
7361
|
+
});
|
|
7362
|
+
let roundAt;
|
|
7363
|
+
let inFlight = false;
|
|
7364
|
+
const runRound = async () => {
|
|
7365
|
+
const startedAt = now();
|
|
7366
|
+
const results = await Promise.allSettled(endpoints.map(async (endpoint) => {
|
|
7367
|
+
const response = await fetchImpl(endpoint.url, {
|
|
7368
|
+
method: "HEAD",
|
|
7369
|
+
redirect: "manual",
|
|
7370
|
+
signal: AbortSignal.timeout(HUB_PROBE_TIMEOUT_MS)
|
|
7371
|
+
});
|
|
7372
|
+
return {
|
|
7373
|
+
id: endpoint.id,
|
|
7374
|
+
ms: Math.max(0, now() - startedAt),
|
|
7375
|
+
status: response.status
|
|
7376
|
+
};
|
|
7377
|
+
}));
|
|
7378
|
+
for (const [index, result] of results.entries()) {
|
|
7379
|
+
const site = sites.get(endpoints[index].id);
|
|
7380
|
+
if (site === void 0) continue;
|
|
7381
|
+
if (result.status === "fulfilled") {
|
|
7382
|
+
site.ms = result.value.ms;
|
|
7383
|
+
site.failures = 0;
|
|
7384
|
+
} else {
|
|
7385
|
+
site.failures += 1;
|
|
7386
|
+
if (site.failures >= 2) site.ms = void 0;
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7389
|
+
roundAt = now();
|
|
7390
|
+
inFlight = false;
|
|
7391
|
+
};
|
|
7392
|
+
return {
|
|
7393
|
+
sampleIfStale: () => {
|
|
7394
|
+
if (inFlight) return;
|
|
7395
|
+
const at = now();
|
|
7396
|
+
if (roundAt !== void 0 && at - roundAt < 3e4) return;
|
|
7397
|
+
inFlight = true;
|
|
7398
|
+
runRound().catch(() => {
|
|
7399
|
+
inFlight = false;
|
|
7400
|
+
});
|
|
7401
|
+
},
|
|
7402
|
+
snapshot: () => {
|
|
7403
|
+
const at = now();
|
|
7404
|
+
const stale = roundAt === void 0 || at - roundAt >= 3e4 * 2;
|
|
7405
|
+
const samples = [];
|
|
7406
|
+
for (const endpoint of endpoints) {
|
|
7407
|
+
const site = sites.get(endpoint.id);
|
|
7408
|
+
samples.push({
|
|
7409
|
+
id: endpoint.id,
|
|
7410
|
+
label: endpoint.label ?? endpoint.id,
|
|
7411
|
+
ms: site.ms,
|
|
7412
|
+
tone: toneFor(site, stale)
|
|
7413
|
+
});
|
|
7414
|
+
}
|
|
7415
|
+
return {
|
|
7416
|
+
samples,
|
|
7417
|
+
roundAt,
|
|
7418
|
+
stale,
|
|
7419
|
+
pending: inFlight
|
|
7420
|
+
};
|
|
7421
|
+
}
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
function toneFor(site, stale) {
|
|
7425
|
+
if (stale) return "dim";
|
|
7426
|
+
if (site.failures >= 2) return "down";
|
|
7427
|
+
return hubLatencyTone(site.ms, site.failures > 0);
|
|
7428
|
+
}
|
|
7429
|
+
/**
|
|
7430
|
+
* Merge the measured provider reading with the probe samples into Hub rows.
|
|
7431
|
+
* The provider row always leads (it answers "is the model I'm using reachable"),
|
|
7432
|
+
* and it staleness-checks against its own budget: a long idle stretch means
|
|
7433
|
+
* nothing was asked, not that the network died.
|
|
7434
|
+
*/
|
|
7435
|
+
function buildHubSamples(snapshot, provider, now = Date.now()) {
|
|
7436
|
+
const ageMs = provider.sampledAt === void 0 ? 0 : now - provider.sampledAt;
|
|
7437
|
+
return [{
|
|
7438
|
+
id: HUB_MODEL_ROW_ID,
|
|
7439
|
+
ms: provider.ms,
|
|
7440
|
+
tone: hubLatencyTone(provider.ms, ageMs >= HUB_MODEL_STALE_MS)
|
|
7441
|
+
}, ...snapshot.samples];
|
|
7442
|
+
}
|
|
7443
|
+
/** Shared tone rule for every Hub row (probe sites and the measured provider). */
|
|
7444
|
+
function hubLatencyTone(ms, stale = false) {
|
|
7445
|
+
if (ms === void 0 || stale) return "dim";
|
|
7446
|
+
return ms <= 100 ? "ok" : "warn";
|
|
7447
|
+
}
|
|
7448
|
+
/**
|
|
7449
|
+
* Compact latency reading: whole milliseconds below a second, one-decimal
|
|
7450
|
+
* seconds above — a probe that never answers must never read as `65400ms`.
|
|
7451
|
+
*/
|
|
7452
|
+
function formatHubLatency(ms) {
|
|
7453
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
7454
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
7455
|
+
}
|
|
7456
|
+
//#endregion
|
|
7457
|
+
//#region src/tui/components/sidebar/panels/hub-panel.ts
|
|
7458
|
+
/** Pending marker: three middle dots (Latin-1, so terminal width is stable). */
|
|
7459
|
+
const PENDING_MARK = "···";
|
|
7460
|
+
/** No reading at all — a site that stopped answering, or nothing measured yet. */
|
|
7461
|
+
const EMPTY_MARK = "--";
|
|
7462
|
+
/**
|
|
7463
|
+
* Hub panel: network health at a glance. One row per reading — the measured
|
|
7464
|
+
* model-provider response time first, then a probe round trip for each
|
|
7465
|
+
* well-known endpoint.
|
|
7466
|
+
*
|
|
7467
|
+
* Values share one right edge through the same two-column engine the Session
|
|
7468
|
+
* panel uses, so rows never jitter as numbers change width, and only the value
|
|
7469
|
+
* carries colour (green ≤100ms, amber above it, red when a site stopped
|
|
7470
|
+
* answering). The panel is a pure view: probing happens in the data provider,
|
|
7471
|
+
* which is what keeps a slow endpoint from ever touching a frame.
|
|
7472
|
+
*/
|
|
7473
|
+
var HubPanelContent = class {
|
|
7474
|
+
ctx;
|
|
7475
|
+
colors;
|
|
7476
|
+
constructor(ctx, colors) {
|
|
7477
|
+
this.ctx = ctx;
|
|
7478
|
+
this.colors = colors;
|
|
7479
|
+
}
|
|
7480
|
+
invalidate() {}
|
|
7481
|
+
render(width) {
|
|
7482
|
+
const hub = this.ctx.getData().hub;
|
|
7483
|
+
if (hub === void 0 || hub.samples.length === 0) return [];
|
|
7484
|
+
return layoutSidebarGrid(hub.samples.map((sample) => ({
|
|
7485
|
+
marker: "●",
|
|
7486
|
+
label: this.labelOf(sample),
|
|
7487
|
+
value: this.reading(sample, hub)
|
|
7488
|
+
})), width).map((cell, index) => {
|
|
7489
|
+
const tone = hub.samples[index]?.tone ?? "dim";
|
|
7490
|
+
return `${chalk.hex(this.colors.textDim)(`${cell.marker}${cell.label}`)}${" ".repeat(cell.gap)}${chalk.hex(this.toneColor(tone))(cell.value ?? "")}`;
|
|
7491
|
+
});
|
|
7492
|
+
}
|
|
7493
|
+
labelOf(sample) {
|
|
7494
|
+
return sample.id === "model" ? t("sidebar.hub_model") : sample.label ?? sample.id;
|
|
7495
|
+
}
|
|
7496
|
+
reading(sample, hub) {
|
|
7497
|
+
if (sample.ms !== void 0) return formatHubLatency(sample.ms);
|
|
7498
|
+
if (hub.pending && sample.tone === "dim" && sample.id !== "model") return PENDING_MARK;
|
|
7499
|
+
return EMPTY_MARK;
|
|
7500
|
+
}
|
|
7501
|
+
toneColor(tone) {
|
|
7502
|
+
switch (tone) {
|
|
7503
|
+
case "ok": return this.colors.success;
|
|
7504
|
+
case "warn": return this.colors.warning;
|
|
7505
|
+
case "down": return this.colors.error;
|
|
7506
|
+
case "dim": return this.colors.textDim;
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
};
|
|
7510
|
+
const hubPanel = {
|
|
7511
|
+
id: "hub",
|
|
7512
|
+
get title() {
|
|
7513
|
+
return t("sidebar.hub");
|
|
7514
|
+
},
|
|
7515
|
+
width: 30,
|
|
7516
|
+
build(ctx) {
|
|
7517
|
+
return new HubPanelContent(ctx, ctx.colors);
|
|
7518
|
+
}
|
|
7519
|
+
};
|
|
6977
7520
|
//#endregion
|
|
6978
7521
|
//#region src/utils/git/git-status.ts
|
|
6979
7522
|
/**
|
|
@@ -7533,6 +8076,9 @@ var LifecycleController = class LifecycleController {
|
|
|
7533
8076
|
/** Sidebar-only git cache (the footer owns its own). Rebuilt when the
|
|
7534
8077
|
* workDir changes; `getStatus()` is TTL-cached so polling is cheap. */
|
|
7535
8078
|
sidebarGitCache;
|
|
8079
|
+
/** Sidebar Hub probe: read-through TTL, no timer — probing happens because a
|
|
8080
|
+
* frame asked, so a closed sidebar produces no traffic. */
|
|
8081
|
+
hubProbe = createHubProbe();
|
|
7536
8082
|
sidebarGitCacheWorkDir = "";
|
|
7537
8083
|
/** Session start time for the sidebar uptime counter. Reset on session
|
|
7538
8084
|
* switch so a resumed old session doesn't show the TUI process age. */
|
|
@@ -7729,6 +8275,7 @@ var LifecycleController = class LifecycleController {
|
|
|
7729
8275
|
if (!m.allPanels.some((p) => p.id === "session")) m.register(sessionPanel);
|
|
7730
8276
|
if (!m.allPanels.some((p) => p.id === "agents")) m.register(agentsPanel);
|
|
7731
8277
|
if (!m.allPanels.some((p) => p.id === "goal")) m.register(goalPanel);
|
|
8278
|
+
if (!m.allPanels.some((p) => p.id === "hub")) m.register(hubPanel);
|
|
7732
8279
|
m.setDataProvider(() => this.buildSidebarData());
|
|
7733
8280
|
}
|
|
7734
8281
|
const sidebar = this.host.state.sidebarPane;
|
|
@@ -7770,9 +8317,25 @@ var LifecycleController = class LifecycleController {
|
|
|
7770
8317
|
git: this.readSidebarGit(appState.workDir),
|
|
7771
8318
|
sessionStats: this.readSidebarSessionStats(appState),
|
|
7772
8319
|
agents: this.host.sessionEventHandler.getSubagentSlots(),
|
|
8320
|
+
hub: this.readSidebarHub(),
|
|
7773
8321
|
goal: this.readSidebarGoal(appState)
|
|
7774
8322
|
};
|
|
7775
8323
|
}
|
|
8324
|
+
/**
|
|
8325
|
+
* Hub block. Probe rounds are kicked off from the render path only (the
|
|
8326
|
+
* sidebar container stops rendering while closed), and the round is always
|
|
8327
|
+
* fire-and-forget — a dead endpoint can never hold up a frame. The model row
|
|
8328
|
+
* is different: it reads the first-token latency of real requests, so it is
|
|
8329
|
+
* measured rather than probed, and it goes stale on its own budget.
|
|
8330
|
+
*/
|
|
8331
|
+
readSidebarHub() {
|
|
8332
|
+
if (this.host.state.sidebarManager?.isOpen === true) this.hubProbe.sampleIfStale();
|
|
8333
|
+
const snapshot = this.hubProbe.snapshot();
|
|
8334
|
+
return {
|
|
8335
|
+
samples: buildHubSamples(snapshot, this.host.sessionEventHandler.getProviderLatency()),
|
|
8336
|
+
pending: snapshot.pending
|
|
8337
|
+
};
|
|
8338
|
+
}
|
|
7776
8339
|
readSidebarGit(workDir) {
|
|
7777
8340
|
if (this.sidebarGitCache === void 0 || this.sidebarGitCacheWorkDir !== workDir) {
|
|
7778
8341
|
this.sidebarGitCache = createGitStatusCache(workDir);
|
|
@@ -13632,7 +14195,7 @@ var ScreamTUI = class {
|
|
|
13632
14195
|
const card = new OnboardingCardComponent({
|
|
13633
14196
|
colors: this.state.theme.colors,
|
|
13634
14197
|
onConfigure: () => {
|
|
13635
|
-
import("./dispatch-
|
|
14198
|
+
import("./dispatch-B6jbxY0D.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
|
|
13636
14199
|
this.restoreEditor();
|
|
13637
14200
|
onDone?.();
|
|
13638
14201
|
});
|
|
@@ -14459,15 +15022,13 @@ async function runChannelSetup() {
|
|
|
14459
15022
|
//#endregion
|
|
14460
15023
|
//#region src/cli/run-stream-json.ts
|
|
14461
15024
|
/**
|
|
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.
|
|
15025
|
+
* Stream-json stdio adapter (cc-connect compat).
|
|
14468
15026
|
*
|
|
14469
|
-
*
|
|
14470
|
-
*
|
|
15027
|
+
* Reads stdin as line-based stream-json messages, processes them through the
|
|
15028
|
+
* ScreamCode agent, and writes stdout as stream-json events. This is the
|
|
15029
|
+
* integration surface cc-connect drives when it spawns `scream stream-json`,
|
|
15030
|
+
* and any tool that speaks the same stdio protocol can use ScreamCode as an
|
|
15031
|
+
* agent backend through it.
|
|
14471
15032
|
*/
|
|
14472
15033
|
function buildStreamJsonRuntimePrompt(input) {
|
|
14473
15034
|
const appendParts = [
|
|
@@ -14480,7 +15041,7 @@ function buildStreamJsonRuntimePrompt(input) {
|
|
|
14480
15041
|
append: appendParts.join("\n\n")
|
|
14481
15042
|
};
|
|
14482
15043
|
}
|
|
14483
|
-
var
|
|
15044
|
+
var StreamJsonWriter = class {
|
|
14484
15045
|
writeLine;
|
|
14485
15046
|
sessionId = "";
|
|
14486
15047
|
msgCounter = 0;
|
|
@@ -14611,7 +15172,7 @@ var ClaudeStreamJsonWriter = class {
|
|
|
14611
15172
|
output: 0
|
|
14612
15173
|
};
|
|
14613
15174
|
}
|
|
14614
|
-
/** Emit a resume hint as a meta message
|
|
15175
|
+
/** Emit a resume hint as a stream-json meta message. */
|
|
14615
15176
|
emitResumeHint(sessionId) {
|
|
14616
15177
|
this.writeJson({
|
|
14617
15178
|
role: "meta",
|
|
@@ -14686,7 +15247,7 @@ async function* readStdinMessages() {
|
|
|
14686
15247
|
else log.debug("stream-json: ignoring unknown stdin message type", { type: msg.type });
|
|
14687
15248
|
}
|
|
14688
15249
|
}
|
|
14689
|
-
/** Extract plain text from a
|
|
15250
|
+
/** Extract plain text from a stream-json user message. */
|
|
14690
15251
|
function extractUserText(msg) {
|
|
14691
15252
|
const content = msg.message.content;
|
|
14692
15253
|
if (typeof content === "string") return content;
|
|
@@ -14727,7 +15288,7 @@ function mapCcConnectMode(mode) {
|
|
|
14727
15288
|
}
|
|
14728
15289
|
/**
|
|
14729
15290
|
* Install the stdout EPIPE guard and return the line writer handed to
|
|
14730
|
-
* {@link
|
|
15291
|
+
* {@link StreamJsonWriter}.
|
|
14731
15292
|
*
|
|
14732
15293
|
* cc-connect closes our stdout when it kills/restarts the agent (idle
|
|
14733
15294
|
* timeout, /restart, config change). Writing after that raises EPIPE as an
|
|
@@ -14783,7 +15344,7 @@ async function runStreamJson(opts) {
|
|
|
14783
15344
|
}
|
|
14784
15345
|
const streamSubagentModels = streamTuiConfig.subagentModels;
|
|
14785
15346
|
harness.setSubagentModelBindings(() => streamSubagentModels);
|
|
14786
|
-
const writer = new
|
|
15347
|
+
const writer = new StreamJsonWriter(installStdoutEpipeGuard());
|
|
14787
15348
|
let session;
|
|
14788
15349
|
let currentSessionId;
|
|
14789
15350
|
let sessionKey = "cc-connect-main";
|