scream-code 0.13.3 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-DFhvkeiL.mjs → app-CT7KRNF5.mjs} +106 -11
- package/dist/{dispatch-C7WVQ8OE.mjs → dispatch-BKrBb5IE.mjs} +1 -1
- package/dist/{dispatch-sa_KwTpz.mjs → dispatch-Dk8_Wqp6.mjs} +1073 -18
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-Dfg978Sj.mjs → text-input-dialog-CYs9xQZi.mjs} +1 -1
- package/dist/{text-input-dialog-Cwnq9vFO.mjs → text-input-dialog-ClcJf9pu.mjs} +8 -0
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
6
6
|
import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
|
|
7
7
|
import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
|
|
8
8
|
import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
|
|
9
|
-
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-
|
|
9
|
+
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-ClcJf9pu.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
12
12
|
import * as fs$1 from "node:fs/promises";
|
|
@@ -44525,22 +44525,25 @@ function extractUsage(usage) {
|
|
|
44525
44525
|
const completionTokens = typeof u["completion_tokens"] === "number" ? u["completion_tokens"] : 0;
|
|
44526
44526
|
let cached = 0;
|
|
44527
44527
|
let other = 0;
|
|
44528
|
+
let created = 0;
|
|
44529
|
+
const details = typeof u["prompt_tokens_details"] === "object" && u["prompt_tokens_details"] !== null ? u["prompt_tokens_details"] : void 0;
|
|
44528
44530
|
if (typeof u["prompt_cache_hit_tokens"] === "number") {
|
|
44529
44531
|
cached = u["prompt_cache_hit_tokens"];
|
|
44530
44532
|
other = typeof u["prompt_cache_miss_tokens"] === "number" ? u["prompt_cache_miss_tokens"] : Math.max(0, promptTokens - cached);
|
|
44531
44533
|
} else {
|
|
44532
44534
|
if (typeof u["cached_tokens"] === "number") cached = u["cached_tokens"];
|
|
44533
|
-
else if (typeof
|
|
44534
|
-
const details = u["prompt_tokens_details"];
|
|
44535
|
-
if (typeof details["cached_tokens"] === "number") cached = details["cached_tokens"];
|
|
44536
|
-
}
|
|
44535
|
+
else if (details !== void 0 && typeof details["cached_tokens"] === "number") cached = details["cached_tokens"];
|
|
44537
44536
|
other = Math.max(0, promptTokens - cached);
|
|
44537
|
+
if (details !== void 0 && typeof details["cache_write_tokens"] === "number") {
|
|
44538
|
+
created = details["cache_write_tokens"];
|
|
44539
|
+
other = Math.max(0, other - created);
|
|
44540
|
+
}
|
|
44538
44541
|
}
|
|
44539
44542
|
return {
|
|
44540
44543
|
inputOther: other,
|
|
44541
44544
|
output: completionTokens,
|
|
44542
44545
|
inputCacheRead: cached,
|
|
44543
|
-
inputCacheCreation:
|
|
44546
|
+
inputCacheCreation: created
|
|
44544
44547
|
};
|
|
44545
44548
|
}
|
|
44546
44549
|
/**
|
|
@@ -82866,7 +82869,7 @@ function restoreAgentRecord(agent, input) {
|
|
|
82866
82869
|
agent.permission.recordApprovalResult(input);
|
|
82867
82870
|
return;
|
|
82868
82871
|
case "usage.record":
|
|
82869
|
-
agent.usage.record(input.model, input.usage, "session");
|
|
82872
|
+
agent.usage.record(input.model, input.usage, input.usageScope ?? "session", { skipCurrentTurn: true });
|
|
82870
82873
|
return;
|
|
82871
82874
|
case "full_compaction.begin":
|
|
82872
82875
|
agent.fullCompaction.begin(input);
|
|
@@ -96982,6 +96985,8 @@ const TURN_DEFAULTS = {
|
|
|
96982
96985
|
};
|
|
96983
96986
|
//#endregion
|
|
96984
96987
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
96988
|
+
/** Cap on how long the first turn waits for MCP servers to finish loading. */
|
|
96989
|
+
const MCP_WAIT_TIMEOUT_MS = 1e4;
|
|
96985
96990
|
var TurnFlow = class {
|
|
96986
96991
|
agent;
|
|
96987
96992
|
steerBuffer = [];
|
|
@@ -97347,7 +97352,7 @@ var TurnFlow = class {
|
|
|
97347
97352
|
async runTurn(turnId, signal) {
|
|
97348
97353
|
let stopHookContinuationUsed = false;
|
|
97349
97354
|
const deduper = new ToolCallDeduplicator();
|
|
97350
|
-
await this.agent.mcp?.waitForInitialLoad(signal);
|
|
97355
|
+
await Promise.race([this.agent.mcp?.waitForInitialLoad(signal) ?? Promise.resolve(), new Promise((resolve) => setTimeout(resolve, MCP_WAIT_TIMEOUT_MS))]);
|
|
97351
97356
|
while (true) {
|
|
97352
97357
|
signal.throwIfAborted();
|
|
97353
97358
|
const model = this.agent.config.model;
|
|
@@ -97933,6 +97938,14 @@ var UsageRecorder = class {
|
|
|
97933
97938
|
agent;
|
|
97934
97939
|
byModel = {};
|
|
97935
97940
|
currentTurn;
|
|
97941
|
+
/**
|
|
97942
|
+
* Session-wide, turn-scoped usage only (`scope === 'turn'`). Restored from
|
|
97943
|
+
* the wire log on resume (records restore replays `usage.record` with its
|
|
97944
|
+
* original scope), so the TUI's per-session HitR survives process restarts
|
|
97945
|
+
* instead of resetting to zero. Compaction summaries (scope 'session')
|
|
97946
|
+
* never enter this total, matching the live turn.step.completed accumulation.
|
|
97947
|
+
*/
|
|
97948
|
+
turnTotal;
|
|
97936
97949
|
constructor(agent) {
|
|
97937
97950
|
this.agent = agent;
|
|
97938
97951
|
}
|
|
@@ -97942,7 +97955,7 @@ var UsageRecorder = class {
|
|
|
97942
97955
|
endTurn() {
|
|
97943
97956
|
this.currentTurn = void 0;
|
|
97944
97957
|
}
|
|
97945
|
-
record(model, usage, scope = "session") {
|
|
97958
|
+
record(model, usage, scope = "session", opts) {
|
|
97946
97959
|
this.agent?.records.logRecord({
|
|
97947
97960
|
type: "usage.record",
|
|
97948
97961
|
model,
|
|
@@ -97951,7 +97964,10 @@ var UsageRecorder = class {
|
|
|
97951
97964
|
});
|
|
97952
97965
|
const current = this.byModel[model];
|
|
97953
97966
|
this.byModel[model] = current === void 0 ? copyUsage(usage) : addUsage(current, usage);
|
|
97954
|
-
if (scope === "turn")
|
|
97967
|
+
if (scope === "turn") {
|
|
97968
|
+
if (opts?.skipCurrentTurn !== true) this.currentTurn = this.currentTurn === void 0 ? copyUsage(usage) : addUsage(this.currentTurn, usage);
|
|
97969
|
+
this.turnTotal = this.turnTotal === void 0 ? copyUsage(usage) : addUsage(this.turnTotal, usage);
|
|
97970
|
+
}
|
|
97955
97971
|
this.agent?.emitStatusUpdated();
|
|
97956
97972
|
}
|
|
97957
97973
|
data() {
|
|
@@ -97961,12 +97977,13 @@ var UsageRecorder = class {
|
|
|
97961
97977
|
return {
|
|
97962
97978
|
byModel: hasByModel ? byModel : void 0,
|
|
97963
97979
|
total: hasByModel ? totalUsage(byModel) : void 0,
|
|
97964
|
-
currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn)
|
|
97980
|
+
currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn),
|
|
97981
|
+
...this.turnTotal !== void 0 ? { turnTotal: copyUsage(this.turnTotal) } : {}
|
|
97965
97982
|
};
|
|
97966
97983
|
}
|
|
97967
97984
|
status() {
|
|
97968
97985
|
const status = this.data();
|
|
97969
|
-
if (status.byModel === void 0 && status.total === void 0 && status.currentTurn === void 0) return;
|
|
97986
|
+
if (status.byModel === void 0 && status.total === void 0 && status.currentTurn === void 0 && status.turnTotal === void 0) return;
|
|
97970
97987
|
return status;
|
|
97971
97988
|
}
|
|
97972
97989
|
byModelSnapshot() {
|
|
@@ -102165,6 +102182,19 @@ var StdioMcpClient = class {
|
|
|
102165
102182
|
await this.closeStartedClient();
|
|
102166
102183
|
}
|
|
102167
102184
|
/**
|
|
102185
|
+
* Synchronously terminate the child process, for the process-exit fallback
|
|
102186
|
+
* where `close()` (async, awaits transport cleanup) cannot run. The SDK
|
|
102187
|
+
* transport exposes the child pid but not the child handle, so we signal it
|
|
102188
|
+
* directly. Safe to call on an already-exited or never-started process.
|
|
102189
|
+
*/
|
|
102190
|
+
killSync() {
|
|
102191
|
+
const pid = this.transport.pid;
|
|
102192
|
+
if (pid === null || pid <= 0) return;
|
|
102193
|
+
try {
|
|
102194
|
+
process.kill(pid, "SIGTERM");
|
|
102195
|
+
} catch {}
|
|
102196
|
+
}
|
|
102197
|
+
/**
|
|
102168
102198
|
* Register a listener that fires when the underlying transport closes on
|
|
102169
102199
|
* its own — i.e. the caller has not yet invoked {@link close}. At most one
|
|
102170
102200
|
* listener can be installed; later registrations replace earlier ones.
|
|
@@ -102325,6 +102355,7 @@ var McpConnectionManager = class {
|
|
|
102325
102355
|
this.options = options;
|
|
102326
102356
|
this.oauthService = options.oauthService;
|
|
102327
102357
|
this.log = options.log ?? log;
|
|
102358
|
+
process.on("exit", () => this.killAllSync());
|
|
102328
102359
|
}
|
|
102329
102360
|
/**
|
|
102330
102361
|
* Returns the URL of an HTTP MCP server by name, or `undefined` for
|
|
@@ -102563,6 +102594,19 @@ var McpConnectionManager = class {
|
|
|
102563
102594
|
await client.close();
|
|
102564
102595
|
} catch {}
|
|
102565
102596
|
}
|
|
102597
|
+
/**
|
|
102598
|
+
* Synchronously signal every still-running stdio child process. Registered
|
|
102599
|
+
* as a `process.on('exit')` fallback so MCP children never survive the host
|
|
102600
|
+
* — whether the app exits cleanly, is killed, or the terminal is closed.
|
|
102601
|
+
* `close()` (async) remains the graceful path; this only runs when the
|
|
102602
|
+
* event loop is already unwinding.
|
|
102603
|
+
*/
|
|
102604
|
+
killAllSync() {
|
|
102605
|
+
for (const entry of this.entries.values()) {
|
|
102606
|
+
const client = entry.client;
|
|
102607
|
+
if (client instanceof StdioMcpClient) client.killSync();
|
|
102608
|
+
}
|
|
102609
|
+
}
|
|
102566
102610
|
isCurrent(entry, attemptId) {
|
|
102567
102611
|
return this.entries.get(entry.name) === entry && entry.attemptId === attemptId;
|
|
102568
102612
|
}
|
|
@@ -121024,7 +121068,7 @@ var SDKRpcClient = class {
|
|
|
121024
121068
|
const maxContextTokens = config.modelCapabilities?.max_context_tokens ?? 0;
|
|
121025
121069
|
const contextTokens = context.tokenCount;
|
|
121026
121070
|
const contextUsage = maxContextTokens > 0 ? contextTokens / maxContextTokens : 0;
|
|
121027
|
-
const hasUsage = usage.byModel !== void 0 || usage.total !== void 0 || usage.currentTurn !== void 0;
|
|
121071
|
+
const hasUsage = usage.byModel !== void 0 || usage.total !== void 0 || usage.currentTurn !== void 0 || usage.turnTotal !== void 0;
|
|
121028
121072
|
return {
|
|
121029
121073
|
model: config.modelAlias ?? config.provider?.model,
|
|
121030
121074
|
thinkingLevel: config.thinkingLevel,
|
|
@@ -122752,6 +122796,20 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122752
122796
|
description: "registry.logout_desc",
|
|
122753
122797
|
priority: 179
|
|
122754
122798
|
},
|
|
122799
|
+
{
|
|
122800
|
+
name: "search",
|
|
122801
|
+
aliases: [],
|
|
122802
|
+
description: "registry.search_desc",
|
|
122803
|
+
priority: 178,
|
|
122804
|
+
availability: "always"
|
|
122805
|
+
},
|
|
122806
|
+
{
|
|
122807
|
+
name: "trace",
|
|
122808
|
+
aliases: [],
|
|
122809
|
+
description: "registry.trace_desc",
|
|
122810
|
+
priority: 177,
|
|
122811
|
+
availability: "always"
|
|
122812
|
+
},
|
|
122755
122813
|
{
|
|
122756
122814
|
name: "exit",
|
|
122757
122815
|
aliases: ["quit", "q"],
|
|
@@ -122924,6 +122982,14 @@ const SESSION_TIPS = [
|
|
|
122924
122982
|
{
|
|
122925
122983
|
i18nKey: "editor.tip_12",
|
|
122926
122984
|
isAd: false
|
|
122985
|
+
},
|
|
122986
|
+
{
|
|
122987
|
+
i18nKey: "editor.tip_13",
|
|
122988
|
+
isAd: false
|
|
122989
|
+
},
|
|
122990
|
+
{
|
|
122991
|
+
i18nKey: "editor.tip_14",
|
|
122992
|
+
isAd: false
|
|
122927
122993
|
}
|
|
122928
122994
|
];
|
|
122929
122995
|
/** Interval for random tip rotation (ms). */
|
|
@@ -124040,6 +124106,989 @@ async function handleDiyConfig(host) {
|
|
|
124040
124106
|
host.showStatus(t("auth.connected", { name: `${providerId} · ${modelId} (${wire})` }));
|
|
124041
124107
|
}
|
|
124042
124108
|
//#endregion
|
|
124109
|
+
//#region src/tui/commands/search.ts
|
|
124110
|
+
/**
|
|
124111
|
+
* Open the full-screen conversation search overlay (same as Ctrl+Shift+F).
|
|
124112
|
+
* The overlay is owned by pi-tui; `openSearch` is a TS-private method but a
|
|
124113
|
+
* plain instance method at runtime, so we reach it through a cast instead of
|
|
124114
|
+
* adding an upstream API for a single caller.
|
|
124115
|
+
*/
|
|
124116
|
+
function handleSearchCommand(host) {
|
|
124117
|
+
host.state.ui?.openSearch?.();
|
|
124118
|
+
}
|
|
124119
|
+
//#endregion
|
|
124120
|
+
//#region src/utils/trace/trace-builder.ts
|
|
124121
|
+
/**
|
|
124122
|
+
* Build trace cells from a session's wire log (`wire.jsonl`).
|
|
124123
|
+
*
|
|
124124
|
+
* The wire log records the full conversation trajectory: user prompts, model
|
|
124125
|
+
* requests (request.header), step content blocks (thinking / text / tool-call),
|
|
124126
|
+
* tool calls and results, usage records and compactions. This module replays
|
|
124127
|
+
* the log in order and flattens it into the closed `TraceCell` model.
|
|
124128
|
+
*
|
|
124129
|
+
* Parsing is intentionally loose (records are plain JSON) so the command does
|
|
124130
|
+
* not depend on the agent-core wire types; unknown/foreign records are
|
|
124131
|
+
* skipped defensively.
|
|
124132
|
+
*/
|
|
124133
|
+
function asRecord(value) {
|
|
124134
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
124135
|
+
}
|
|
124136
|
+
function asString(value) {
|
|
124137
|
+
return typeof value === "string" ? value : void 0;
|
|
124138
|
+
}
|
|
124139
|
+
function asNumber(value) {
|
|
124140
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
124141
|
+
}
|
|
124142
|
+
function asRecordArray(value) {
|
|
124143
|
+
if (!Array.isArray(value)) return [];
|
|
124144
|
+
return value.flatMap((item) => {
|
|
124145
|
+
const rec = asRecord(item);
|
|
124146
|
+
return rec ? [rec] : [];
|
|
124147
|
+
});
|
|
124148
|
+
}
|
|
124149
|
+
function asStringArray(value) {
|
|
124150
|
+
if (!Array.isArray(value)) return [];
|
|
124151
|
+
return value.flatMap((item) => typeof item === "string" ? [item] : []);
|
|
124152
|
+
}
|
|
124153
|
+
/** Concatenate the text of content parts (text + thinking) for a prompt. */
|
|
124154
|
+
function contentPartsText(parts) {
|
|
124155
|
+
return asRecordArray(parts).map((part) => asString(part["text"]) ?? "").join("");
|
|
124156
|
+
}
|
|
124157
|
+
/**
|
|
124158
|
+
* Replay `wire.jsonl` and produce ordered trace cells.
|
|
124159
|
+
* Throws when the file is missing or contains no usable records.
|
|
124160
|
+
*/
|
|
124161
|
+
function buildTraceCells({ wirePath }) {
|
|
124162
|
+
const rows = readWireRows(wirePath);
|
|
124163
|
+
if (rows.length === 0) throw new Error(`no wire records in ${wirePath}`);
|
|
124164
|
+
const cells = [];
|
|
124165
|
+
let lastTime;
|
|
124166
|
+
let nextIndex = 1;
|
|
124167
|
+
let lastCell;
|
|
124168
|
+
const pushCell = (kind, text, fields, time) => {
|
|
124169
|
+
if (lastCell && time !== void 0 && lastCell.endAt === void 0) lastCell.endAt = time;
|
|
124170
|
+
const seconds = time !== void 0 && lastTime !== void 0 ? (time - lastTime) / 1e3 : null;
|
|
124171
|
+
if (time !== void 0) lastTime = time;
|
|
124172
|
+
const cell = {
|
|
124173
|
+
index: nextIndex++,
|
|
124174
|
+
kind,
|
|
124175
|
+
text,
|
|
124176
|
+
timeSeconds: seconds,
|
|
124177
|
+
turn: turnNo,
|
|
124178
|
+
startedAt: time,
|
|
124179
|
+
...fields
|
|
124180
|
+
};
|
|
124181
|
+
cells.push(cell);
|
|
124182
|
+
lastCell = cell;
|
|
124183
|
+
return cell;
|
|
124184
|
+
};
|
|
124185
|
+
let currentStepUuid;
|
|
124186
|
+
let currentStepStartTime;
|
|
124187
|
+
let currentBlocks = [];
|
|
124188
|
+
let currentBlock;
|
|
124189
|
+
let pendingTools = /* @__PURE__ */ new Map();
|
|
124190
|
+
let stepTools = [];
|
|
124191
|
+
let toolsInStep = [];
|
|
124192
|
+
let stepUsage;
|
|
124193
|
+
let stepFinishReason;
|
|
124194
|
+
let stepTtftMs;
|
|
124195
|
+
let stepDecodingMs;
|
|
124196
|
+
let stepModel;
|
|
124197
|
+
let currentTurnStart;
|
|
124198
|
+
let pendingSystem = [];
|
|
124199
|
+
let lastSystemTime;
|
|
124200
|
+
let turnNo = 0;
|
|
124201
|
+
const flushPendingSystem = (time) => {
|
|
124202
|
+
if (pendingSystem.length === 0) return;
|
|
124203
|
+
pushCell("system", pendingSystem.join(" · "), {
|
|
124204
|
+
requestOnly: true,
|
|
124205
|
+
sourceSeq: void 0,
|
|
124206
|
+
startedAt: lastSystemTime
|
|
124207
|
+
}, time);
|
|
124208
|
+
pendingSystem = [];
|
|
124209
|
+
lastSystemTime = void 0;
|
|
124210
|
+
};
|
|
124211
|
+
const finalizeStep = (time) => {
|
|
124212
|
+
if (currentStepUuid === void 0) return;
|
|
124213
|
+
const thinking = currentBlocks.filter((b) => b.type === "thinking").map((b) => b.text).join("");
|
|
124214
|
+
const text = currentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
124215
|
+
const summary = text.trim().replaceAll(/\s+/g, " ").slice(0, 80) || (thinking.trim() ? "思考…" : "");
|
|
124216
|
+
const toolsText = toolsInStep.join(", ");
|
|
124217
|
+
const messageCell = pushCell("message", (toolsText ? `${summary}${summary ? " — " : ""}工具: ${toolsText}` : summary) || "(空回复)", {
|
|
124218
|
+
sourceSeq: void 0,
|
|
124219
|
+
inputDetail: void 0,
|
|
124220
|
+
outputDetail: text || void 0,
|
|
124221
|
+
thinkingDetail: thinking || void 0,
|
|
124222
|
+
input: stepUsage?.["inputOther"],
|
|
124223
|
+
cacheRead: stepUsage?.["inputCacheRead"],
|
|
124224
|
+
cacheWrite: stepUsage?.["inputCacheCreation"],
|
|
124225
|
+
output: stepUsage?.["output"],
|
|
124226
|
+
ttftMs: stepTtftMs,
|
|
124227
|
+
decodingMs: stepDecodingMs,
|
|
124228
|
+
model: stepModel,
|
|
124229
|
+
finishReason: stepFinishReason,
|
|
124230
|
+
startedAt: currentStepStartTime
|
|
124231
|
+
}, time);
|
|
124232
|
+
if (currentStepStartTime !== void 0 && time !== void 0) {
|
|
124233
|
+
messageCell.timeSeconds = (time - currentStepStartTime) / 1e3;
|
|
124234
|
+
messageCell.endAt = time;
|
|
124235
|
+
}
|
|
124236
|
+
for (const tool of stepTools) pushCell("tool", `${tool.name}${tool.isError ? " ✗" : " ✓"}`, {
|
|
124237
|
+
inputDetail: tool.argsText,
|
|
124238
|
+
outputDetail: tool.resultText || void 0,
|
|
124239
|
+
result: tool.resultText.replaceAll(/\s+/g, " ").slice(0, 80) || void 0,
|
|
124240
|
+
isError: tool.isError,
|
|
124241
|
+
sourceSeq: tool.callSeq,
|
|
124242
|
+
startedAt: tool.startedAt
|
|
124243
|
+
}, time);
|
|
124244
|
+
currentStepUuid = void 0;
|
|
124245
|
+
currentStepStartTime = void 0;
|
|
124246
|
+
currentBlocks = [];
|
|
124247
|
+
currentBlock = void 0;
|
|
124248
|
+
pendingTools = /* @__PURE__ */ new Map();
|
|
124249
|
+
stepTools = [];
|
|
124250
|
+
toolsInStep = [];
|
|
124251
|
+
stepUsage = void 0;
|
|
124252
|
+
stepFinishReason = void 0;
|
|
124253
|
+
stepTtftMs = void 0;
|
|
124254
|
+
stepDecodingMs = void 0;
|
|
124255
|
+
stepModel = void 0;
|
|
124256
|
+
};
|
|
124257
|
+
const handleLoopEvent = (event, time, seq) => {
|
|
124258
|
+
switch (asString(event["type"])) {
|
|
124259
|
+
case "step.begin":
|
|
124260
|
+
currentStepUuid = asString(event["stepUuid"]) ?? asString(event["uuid"]);
|
|
124261
|
+
currentStepStartTime = time;
|
|
124262
|
+
currentBlocks = [];
|
|
124263
|
+
currentBlock = void 0;
|
|
124264
|
+
pendingTools = /* @__PURE__ */ new Map();
|
|
124265
|
+
toolsInStep = [];
|
|
124266
|
+
break;
|
|
124267
|
+
case "block.start": {
|
|
124268
|
+
const blockType = asString(event["blockType"]);
|
|
124269
|
+
if (blockType === "thinking" || blockType === "text") {
|
|
124270
|
+
currentBlock = {
|
|
124271
|
+
type: blockType,
|
|
124272
|
+
text: ""
|
|
124273
|
+
};
|
|
124274
|
+
currentBlocks.push(currentBlock);
|
|
124275
|
+
}
|
|
124276
|
+
break;
|
|
124277
|
+
}
|
|
124278
|
+
case "content.part": {
|
|
124279
|
+
const part = asRecord(event["part"]);
|
|
124280
|
+
const text = asString(part?.["text"]) ?? asString(part?.["think"]) ?? "";
|
|
124281
|
+
if (!text) break;
|
|
124282
|
+
const isThink = part?.["type"] === "think" || part?.["type"] === "thinking";
|
|
124283
|
+
if (currentBlock) currentBlock.text += text;
|
|
124284
|
+
else {
|
|
124285
|
+
const fallback = currentBlocks.at(-1);
|
|
124286
|
+
if (fallback && fallback.type === (isThink ? "thinking" : "text")) fallback.text += text;
|
|
124287
|
+
else currentBlocks.push({
|
|
124288
|
+
type: isThink ? "thinking" : "text",
|
|
124289
|
+
text
|
|
124290
|
+
});
|
|
124291
|
+
}
|
|
124292
|
+
break;
|
|
124293
|
+
}
|
|
124294
|
+
case "block.end":
|
|
124295
|
+
currentBlock = void 0;
|
|
124296
|
+
break;
|
|
124297
|
+
case "tool.call": {
|
|
124298
|
+
const name = asString(event["name"]) ?? "tool";
|
|
124299
|
+
const args = event["args"];
|
|
124300
|
+
const argsText = typeof args === "string" ? args : JSON.stringify(args ?? "");
|
|
124301
|
+
const toolCallId = asString(event["toolCallId"]) ?? asString(event["uuid"]) ?? `${name}-${seq}`;
|
|
124302
|
+
pendingTools.set(toolCallId, {
|
|
124303
|
+
name,
|
|
124304
|
+
argsText,
|
|
124305
|
+
resultText: "",
|
|
124306
|
+
startedAt: time,
|
|
124307
|
+
callSeq: seq
|
|
124308
|
+
});
|
|
124309
|
+
if (!toolsInStep.includes(name)) toolsInStep.push(name);
|
|
124310
|
+
break;
|
|
124311
|
+
}
|
|
124312
|
+
case "tool.result": {
|
|
124313
|
+
const toolCallId = asString(event["toolCallId"]) ?? "";
|
|
124314
|
+
const pending = pendingTools.get(toolCallId);
|
|
124315
|
+
const resultRec = asRecord(event["result"]);
|
|
124316
|
+
const isError = resultRec?.["isError"] === true || resultRec?.["is_error"] === true || asString(resultRec?.["error_name"]) !== void 0;
|
|
124317
|
+
const resultText = asString(resultRec?.["output"]) ?? asString(resultRec?.["result"]) ?? asString(resultRec?.["error_message"]) ?? "";
|
|
124318
|
+
if (pending) {
|
|
124319
|
+
pending.resultText = resultText;
|
|
124320
|
+
pending.isError = isError;
|
|
124321
|
+
stepTools.push(pending);
|
|
124322
|
+
pendingTools.delete(toolCallId);
|
|
124323
|
+
}
|
|
124324
|
+
break;
|
|
124325
|
+
}
|
|
124326
|
+
case "step.end": {
|
|
124327
|
+
const usage = asRecord(event["usage"]);
|
|
124328
|
+
if (usage) stepUsage = {
|
|
124329
|
+
inputOther: asNumber(usage["inputOther"]) ?? 0,
|
|
124330
|
+
inputCacheRead: asNumber(usage["inputCacheRead"]) ?? 0,
|
|
124331
|
+
inputCacheCreation: asNumber(usage["inputCacheCreation"]) ?? 0,
|
|
124332
|
+
output: asNumber(usage["output"]) ?? 0
|
|
124333
|
+
};
|
|
124334
|
+
stepFinishReason = asString(event["finishReason"]);
|
|
124335
|
+
stepTtftMs = asNumber(event["llmFirstTokenLatencyMs"]);
|
|
124336
|
+
stepDecodingMs = asNumber(event["llmStreamDurationMs"]);
|
|
124337
|
+
stepModel = asString(event["reportedModel"]);
|
|
124338
|
+
finalizeStep(time);
|
|
124339
|
+
break;
|
|
124340
|
+
}
|
|
124341
|
+
default: break;
|
|
124342
|
+
}
|
|
124343
|
+
};
|
|
124344
|
+
for (const { seq, time, record } of rows) switch (asString(record["type"])) {
|
|
124345
|
+
case "context.append_loop_event": {
|
|
124346
|
+
const event = asRecord(record["event"]);
|
|
124347
|
+
if (!event) break;
|
|
124348
|
+
handleLoopEvent(event, time, seq);
|
|
124349
|
+
break;
|
|
124350
|
+
}
|
|
124351
|
+
case "turn.prompt": {
|
|
124352
|
+
finalizeStep(time);
|
|
124353
|
+
turnNo += 1;
|
|
124354
|
+
flushPendingSystem(time);
|
|
124355
|
+
const input = record["input"];
|
|
124356
|
+
const text = contentPartsText(input).trim();
|
|
124357
|
+
pushCell("user", text.replaceAll(/\s+/g, " ").slice(0, 80) || "(空输入)", {
|
|
124358
|
+
opensTurn: true,
|
|
124359
|
+
inputDetail: text || void 0,
|
|
124360
|
+
sourceSeq: seq
|
|
124361
|
+
}, time);
|
|
124362
|
+
currentTurnStart = time;
|
|
124363
|
+
break;
|
|
124364
|
+
}
|
|
124365
|
+
case "turn.steer": {
|
|
124366
|
+
const input = record["input"];
|
|
124367
|
+
const text = contentPartsText(input).trim();
|
|
124368
|
+
pushCell("context", `转向: ${text.replaceAll(/\s+/g, " ").slice(0, 80)}`, {
|
|
124369
|
+
inputDetail: text || void 0,
|
|
124370
|
+
sourceSeq: seq
|
|
124371
|
+
}, time);
|
|
124372
|
+
break;
|
|
124373
|
+
}
|
|
124374
|
+
case "request.header": {
|
|
124375
|
+
const provider = asString(record["provider"]) ?? "";
|
|
124376
|
+
const model = asString(record["model"]) ?? "";
|
|
124377
|
+
const tools = asRecordArray(record["activeTools"]).map((t) => asString(t["name"]) ?? "");
|
|
124378
|
+
pushCell("system", `请求 ${provider ? `${provider}/` : ""}${model}`, {
|
|
124379
|
+
requestOnly: true,
|
|
124380
|
+
inputDetail: tools.length > 0 ? `工具: ${tools.join(", ")}` : void 0,
|
|
124381
|
+
sourceSeq: seq
|
|
124382
|
+
}, time);
|
|
124383
|
+
break;
|
|
124384
|
+
}
|
|
124385
|
+
case "tools.set_active_tools": {
|
|
124386
|
+
const names = asStringArray(record["names"]).length > 0 ? asStringArray(record["names"]) : asRecordArray(record["names"]).map((n) => asString(n["name"]) ?? "");
|
|
124387
|
+
pendingSystem.push(`工具集: ${names.join(", ")}`);
|
|
124388
|
+
lastSystemTime = time;
|
|
124389
|
+
break;
|
|
124390
|
+
}
|
|
124391
|
+
case "config.update": {
|
|
124392
|
+
const cfg = asRecord(record);
|
|
124393
|
+
const bits = [];
|
|
124394
|
+
if (asString(cfg?.["modelAlias"])) bits.push(`模型别名: ${cfg["modelAlias"]}`);
|
|
124395
|
+
if (asString(cfg?.["systemPrompt"])) bits.push("系统提示词已更新");
|
|
124396
|
+
if (bits.length === 0) break;
|
|
124397
|
+
pendingSystem.push(bits.join(" · "));
|
|
124398
|
+
lastSystemTime = time;
|
|
124399
|
+
break;
|
|
124400
|
+
}
|
|
124401
|
+
case "usage.record":
|
|
124402
|
+
if (currentStepUuid === void 0) {
|
|
124403
|
+
const usage = asRecord(record["usage"]);
|
|
124404
|
+
pushCell("context", "usage", {
|
|
124405
|
+
input: asNumber(usage?.["inputOther"]),
|
|
124406
|
+
cacheRead: asNumber(usage?.["inputCacheRead"]),
|
|
124407
|
+
cacheWrite: asNumber(usage?.["inputCacheCreation"]),
|
|
124408
|
+
output: asNumber(usage?.["output"]),
|
|
124409
|
+
sourceSeq: seq
|
|
124410
|
+
}, time);
|
|
124411
|
+
}
|
|
124412
|
+
break;
|
|
124413
|
+
case "full_compaction.begin": {
|
|
124414
|
+
finalizeStep(time);
|
|
124415
|
+
const reason = asString(record["reason"]);
|
|
124416
|
+
const instruction = asString(record["instruction"]);
|
|
124417
|
+
const source = asString(record["source"]);
|
|
124418
|
+
pushCell("compacted", `压缩上下文${reason ? `(${reason})` : ""}`, {
|
|
124419
|
+
sourceSeq: seq,
|
|
124420
|
+
startedAt: currentTurnStart,
|
|
124421
|
+
inputDetail: instruction || void 0,
|
|
124422
|
+
result: source ? `来源: ${source}` : void 0
|
|
124423
|
+
}, time);
|
|
124424
|
+
break;
|
|
124425
|
+
}
|
|
124426
|
+
case "micro_compaction.apply": {
|
|
124427
|
+
finalizeStep(time);
|
|
124428
|
+
const reason = asString(record["reason"]);
|
|
124429
|
+
pushCell("compacted", `微压缩${reason ? `(${reason})` : ""}`, {
|
|
124430
|
+
sourceSeq: seq,
|
|
124431
|
+
startedAt: currentTurnStart
|
|
124432
|
+
}, time);
|
|
124433
|
+
break;
|
|
124434
|
+
}
|
|
124435
|
+
default: break;
|
|
124436
|
+
}
|
|
124437
|
+
finalizeStep(void 0);
|
|
124438
|
+
flushPendingSystem(void 0);
|
|
124439
|
+
return cells;
|
|
124440
|
+
}
|
|
124441
|
+
function readWireRows(wirePath) {
|
|
124442
|
+
const content = readFileSync(wirePath, "utf8");
|
|
124443
|
+
const rows = [];
|
|
124444
|
+
let seq = 0;
|
|
124445
|
+
for (const line of content.split("\n")) {
|
|
124446
|
+
if (!line.trim()) continue;
|
|
124447
|
+
seq += 1;
|
|
124448
|
+
try {
|
|
124449
|
+
const rec = asRecord(JSON.parse(line));
|
|
124450
|
+
if (!rec) continue;
|
|
124451
|
+
const time = asNumber(rec["time"]);
|
|
124452
|
+
rows.push({
|
|
124453
|
+
seq,
|
|
124454
|
+
time,
|
|
124455
|
+
record: rec
|
|
124456
|
+
});
|
|
124457
|
+
} catch {}
|
|
124458
|
+
}
|
|
124459
|
+
return rows;
|
|
124460
|
+
}
|
|
124461
|
+
//#endregion
|
|
124462
|
+
//#region src/utils/trace/render-trace-html.ts
|
|
124463
|
+
const KIND_LABELS = {
|
|
124464
|
+
system: "SYSTEM",
|
|
124465
|
+
user: "USER",
|
|
124466
|
+
context: "CONTEXT",
|
|
124467
|
+
compacted: "COMPACTED",
|
|
124468
|
+
message: "ASSISTANT",
|
|
124469
|
+
tool: "TOOL"
|
|
124470
|
+
};
|
|
124471
|
+
const KIND_TAG_STYLE = {
|
|
124472
|
+
system: "color:#CFD3D6;background:#353638",
|
|
124473
|
+
user: "color:#679EFE;background:#34415B",
|
|
124474
|
+
context: "color:#59C984;background:#233C2C",
|
|
124475
|
+
compacted: "color:#CFD3D6;background:#353638",
|
|
124476
|
+
message: "color:#9474BC;background:#352F3A",
|
|
124477
|
+
tool: "color:#DD8629;background:#27241F"
|
|
124478
|
+
};
|
|
124479
|
+
const SPAN_COLORS = {
|
|
124480
|
+
system: "#353638",
|
|
124481
|
+
user: "#679EFE",
|
|
124482
|
+
context: "#59C984",
|
|
124483
|
+
compacted: "#CFD3D6",
|
|
124484
|
+
message: "#8C6BB5",
|
|
124485
|
+
tool: "#DD8629"
|
|
124486
|
+
};
|
|
124487
|
+
const KIND_LANE = {
|
|
124488
|
+
user: 0,
|
|
124489
|
+
context: 1,
|
|
124490
|
+
message: 1,
|
|
124491
|
+
compacted: 1,
|
|
124492
|
+
tool: 2,
|
|
124493
|
+
system: 1
|
|
124494
|
+
};
|
|
124495
|
+
const CSS = `
|
|
124496
|
+
:root { color-scheme: dark; }
|
|
124497
|
+
* { box-sizing: border-box; }
|
|
124498
|
+
html, body { height: 100%; margin: 0; }
|
|
124499
|
+
body {
|
|
124500
|
+
background: #232324; color: #F9FAFB;
|
|
124501
|
+
font: 13px/20px -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
|
124502
|
+
"Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
124503
|
+
}
|
|
124504
|
+
.mono { font-family: "SF Mono", "JetBrains Mono", "Fira Code", Consolas, Menlo, monospace; }
|
|
124505
|
+
#root { display: flex; flex-direction: column; height: 100%; }
|
|
124506
|
+
.toolbar {
|
|
124507
|
+
flex: 0 0 32px; display: flex; align-items: center; gap: 10px;
|
|
124508
|
+
padding: 0 6px; border-bottom: 1px solid rgba(255,255,255,.12);
|
|
124509
|
+
background: #232324;
|
|
124510
|
+
}
|
|
124511
|
+
.toolbar .title { font-size: 13px; font-weight: 500; color: #CFD3D6; padding-left: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
124512
|
+
.toolbar .count { font-size: 11px; color: #81858C; white-space: nowrap; }
|
|
124513
|
+
.toolbar .btn {
|
|
124514
|
+
height: 22px; padding: 0 10px; border: 1px solid rgba(255,255,255,.12);
|
|
124515
|
+
border-radius: 4px; background: #2C2C2E; color: #CFD3D6; font-size: 12px; cursor: pointer;
|
|
124516
|
+
white-space: nowrap;
|
|
124517
|
+
}
|
|
124518
|
+
.toolbar .btn:hover { background: #353638; }
|
|
124519
|
+
.toolbar .btn.on { border-color: #679EFE; color: #F9FAFB; background: #232324; }
|
|
124520
|
+
.toolbar .search {
|
|
124521
|
+
margin-left: auto; display: flex; align-items: center;
|
|
124522
|
+
flex: 0 1 220px; min-width: 84px; height: 22px; padding: 0 8px;
|
|
124523
|
+
border: 1px solid rgba(255,255,255,.12); border-radius: 4px; background: #2C2C2E;
|
|
124524
|
+
}
|
|
124525
|
+
.toolbar .search:focus-within { border-color: #679EFE; background: #232324; }
|
|
124526
|
+
.toolbar .search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: #F9FAFB; font-size: 12px; }
|
|
124527
|
+
.toolbar .search input::placeholder { color: #81858C; }
|
|
124528
|
+
.timeline {
|
|
124529
|
+
flex: 0 0 44px; position: relative; border-bottom: 1px solid rgba(255,255,255,.12);
|
|
124530
|
+
background: #1B1B1C; overflow: hidden; cursor: grab;
|
|
124531
|
+
}
|
|
124532
|
+
.timeline .lane-label { position: absolute; left: 4px; font-size: 10px; color: #81858C; line-height: 13px; }
|
|
124533
|
+
.timeline .track { position: absolute; left: 74px; right: 8px; top: 4px; bottom: 4px; }
|
|
124534
|
+
.locator {
|
|
124535
|
+
position: absolute; top: -4px; bottom: -4px; width: 2px; background: #679EFE;
|
|
124536
|
+
cursor: ew-resize; z-index: 6; pointer-events: auto; box-shadow: 0 0 6px rgba(103,158,254,.8);
|
|
124537
|
+
}
|
|
124538
|
+
.locator::after {
|
|
124539
|
+
content: ''; position: absolute; top: 0; left: -4px; width: 10px; height: 10px;
|
|
124540
|
+
background: #679EFE; border-radius: 2px;
|
|
124541
|
+
}
|
|
124542
|
+
.timeline .span {
|
|
124543
|
+
position: absolute; height: 9px; border-radius: 2px; min-width: 2px; cursor: pointer;
|
|
124544
|
+
border: 1px solid rgba(0,0,0,.25);
|
|
124545
|
+
}
|
|
124546
|
+
.timeline .span:hover { outline: 1px solid #F9FAFB; }
|
|
124547
|
+
.timeline .span.active { outline: 2px solid #679EFE; }
|
|
124548
|
+
.timeline .turnTick { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(255,255,255,.22); }
|
|
124549
|
+
.tip {
|
|
124550
|
+
position: fixed; z-index: 30; pointer-events: none; max-width: 340px;
|
|
124551
|
+
background: #2C2C2E; border: 1px solid rgba(255,255,255,.2); border-radius: 6px;
|
|
124552
|
+
padding: 8px 10px; font-size: 12px; line-height: 17px; box-shadow: 0 4px 14px rgba(0,0,0,.5);
|
|
124553
|
+
display: none; white-space: normal; word-break: break-word;
|
|
124554
|
+
}
|
|
124555
|
+
.tip .tip-title { font-weight: 600; color: #F9FAFB; }
|
|
124556
|
+
.tip .tip-facts { color: #ADB2B8; margin-top: 2px; }
|
|
124557
|
+
.tip .tip-body { color: #CFD3D6; margin-top: 2px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
124558
|
+
.split { display: flex; flex: 1; min-height: 0; }
|
|
124559
|
+
.tablePane { flex: 1; overflow-y: auto; overflow-x: hidden; }
|
|
124560
|
+
table { width: 100%; border-spacing: 0; table-layout: fixed; }
|
|
124561
|
+
col.event-column { width: 122px; }
|
|
124562
|
+
td { height: 30px; padding: 0 8px; border-bottom: 1px solid rgba(255,255,255,.06); vertical-align: middle; }
|
|
124563
|
+
td.event { padding-left: 10px; white-space: nowrap; }
|
|
124564
|
+
td.content { padding-left: 4px; }
|
|
124565
|
+
tr.row { cursor: pointer; }
|
|
124566
|
+
tr.row { content-visibility: auto; contain-intrinsic-size: 30px; }
|
|
124567
|
+
tr.row:hover { background: rgba(255,255,255,.08); }
|
|
124568
|
+
tr.row.selected { background: rgba(255,255,255,.14); }
|
|
124569
|
+
tr.row.selected td { box-shadow: inset 1px 0 0 #679EFE; }
|
|
124570
|
+
tr.turnrow td { background: #1B1B1C; font-weight: 500; }
|
|
124571
|
+
.kindTag {
|
|
124572
|
+
display: inline-flex; align-items: center; height: 19px; padding: 0 5px;
|
|
124573
|
+
border-radius: 4px; font-size: 10px; font-weight: 650; line-height: 16px;
|
|
124574
|
+
letter-spacing: .035em; max-width: 96px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
124575
|
+
}
|
|
124576
|
+
.seq { margin-left: 6px; font-size: 11px; color: #81858C; }
|
|
124577
|
+
.summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: #F9FAFB; }
|
|
124578
|
+
.toolline { font-family: "SF Mono", "JetBrains Mono", Consolas, Menlo, monospace; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
124579
|
+
.toolline .tname { color: #F9FAFB; }
|
|
124580
|
+
.toolline .targs { margin-left: 7px; color: #ADB2B8; }
|
|
124581
|
+
.toolline .tarrow { margin-left: 7px; color: #81858C; }
|
|
124582
|
+
.toolline .tresult { margin-left: 7px; color: #CFD3D6; }
|
|
124583
|
+
.toolline .terror { margin-left: 7px; color: #F25A5A; }
|
|
124584
|
+
.toolline .tempty { margin-left: 7px; color: #81858C; }
|
|
124585
|
+
.facts { color: #81858C; font-size: 11px; margin-left: 8px; display: inline; }
|
|
124586
|
+
.detail {
|
|
124587
|
+
width: clamp(320px, 38%, 440px); max-width: calc(100% - 280px);
|
|
124588
|
+
border-left: 1px solid rgba(255,255,255,.12); background: #232324;
|
|
124589
|
+
display: flex; flex-direction: column; min-height: 0;
|
|
124590
|
+
}
|
|
124591
|
+
.detail.hidden { display: none; }
|
|
124592
|
+
.detail .dhead {
|
|
124593
|
+
flex: 0 0 42px; display: flex; align-items: center; gap: 8px;
|
|
124594
|
+
padding: 0 8px 0 12px; border-bottom: 1px solid rgba(255,255,255,.12);
|
|
124595
|
+
}
|
|
124596
|
+
.detail .dhead .dname { font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
124597
|
+
.detail .dhead .dclose { margin-left: auto; width: 28px; height: 28px; border: 0; border-radius: 6px; background: transparent; color: #ADB2B8; font-size: 18px; cursor: pointer; }
|
|
124598
|
+
.detail .dhead .dclose:hover { background: rgba(255,255,255,.08); }
|
|
124599
|
+
.detail .dbody { flex: 1; overflow-y: auto; padding: 12px 14px; }
|
|
124600
|
+
.ovgrid { display: grid; grid-template-columns: 94px minmax(0, 1fr); gap: 2px 12px; font-size: 13px; }
|
|
124601
|
+
.ovgrid dt { color: #ADB2B8; }
|
|
124602
|
+
.ovgrid dd { margin: 0; color: #F9FAFB; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
124603
|
+
.section { margin-top: 16px; }
|
|
124604
|
+
.section h4 { margin: 0 0 4px; font-size: 11px; font-weight: 500; color: #CFD3D6; text-transform: uppercase; }
|
|
124605
|
+
.payload {
|
|
124606
|
+
font-family: "SF Mono", "JetBrains Mono", "Fira Code", Consolas, Menlo, monospace;
|
|
124607
|
+
font-size: 12px; line-height: 19px; background: #1B1B1C; padding: 14px;
|
|
124608
|
+
border-radius: 4px; white-space: pre-wrap; word-break: break-word; color: #CFD3D6;
|
|
124609
|
+
}
|
|
124610
|
+
.payload.error { color: #F25A5A; }
|
|
124611
|
+
.placeholder { color: #81858C; padding: 32px; text-align: center; }
|
|
124612
|
+
`;
|
|
124613
|
+
const RENDER_JS = `
|
|
124614
|
+
var cells = JSON.parse(document.getElementById('data').textContent);
|
|
124615
|
+
var labels = ${JSON.stringify(KIND_LABELS)};
|
|
124616
|
+
var tagStyles = ${JSON.stringify(KIND_TAG_STYLE)};
|
|
124617
|
+
var spanColors = ${JSON.stringify(SPAN_COLORS)};
|
|
124618
|
+
var laneOf = ${JSON.stringify(KIND_LANE)};
|
|
124619
|
+
var tbody = document.getElementById('rows');
|
|
124620
|
+
var drawer = document.getElementById('detail');
|
|
124621
|
+
var drawerName = document.getElementById('dname');
|
|
124622
|
+
var drawerBody = document.getElementById('dbody');
|
|
124623
|
+
var searchInput = document.getElementById('q');
|
|
124624
|
+
var timeline = document.getElementById('timeline-track');
|
|
124625
|
+
var track = timeline;
|
|
124626
|
+
var tablePane = document.querySelector('.tablePane');
|
|
124627
|
+
var locator = document.getElementById('locator');
|
|
124628
|
+
var currentFiltered = [];
|
|
124629
|
+
var turnsBtn = document.getElementById('turns');
|
|
124630
|
+
var callsBtn = document.getElementById('calls');
|
|
124631
|
+
var modeBtn = document.getElementById('mode');
|
|
124632
|
+
var jsonBtn = document.getElementById('json');
|
|
124633
|
+
var tip = document.getElementById('tip');
|
|
124634
|
+
var collapsedTurns = false;
|
|
124635
|
+
var collapsedCalls = false;
|
|
124636
|
+
var timeMode = false;
|
|
124637
|
+
var selectedIndex = -1;
|
|
124638
|
+
var rowEls = [];
|
|
124639
|
+
function showTip(text, x, y) {
|
|
124640
|
+
tip.innerHTML = text;
|
|
124641
|
+
tip.style.display = 'block';
|
|
124642
|
+
var w = tip.offsetWidth, h = tip.offsetHeight;
|
|
124643
|
+
var left = x + 14, top = y + 14;
|
|
124644
|
+
if (left + w > window.innerWidth - 8) left = x - w - 14;
|
|
124645
|
+
if (top + h > window.innerHeight - 8) top = y - h - 14;
|
|
124646
|
+
tip.style.left = Math.max(4, left) + 'px';
|
|
124647
|
+
tip.style.top = Math.max(4, top) + 'px';
|
|
124648
|
+
}
|
|
124649
|
+
function hideTip() { tip.style.display = 'none'; }
|
|
124650
|
+
function fmtMs(v) { if (v === undefined || v === null) return null; if (v < 1000) return v + ' ms'; return (v / 1000).toFixed(2) + ' s'; }
|
|
124651
|
+
function timingFacts(cell) {
|
|
124652
|
+
var parts = [];
|
|
124653
|
+
var ttft = fmtMs(cell.ttftMs), dec = fmtMs(cell.decodingMs);
|
|
124654
|
+
if (ttft) parts.push('TTFT ' + ttft);
|
|
124655
|
+
if (dec) parts.push('解码 ' + dec);
|
|
124656
|
+
if (cell.model) parts.push('模型 ' + cell.model);
|
|
124657
|
+
if (cell.finishReason) parts.push('结束 ' + cell.finishReason);
|
|
124658
|
+
return parts;
|
|
124659
|
+
}
|
|
124660
|
+
function esc(v) { return String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
124661
|
+
function fmtSeconds(s) {
|
|
124662
|
+
if (s === null || s === undefined) return '—';
|
|
124663
|
+
if (s < 1) return Math.round(s * 1000) + ' ms';
|
|
124664
|
+
return s.toFixed(2) + ' s';
|
|
124665
|
+
}
|
|
124666
|
+
function toolContent(cell) {
|
|
124667
|
+
var html = '<span class="tname">' + esc(cell.text) + '</span>';
|
|
124668
|
+
if (cell.inputDetail) html += '<span class="targs">' + esc(cell.inputDetail) + '</span>';
|
|
124669
|
+
if (cell.isError) html += '<span class="terror">→ ' + esc(cell.result || 'failed') + '</span>';
|
|
124670
|
+
else if (cell.result) html += '<span class="tarrow">→</span><span class="tresult">' + esc(cell.result) + '</span>';
|
|
124671
|
+
else html += '<span class="tempty">→ No output</span>';
|
|
124672
|
+
return html;
|
|
124673
|
+
}
|
|
124674
|
+
function overviewRows(cell) {
|
|
124675
|
+
var rows = [['类型', labels[cell.kind] || cell.kind], ['序号', '#' + cell.index], ['耗时', fmtSeconds(cell.timeSeconds)]];
|
|
124676
|
+
if (cell.turn) rows.push(['回合', String(cell.turn)]);
|
|
124677
|
+
if (cell.input !== undefined) rows.push(['输入', String(cell.input)]);
|
|
124678
|
+
if (cell.cacheRead) rows.push(['缓存读', String(cell.cacheRead)]);
|
|
124679
|
+
if (cell.cacheWrite) rows.push(['缓存写', String(cell.cacheWrite)]);
|
|
124680
|
+
if (cell.output !== undefined) rows.push(['输出', String(cell.output)]);
|
|
124681
|
+
var ttft = fmtMs(cell.ttftMs);
|
|
124682
|
+
if (ttft) rows.push(['TTFT', ttft]);
|
|
124683
|
+
var dec = fmtMs(cell.decodingMs);
|
|
124684
|
+
if (dec) rows.push(['解码', dec]);
|
|
124685
|
+
if (cell.model) rows.push(['模型', cell.model]);
|
|
124686
|
+
if (cell.finishReason) rows.push(['结束', cell.finishReason]);
|
|
124687
|
+
return rows.map(function (r) { return '<dt>' + esc(r[0]) + '</dt><dd>' + esc(r[1]) + '</dd>'; }).join('');
|
|
124688
|
+
}
|
|
124689
|
+
function section(title, value, cls) {
|
|
124690
|
+
if (!value) return '';
|
|
124691
|
+
return '<div class="section"><h4>' + title + '</h4><div class="payload' + (cls ? ' ' + cls : '') + '">' + esc(value) + '</div></div>';
|
|
124692
|
+
}
|
|
124693
|
+
function showDetail(i) {
|
|
124694
|
+
if (selectedIndex === i) { hideDetail(); return; }
|
|
124695
|
+
selectedIndex = i;
|
|
124696
|
+
var cell = cells[i];
|
|
124697
|
+
for (var k = 0; k < rowEls.length; k++) rowEls[k].classList.remove('selected');
|
|
124698
|
+
if (rowEls[i]) {
|
|
124699
|
+
rowEls[i].classList.add('selected');
|
|
124700
|
+
if (rowEls[i].scrollIntoView) rowEls[i].scrollIntoView({ block: 'center' });
|
|
124701
|
+
}
|
|
124702
|
+
var spans = timeline.querySelectorAll('.span');
|
|
124703
|
+
for (var s = 0; s < spans.length; s++) spans[s].classList.remove('active');
|
|
124704
|
+
if (timeline.querySelector('span[data-i="' + i + '"]')) timeline.querySelector('span[data-i="' + i + '"]').classList.add('active');
|
|
124705
|
+
drawerName.textContent = (labels[cell.kind] || cell.kind) + ' #' + cell.index;
|
|
124706
|
+
var html = '<dl class="ovgrid">' + overviewRows(cell) + '</dl>';
|
|
124707
|
+
html += section('思考', cell.thinkingDetail);
|
|
124708
|
+
html += section('内容', cell.outputDetail);
|
|
124709
|
+
html += section('输入', cell.inputDetail);
|
|
124710
|
+
if (cell.kind === 'tool') html += section('工具结果', cell.result || cell.outputDetail, cell.isError ? 'error' : '');
|
|
124711
|
+
drawerBody.innerHTML = html || '<div class="placeholder">无详情</div>';
|
|
124712
|
+
drawer.classList.remove('hidden');
|
|
124713
|
+
}
|
|
124714
|
+
function hideDetail() {
|
|
124715
|
+
selectedIndex = -1;
|
|
124716
|
+
drawer.classList.add('hidden');
|
|
124717
|
+
for (var k = 0; k < rowEls.length; k++) rowEls[k].classList.remove('selected');
|
|
124718
|
+
var spans = timeline.querySelectorAll('.span');
|
|
124719
|
+
for (var s = 0; s < spans.length; s++) spans[s].classList.remove('active');
|
|
124720
|
+
}
|
|
124721
|
+
function renderTimeline(visible) {
|
|
124722
|
+
timeline.innerHTML = '';
|
|
124723
|
+
if (visible.length < 2) return;
|
|
124724
|
+
var n = visible.length;
|
|
124725
|
+
if (timeMode && visible.every(function (c) { return c.startedAt !== undefined; })) {
|
|
124726
|
+
var min = Infinity, max = -Infinity;
|
|
124727
|
+
for (var i = 0; i < n; i++) {
|
|
124728
|
+
var s = visible[i].startedAt, e = visible[i].endAt !== undefined ? visible[i].endAt : (s || 0) + 1000;
|
|
124729
|
+
if (s < min) min = s;
|
|
124730
|
+
if (e > max) max = e;
|
|
124731
|
+
}
|
|
124732
|
+
var total = max - min;
|
|
124733
|
+
var idleCap = total * 0.05; // compress idle gaps longer than 5% of the span
|
|
124734
|
+
var cursor = min;
|
|
124735
|
+
var scaled = [];
|
|
124736
|
+
for (var j = 0; j < n; j++) {
|
|
124737
|
+
var cs = visible[j].startedAt;
|
|
124738
|
+
var ce = visible[j].endAt !== undefined ? visible[j].endAt : cs + 1000;
|
|
124739
|
+
var gap = cs - cursor;
|
|
124740
|
+
if (gap > idleCap) { min += gap - idleCap; max -= gap - idleCap; }
|
|
124741
|
+
cursor = ce;
|
|
124742
|
+
scaled.push([cs - min, ce - min]);
|
|
124743
|
+
}
|
|
124744
|
+
total = max - min;
|
|
124745
|
+
for (var k = 0; k < n; k++) {
|
|
124746
|
+
var span = makeSpan(visible[k], k, (scaled[k][0] / total) * 100, (scaled[k][1] - scaled[k][0]) / total * 100);
|
|
124747
|
+
timeline.appendChild(span);
|
|
124748
|
+
}
|
|
124749
|
+
} else {
|
|
124750
|
+
var widthPct = 100 / n;
|
|
124751
|
+
for (var m = 0; m < n; m++) {
|
|
124752
|
+
var sp = makeSpan(visible[m], m, m * widthPct, widthPct - 0.4);
|
|
124753
|
+
timeline.appendChild(sp);
|
|
124754
|
+
}
|
|
124755
|
+
}
|
|
124756
|
+
// Turn boundary ticks (time mode uses the scaled coordinates).
|
|
124757
|
+
var prevTurn = null;
|
|
124758
|
+
for (var t = 0; t < n; t++) {
|
|
124759
|
+
var tn = visible[t].turn || 0;
|
|
124760
|
+
if (prevTurn !== null && tn !== prevTurn) {
|
|
124761
|
+
var tick = document.createElement('span');
|
|
124762
|
+
tick.className = 'turnTick';
|
|
124763
|
+
if (timeMode && scaled) {
|
|
124764
|
+
tick.style.left = (scaled[t][0] / total * 100) + '%';
|
|
124765
|
+
} else {
|
|
124766
|
+
tick.style.left = (t * 100 / n) + '%';
|
|
124767
|
+
}
|
|
124768
|
+
timeline.appendChild(tick);
|
|
124769
|
+
}
|
|
124770
|
+
prevTurn = tn;
|
|
124771
|
+
}
|
|
124772
|
+
}
|
|
124773
|
+
function makeSpan(cell, idx, leftPct, widthPct) {
|
|
124774
|
+
var span = document.createElement('span');
|
|
124775
|
+
span.className = 'span';
|
|
124776
|
+
span.style.left = Math.max(0, leftPct) + '%';
|
|
124777
|
+
span.style.width = 'max(2px, ' + Math.max(0.3, widthPct) + '%)';
|
|
124778
|
+
span.style.top = (laneOf[cell.kind] || 1) * 13 + 'px';
|
|
124779
|
+
span.style.background = spanColors[cell.kind] || '#353638';
|
|
124780
|
+
span.setAttribute('data-i', String(idx));
|
|
124781
|
+
span.title = '';
|
|
124782
|
+
span.addEventListener('mouseenter', function (e) {
|
|
124783
|
+
if (rowEls[idx]) rowEls[idx].classList.add('hover');
|
|
124784
|
+
if (cells.length <= 2000) {
|
|
124785
|
+
var facts = timingFacts(cell);
|
|
124786
|
+
var ftext = [];
|
|
124787
|
+
if (cell.timeSeconds !== null && cell.timeSeconds !== undefined) ftext.push('耗时 ' + cell.timeSeconds.toFixed(1) + 's');
|
|
124788
|
+
ftext = ftext.concat(facts);
|
|
124789
|
+
showTip('<div class="tip-title">#' + cell.index + ' ' + (labels[cell.kind] || cell.kind) + '</div>' +
|
|
124790
|
+
(ftext.length ? '<div class="tip-facts">' + ftext.join(' · ') + '</div>' : '') +
|
|
124791
|
+
'<div class="tip-body">' + esc(cell.text) + '</div>', e.clientX, e.clientY);
|
|
124792
|
+
}
|
|
124793
|
+
});
|
|
124794
|
+
span.addEventListener('mousemove', function (e) { if (cells.length <= 2000) { tip.style.left = '0px'; tip.style.top = '0px'; showTip(tip.innerHTML, e.clientX, e.clientY); } });
|
|
124795
|
+
span.addEventListener('mouseleave', function () { if (rowEls[idx]) rowEls[idx].classList.remove('hover'); hideTip(); });
|
|
124796
|
+
span.addEventListener('click', function (e) {
|
|
124797
|
+
e.stopPropagation();
|
|
124798
|
+
showDetail(idx);
|
|
124799
|
+
});
|
|
124800
|
+
return span;
|
|
124801
|
+
}
|
|
124802
|
+
function render() {
|
|
124803
|
+
var q = (searchInput.value || '').toLowerCase();
|
|
124804
|
+
// Keep every cell (including requestOnly system rows) so ledger indices
|
|
124805
|
+
// stay aligned with the cells array; the timeline renders them too.
|
|
124806
|
+
currentFiltered = cells.filter(function (c) {
|
|
124807
|
+
if (collapsedCalls && c.kind === 'tool') return false;
|
|
124808
|
+
if (q && !(c.text + ' ' + (c.outputDetail || '') + ' ' + (c.thinkingDetail || '')).toLowerCase().includes(q)) return false;
|
|
124809
|
+
return true;
|
|
124810
|
+
});
|
|
124811
|
+
var filtered = currentFiltered;
|
|
124812
|
+
renderTimeline(filtered);
|
|
124813
|
+
tbody.innerHTML = '';
|
|
124814
|
+
rowEls = [];
|
|
124815
|
+
var shown = 0;
|
|
124816
|
+
var lastTurn = null;
|
|
124817
|
+
var turnCounts = {};
|
|
124818
|
+
for (var i = 0; i < filtered.length; i++) turnCounts[filtered[i].turn || 0] = (turnCounts[filtered[i].turn || 0] || 0) + 1;
|
|
124819
|
+
for (var i2 = 0; i2 < filtered.length; i2++) {
|
|
124820
|
+
var cell = filtered[i2];
|
|
124821
|
+
var turn = cell.turn || 0;
|
|
124822
|
+
var row;
|
|
124823
|
+
if (collapsedTurns) {
|
|
124824
|
+
// Collapsed mode: one summary row per turn; cell rows are skipped.
|
|
124825
|
+
if (turn !== lastTurn) {
|
|
124826
|
+
var trow = document.createElement('tr');
|
|
124827
|
+
trow.className = 'row turnrow';
|
|
124828
|
+
var tev = document.createElement('td');
|
|
124829
|
+
tev.className = 'event';
|
|
124830
|
+
tev.innerHTML = '<span class="kindTag" style="' + tagStyles.user + '">TURN</span><span class="seq">' + turn + '</span>';
|
|
124831
|
+
var tco = document.createElement('td');
|
|
124832
|
+
tco.className = 'content';
|
|
124833
|
+
var tsum = document.createElement('div');
|
|
124834
|
+
tsum.className = 'summary';
|
|
124835
|
+
tsum.textContent = cell.text;
|
|
124836
|
+
tco.appendChild(tsum);
|
|
124837
|
+
var tfacts = document.createElement('span');
|
|
124838
|
+
tfacts.className = 'facts';
|
|
124839
|
+
tfacts.textContent = '· ' + (turnCounts[turn] || 0) + ' 条';
|
|
124840
|
+
tco.appendChild(tfacts);
|
|
124841
|
+
trow.appendChild(tev); trow.appendChild(tco);
|
|
124842
|
+
trow.addEventListener('click', (function (t) {
|
|
124843
|
+
return function () {
|
|
124844
|
+
collapsedTurns = false;
|
|
124845
|
+
if (turnsBtn) turnsBtn.classList.remove('on');
|
|
124846
|
+
render();
|
|
124847
|
+
var idx = currentFiltered.findIndex(function (c) { return c.turn === t; });
|
|
124848
|
+
if (idx >= 0 && rowEls[idx]) { rowEls[idx].scrollIntoView({ block: 'center' }); showDetail(idx); }
|
|
124849
|
+
};
|
|
124850
|
+
})(turn));
|
|
124851
|
+
tbody.appendChild(trow);
|
|
124852
|
+
shown++;
|
|
124853
|
+
lastTurn = turn;
|
|
124854
|
+
}
|
|
124855
|
+
continue;
|
|
124856
|
+
}
|
|
124857
|
+
row = document.createElement('tr');
|
|
124858
|
+
row.className = 'row';
|
|
124859
|
+
if (selectedIndex === i2) row.classList.add('selected');
|
|
124860
|
+
var tag = document.createElement('span');
|
|
124861
|
+
tag.className = 'kindTag';
|
|
124862
|
+
tag.setAttribute('style', tagStyles[cell.kind]);
|
|
124863
|
+
tag.textContent = labels[cell.kind] || cell.kind;
|
|
124864
|
+
var seq = document.createElement('span');
|
|
124865
|
+
seq.className = 'seq';
|
|
124866
|
+
seq.textContent = '#' + cell.index;
|
|
124867
|
+
var eventTd = document.createElement('td');
|
|
124868
|
+
eventTd.className = 'event';
|
|
124869
|
+
eventTd.appendChild(tag); eventTd.appendChild(seq);
|
|
124870
|
+
var contentTd = document.createElement('td');
|
|
124871
|
+
contentTd.className = 'content';
|
|
124872
|
+
if (cell.kind === 'tool') {
|
|
124873
|
+
var tl = document.createElement('div');
|
|
124874
|
+
tl.className = 'toolline';
|
|
124875
|
+
tl.innerHTML = toolContent(cell);
|
|
124876
|
+
contentTd.appendChild(tl);
|
|
124877
|
+
} else {
|
|
124878
|
+
var sum = document.createElement('div');
|
|
124879
|
+
sum.className = 'summary';
|
|
124880
|
+
sum.textContent = cell.text;
|
|
124881
|
+
contentTd.appendChild(sum);
|
|
124882
|
+
var facts = document.createElement('span');
|
|
124883
|
+
facts.className = 'facts';
|
|
124884
|
+
var f = [];
|
|
124885
|
+
if (cell.timeSeconds !== null && cell.timeSeconds !== undefined) f.push(fmtSeconds(cell.timeSeconds));
|
|
124886
|
+
f = f.concat(timingFacts(cell));
|
|
124887
|
+
if (cell.input !== undefined) f.push('in ' + cell.input);
|
|
124888
|
+
if (cell.cacheRead) f.push('read ' + cell.cacheRead);
|
|
124889
|
+
if (cell.cacheWrite) f.push('write ' + cell.cacheWrite);
|
|
124890
|
+
if (cell.output !== undefined) f.push('out ' + cell.output);
|
|
124891
|
+
if (f.length) facts.textContent = '· ' + f.join(' · ');
|
|
124892
|
+
contentTd.appendChild(facts);
|
|
124893
|
+
}
|
|
124894
|
+
row.appendChild(eventTd); row.appendChild(contentTd);
|
|
124895
|
+
(function (idx, el) { el.addEventListener('click', function () { showDetail(idx); }); })(i2, row);
|
|
124896
|
+
tbody.appendChild(row);
|
|
124897
|
+
rowEls[i2] = row;
|
|
124898
|
+
shown++;
|
|
124899
|
+
lastTurn = turn;
|
|
124900
|
+
}
|
|
124901
|
+
if (!shown) tbody.innerHTML = '<tr><td colspan="2"><div class="placeholder">无匹配记录</div></td></tr>';
|
|
124902
|
+
document.getElementById('count').textContent = shown + ' 条';
|
|
124903
|
+
}
|
|
124904
|
+
if (searchInput) searchInput.addEventListener('input', render);
|
|
124905
|
+
if (turnsBtn) turnsBtn.addEventListener('click', function () { collapsedTurns = !collapsedTurns; turnsBtn.classList.toggle('on', collapsedTurns); render(); });
|
|
124906
|
+
if (callsBtn) callsBtn.addEventListener('click', function () { collapsedCalls = !collapsedCalls; callsBtn.classList.toggle('on', collapsedCalls); render(); });
|
|
124907
|
+
if (modeBtn) modeBtn.addEventListener('click', function () { timeMode = !timeMode; modeBtn.textContent = timeMode ? 'Time' : 'Seq'; modeBtn.classList.toggle('on', timeMode); render(); });
|
|
124908
|
+
if (jsonBtn) jsonBtn.addEventListener('click', function () {
|
|
124909
|
+
var blob = new Blob([JSON.stringify({ cells: cells }, null, 2)], { type: 'application/json' });
|
|
124910
|
+
var url = URL.createObjectURL(blob);
|
|
124911
|
+
var a = document.createElement('a');
|
|
124912
|
+
a.href = url;
|
|
124913
|
+
a.download = 'scream-trace.json';
|
|
124914
|
+
a.click();
|
|
124915
|
+
URL.revokeObjectURL(url);
|
|
124916
|
+
});
|
|
124917
|
+
// Timeline navigation: wheel or drag over the strip scrolls the ledger and
|
|
124918
|
+
// positions the view at the corresponding rows.
|
|
124919
|
+
var timelineEl = timeline.parentElement;
|
|
124920
|
+
timelineEl.addEventListener('wheel', function (e) {
|
|
124921
|
+
if (!tablePane) return;
|
|
124922
|
+
e.preventDefault();
|
|
124923
|
+
tablePane.scrollTop += e.deltaY * 3;
|
|
124924
|
+
}, { passive: false });
|
|
124925
|
+
var dragStartY = null, dragStartScroll = 0;
|
|
124926
|
+
timelineEl.addEventListener('mousedown', function (e) {
|
|
124927
|
+
dragStartY = e.clientY;
|
|
124928
|
+
dragStartScroll = tablePane ? tablePane.scrollTop : 0;
|
|
124929
|
+
});
|
|
124930
|
+
window.addEventListener('mousemove', function (e) {
|
|
124931
|
+
if (dragStartY === null || !tablePane) return;
|
|
124932
|
+
tablePane.scrollTop = dragStartScroll + (dragStartY - e.clientY) * 3;
|
|
124933
|
+
});
|
|
124934
|
+
window.addEventListener('mouseup', function () { dragStartY = null; });
|
|
124935
|
+
// Draggable locator: drag or click on the strip to jump to a row.
|
|
124936
|
+
var locDrag = false;
|
|
124937
|
+
function locateAt(clientX) {
|
|
124938
|
+
var trackRect = track.getBoundingClientRect();
|
|
124939
|
+
var p = Math.min(1, Math.max(0, (clientX - trackRect.left) / trackRect.width));
|
|
124940
|
+
// The locator lives in .timeline (outside the cleared track); offset by the track origin.
|
|
124941
|
+
locator.style.left = (trackRect.left - timelineRectLeft() + p * trackRect.width - 1) + 'px';
|
|
124942
|
+
var n = currentFiltered.length;
|
|
124943
|
+
if (n < 2) return;
|
|
124944
|
+
var idx = Math.round(p * (n - 1));
|
|
124945
|
+
if (rowEls[idx] && rowEls[idx].scrollIntoView) rowEls[idx].scrollIntoView({ block: 'center' });
|
|
124946
|
+
}
|
|
124947
|
+
function timelineRectLeft() {
|
|
124948
|
+
return timeline.parentElement.getBoundingClientRect().left;
|
|
124949
|
+
}
|
|
124950
|
+
locator.addEventListener('mousedown', function (e) { e.stopPropagation(); e.preventDefault(); locDrag = true; });
|
|
124951
|
+
window.addEventListener('mousemove', function (e) {
|
|
124952
|
+
if (!locDrag) return;
|
|
124953
|
+
locateAt(e.clientX);
|
|
124954
|
+
});
|
|
124955
|
+
window.addEventListener('mouseup', function () { locDrag = false; });
|
|
124956
|
+
timelineEl.addEventListener('click', function (e) {
|
|
124957
|
+
if (e.target === locator) return;
|
|
124958
|
+
locateAt(e.clientX);
|
|
124959
|
+
});
|
|
124960
|
+
function syncLocatorFromTable() {
|
|
124961
|
+
if (!tablePane) return;
|
|
124962
|
+
var max = tablePane.scrollHeight - tablePane.clientHeight;
|
|
124963
|
+
var p = max > 0 ? tablePane.scrollTop / max : 0;
|
|
124964
|
+
var trackRect = track.getBoundingClientRect();
|
|
124965
|
+
locator.style.left = (trackRect.left - timelineRectLeft() + p * trackRect.width - 1) + 'px';
|
|
124966
|
+
}
|
|
124967
|
+
if (tablePane) tablePane.addEventListener('scroll', syncLocatorFromTable);
|
|
124968
|
+
render();
|
|
124969
|
+
syncLocatorFromTable();
|
|
124970
|
+
`;
|
|
124971
|
+
function escapeHtml(value) {
|
|
124972
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
124973
|
+
}
|
|
124974
|
+
function renderTraceHtml(doc) {
|
|
124975
|
+
const dataJson = JSON.stringify(doc.cells).replaceAll("</", "<\\/");
|
|
124976
|
+
const meta = `${escapeHtml(doc.sessionId)} · ${new Date(doc.createdAt).toLocaleString()}`;
|
|
124977
|
+
return `<!DOCTYPE html>
|
|
124978
|
+
<html lang="zh">
|
|
124979
|
+
<head>
|
|
124980
|
+
<meta charset="utf-8">
|
|
124981
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
124982
|
+
<title>${escapeHtml(doc.title)} — 会话轨迹</title>
|
|
124983
|
+
<style>${CSS}</style>
|
|
124984
|
+
</head>
|
|
124985
|
+
<body>
|
|
124986
|
+
<div id="root">
|
|
124987
|
+
<div class="toolbar">
|
|
124988
|
+
<span class="title">${escapeHtml(doc.title)}</span>
|
|
124989
|
+
<span class="count" id="count"></span>
|
|
124990
|
+
<span class="count">${meta}</span>
|
|
124991
|
+
<button class="btn" id="turns">Turns</button>
|
|
124992
|
+
<button class="btn" id="calls">Calls</button>
|
|
124993
|
+
<button class="btn" id="mode">Seq</button>
|
|
124994
|
+
<button class="btn" id="json">JSON</button>
|
|
124995
|
+
<div class="search"><input id="q" type="search" placeholder="搜索…"></div>
|
|
124996
|
+
</div>
|
|
124997
|
+
<div class="timeline">
|
|
124998
|
+
<span class="lane-label" style="top:2px">Input</span>
|
|
124999
|
+
<span class="lane-label" style="top:16px">Model</span>
|
|
125000
|
+
<span class="lane-label" style="top:30px">Tools</span>
|
|
125001
|
+
<div class="track" id="timeline-track"></div>
|
|
125002
|
+
<div class="locator" id="locator"></div>
|
|
125003
|
+
</div>
|
|
125004
|
+
<div class="split">
|
|
125005
|
+
<div class="tablePane">
|
|
125006
|
+
<table>
|
|
125007
|
+
<colgroup><col class="event-column"><col></colgroup>
|
|
125008
|
+
<tbody id="rows"></tbody>
|
|
125009
|
+
</table>
|
|
125010
|
+
</div>
|
|
125011
|
+
<aside class="detail hidden" id="detail">
|
|
125012
|
+
<div class="dhead"><span class="dname mono" id="dname"></span>
|
|
125013
|
+
<button class="dclose" onclick="hideDetail()">×</button></div>
|
|
125014
|
+
<div class="dbody" id="dbody"></div>
|
|
125015
|
+
</aside>
|
|
125016
|
+
</div>
|
|
125017
|
+
</div>
|
|
125018
|
+
<script id="data" type="application/json">${dataJson}<\/script>
|
|
125019
|
+
<script>${RENDER_JS}<\/script>
|
|
125020
|
+
<div class="tip" id="tip"></div>
|
|
125021
|
+
</body>
|
|
125022
|
+
</html>`;
|
|
125023
|
+
}
|
|
125024
|
+
//#endregion
|
|
125025
|
+
//#region src/tui/commands/trace.ts
|
|
125026
|
+
/**
|
|
125027
|
+
* `/trace` — snapshot the current session's trajectory as a self-contained
|
|
125028
|
+
* interactive HTML document and open it in the browser. The file is written
|
|
125029
|
+
* to the OS temp dir (never the desktop / project), so repeated invocations
|
|
125030
|
+
* do not accumulate artifacts.
|
|
125031
|
+
*/
|
|
125032
|
+
function handleTraceCommand(host) {
|
|
125033
|
+
runTrace(host);
|
|
125034
|
+
}
|
|
125035
|
+
async function runTrace(host) {
|
|
125036
|
+
try {
|
|
125037
|
+
const session = host.session;
|
|
125038
|
+
const sessionDir = session?.summary?.sessionDir;
|
|
125039
|
+
if (!sessionDir) {
|
|
125040
|
+
host.showError("当前会话不可用,无法导出轨迹");
|
|
125041
|
+
return;
|
|
125042
|
+
}
|
|
125043
|
+
const wirePath = join(sessionDir, "agents", "main", "wire.jsonl");
|
|
125044
|
+
if (!existsSync(wirePath)) {
|
|
125045
|
+
host.showError(`未找到轨迹文件: ${wirePath}`);
|
|
125046
|
+
return;
|
|
125047
|
+
}
|
|
125048
|
+
const cells = buildTraceCells({ wirePath });
|
|
125049
|
+
const html = renderTraceHtml({
|
|
125050
|
+
title: session?.summary?.title ?? host.state.appState.sessionTitle ?? "session",
|
|
125051
|
+
sessionId: session?.id ?? "unknown",
|
|
125052
|
+
createdAt: Date.now(),
|
|
125053
|
+
cells
|
|
125054
|
+
});
|
|
125055
|
+
const filePath = join(tmpdir(), "scream-trace.html");
|
|
125056
|
+
writeFileSync(filePath, html, "utf8");
|
|
125057
|
+
const opened = await openInBrowser(filePath);
|
|
125058
|
+
host.showStatus(opened ? "轨迹已打开" : "轨迹已生成,请手动打开");
|
|
125059
|
+
} catch (error) {
|
|
125060
|
+
host.showError(`轨迹导出失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
125061
|
+
}
|
|
125062
|
+
}
|
|
125063
|
+
function openInBrowser(filePath) {
|
|
125064
|
+
const url = `file://${filePath}?v=${Date.now()}`;
|
|
125065
|
+
let command;
|
|
125066
|
+
let args;
|
|
125067
|
+
if (process.platform === "darwin") {
|
|
125068
|
+
command = "open";
|
|
125069
|
+
args = [url];
|
|
125070
|
+
} else if (process.platform === "win32") {
|
|
125071
|
+
command = "cmd";
|
|
125072
|
+
args = [
|
|
125073
|
+
"/c",
|
|
125074
|
+
"start",
|
|
125075
|
+
"",
|
|
125076
|
+
url
|
|
125077
|
+
];
|
|
125078
|
+
} else {
|
|
125079
|
+
command = "xdg-open";
|
|
125080
|
+
args = [url];
|
|
125081
|
+
}
|
|
125082
|
+
return new Promise((resolve) => {
|
|
125083
|
+
const child = spawn(command, args, {
|
|
125084
|
+
stdio: "ignore",
|
|
125085
|
+
detached: true
|
|
125086
|
+
});
|
|
125087
|
+
child.on("error", () => resolve(false));
|
|
125088
|
+
child.on("spawn", () => resolve(true));
|
|
125089
|
+
});
|
|
125090
|
+
}
|
|
125091
|
+
//#endregion
|
|
124043
125092
|
//#region src/tui/components/dialogs/editor-selector.ts
|
|
124044
125093
|
function getEditorOptions() {
|
|
124045
125094
|
return [
|
|
@@ -125086,10 +126135,10 @@ var FooterComponent = class {
|
|
|
125086
126135
|
const totalInput = sessionUsage.inputCacheRead + sessionUsage.inputCacheCreation + sessionUsage.inputOther;
|
|
125087
126136
|
const hitRatePct = totalInput > 0 ? sessionUsage.inputCacheRead / totalInput * 100 : void 0;
|
|
125088
126137
|
const hitColor = hitRatePct !== void 0 && hitRatePct >= 90 ? colors.success : colors.textDim;
|
|
125089
|
-
const segHit = chalk.hex(
|
|
126138
|
+
const segHit = chalk.hex(colors.textDim)(`${t("footer.hit")}:`) + " " + chalk.hex(hitColor)(hitRatePct === void 0 ? "--" : `${hitRatePct.toFixed(2)}%`);
|
|
125090
126139
|
const contextColor = pickContextColor(state.contextUsage, colors);
|
|
125091
126140
|
const contextBarWidth = width >= 68 ? CONTEXT_BAR_WIDTH : width >= 52 ? 6 : 0;
|
|
125092
|
-
rightText = `${ccDot} ${chalk.hex(contextColor)(formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens, contextBarWidth))} ${
|
|
126141
|
+
rightText = `${ccDot} ${segHit} ${chalk.hex(contextColor)(formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens, contextBarWidth))} ${chalk.hex(colors.textDim)(` ${statusLine}`)}`;
|
|
125093
126142
|
}
|
|
125094
126143
|
const rightWidth = visibleWidth(rightText);
|
|
125095
126144
|
const gap = 3;
|
|
@@ -127393,7 +128442,7 @@ async function guidedGoalSetup(host) {
|
|
|
127393
128442
|
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
127394
128443
|
return;
|
|
127395
128444
|
}
|
|
127396
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
128445
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-CYs9xQZi.mjs");
|
|
127397
128446
|
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
127398
128447
|
title: t("goal.setup_title_initial"),
|
|
127399
128448
|
subtitle: t("goal.setup_desc_hint"),
|
|
@@ -127414,7 +128463,7 @@ async function guidedGoalSetup(host) {
|
|
|
127414
128463
|
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
127415
128464
|
}
|
|
127416
128465
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
127417
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
128466
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-CYs9xQZi.mjs");
|
|
127418
128467
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
127419
128468
|
title: t("goal.wizard_title", { objective }),
|
|
127420
128469
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -136758,6 +137807,12 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
136758
137807
|
case "logout":
|
|
136759
137808
|
await handleLogoutCommand(host);
|
|
136760
137809
|
return;
|
|
137810
|
+
case "search":
|
|
137811
|
+
handleSearchCommand(host);
|
|
137812
|
+
return;
|
|
137813
|
+
case "trace":
|
|
137814
|
+
handleTraceCommand(host);
|
|
137815
|
+
return;
|
|
136761
137816
|
case "eval":
|
|
136762
137817
|
runEvalCommand(host);
|
|
136763
137818
|
return;
|
|
@@ -136776,4 +137831,4 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
136776
137831
|
}
|
|
136777
137832
|
}
|
|
136778
137833
|
//#endregion
|
|
136779
|
-
export { toTerminalHyperlink as $,
|
|
137834
|
+
export { toTerminalHyperlink as $, parseStreamingArgs as $t, highlightLines as A, CLI_USER_AGENT_PRODUCT as An, ENABLE_TERMINAL_THEME_REPORTING as At, AssistantMessageComponent as B, log as Bn, isBusy as Bt, UserMessageComponent as C, detectShellEnvironment as Cn, contrastTextHex as Ct, ToolCallComponent as D, detectInstallSource as Dn, DISABLE_TERMINAL_FOCUS_REPORTING as Dt, toggleEmptySessionHint as E, getLogDir as En, parseOsc11BackgroundTheme as Et, getSharedSpeedTracker as F, ScreamHarness as Fn, QUERY_TERMINAL_THEME as Ft, resetBreathingClock as G, SCREAM_ERROR_INFO as Gn, handleConnectCommand as Gt, WelcomeComponent as H, isScreamError as Hn, FooterComponent as Ht, SkillActivationComponent as I, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as In, TERMINAL_FOCUS_IN as It, handleExportDebugZipCommand as J, STATUS_BULLET as Jt, clearGoalState as K, handleLogoutCommand as Kt, ReadGroupComponent as L, resolveScreamHome as Ln, TERMINAL_FOCUS_OUT as Lt, CachedContainer as M, DEFAULT_CATALOG_URL as Mn, OSC11_RESPONSE as Mt, ThinkingComponent as N, fetchCatalog as Nn, OSC11_RESPONSE_PREFIX as Nt, renderDiffLines as O, CLI_COMMAND_NAME as On, DISABLE_TERMINAL_THEME_REPORTING as Ot, estimateTokens as P, saveCatalogCache as Pn, OSC11_RESPONSE_PREFIX_NO_ESC as Pt, handleTitleCommand as Q, isTodoItemShape as Qt, parseReadGroupOutput as R, MemoryMemoStore as Rn, TERMINAL_THEME_DARK as Rt, handleRevokeCommand as S, saveTuiConfig as Sn, createThemeStyles as St, isTurnElapsedEnabled as T, getInputHistoryFile as Tn, detectTerminalTheme as Tt, BREATHE_CYCLE_MS as U, isOrphanedToolCallError as Un, handleTraceCommand as Ut, AgentGroupComponent as V, resolveGlobalLogPath as Vn, isStreaming as Vt, getBreathingFrame as W, ErrorCodes as Wn, handleSearchCommand as Wt, handleForkCommand as X, argsRecord as Xt, handleExportMdCommand as Y, appendStreamingArgsPreview as Yt, handleInitCommand as Z, formatErrorMessage as Zt, readUpdateCache as _, PIXEL_PULSE_FRAMES as _n, showStatusReport as _t, handleSkillCommand as a, MAIN_AGENT_ID$1 as an, handleFusionPlanCommand as at, handleCcCommand as b, TuiLikePreferencesSchema as bn, createEditorTheme as bt, isPlanExpandable as c, getCtrlCHint as cn, handleThemeCommand as ct, handleMemoryCommand as d, getNoActiveSessionMessage as dn, showModelPicker as dt, serializeToolResultOutput as en, changeThinkingLevel as et, handleChannelCommand as f, buildSkillSlashCommands as fn, showPermissionPicker as ft, refreshUpdateCache as g, setExperimentalFlags as gn, clearInfoPanelState as gt, selectUpdateTarget as h, isExperimentalFlagEnabled as hn, supportsBalance as ht, buildRoleAdditionalText as i, EXIT_CONFIRM_WINDOW_MS as in, handleEditorCommand as it, langFromPath as j, PRODUCT_NAME as jn, OSC11_QUERY as jt, renderDiffLinesClustered as k, CLI_UI_MODE as kn, ENABLE_TERMINAL_FOCUS_REPORTING as kt, MoonLoader as l, getCtrlDHint as ln, handleWolfpackCommand as lt, handleUpdateCommand as m, sortSlashCommands as mn, refreshProviderBalance as mt, clearEvalPanelState as n, truncateErrorMessage as nn, handleAutoCommand as nt, disposeChildren as o, SESSION_TIPS as on, handleModelCommand as ot, handleMcpCommand as p, BUILTIN_SLASH_COMMANDS as pn, showSettingsSelector as pt, refineGoal as q, printableChar as qt, openUrl as r, EMPTY_SESSION_HINT_URL as rn, handleCompactCommand as rt, hasDispose as s, TIP_ROTATION_INTERVAL_MS as sn, handlePlanCommand as st, dispatchInput as t, stringValue as tn, getModelCycleLevel as tt, formatMemoryMemoForInjection as u, getLlmNotSetMessage as un, handleYoloCommand as ut, appendJsonlLine as v, PULSE_WAVE_FRAMES as vn, showUsage as vt, isEmptySessionHintDismissed as w, getDataDir as wn, getColorPalette as wt, getDaemonInstructions as x, loadTuiConfig as xn, createMarkdownTheme as xt, readJsonlFile as y, TuiConfigParseError as yn, resolveThemeSync as yt, BackgroundAgentStatusComponent as z, flushDiagnosticLogs as zn, TERMINAL_THEME_LIGHT as zt };
|