opencode-context-tree 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +22 -24
- package/dist/tui.js +1294 -489
- package/docs/USAGE.md +40 -21
- package/package.json +1 -1
- package/src/core/actions.ts +1 -0
- package/src/core/consumers.ts +55 -13
- package/src/core/decision.ts +62 -0
- package/src/core/lanes.ts +166 -5
- package/src/core/navigation.ts +39 -10
- package/src/core/tokens.ts +22 -4
- package/src/core/tree.ts +98 -5
- package/src/tui/actions.ts +131 -11
- package/src/tui/index.tsx +14 -6
- package/src/tui/route.tsx +544 -180
package/dist/tui.js
CHANGED
|
@@ -66,7 +66,17 @@ function contextSizeOf(messages) {
|
|
|
66
66
|
}
|
|
67
67
|
return { tokens: lastAssistantInput + newer, estimated: newer > 0 };
|
|
68
68
|
}
|
|
69
|
-
function bandFor(tokens) {
|
|
69
|
+
function bandFor(tokens, limit) {
|
|
70
|
+
if (limit && limit > 0) {
|
|
71
|
+
const r = tokens / limit;
|
|
72
|
+
if (r < 0.25)
|
|
73
|
+
return "low";
|
|
74
|
+
if (r < 0.6)
|
|
75
|
+
return "healthy";
|
|
76
|
+
if (r < 0.85)
|
|
77
|
+
return "filling";
|
|
78
|
+
return "red";
|
|
79
|
+
}
|
|
70
80
|
if (tokens < 8000)
|
|
71
81
|
return "low";
|
|
72
82
|
if (tokens < 32000)
|
|
@@ -75,6 +85,12 @@ function bandFor(tokens) {
|
|
|
75
85
|
return "filling";
|
|
76
86
|
return "red";
|
|
77
87
|
}
|
|
88
|
+
function contextBar(tokens, limit, cells = 5) {
|
|
89
|
+
if (!(limit > 0))
|
|
90
|
+
return "";
|
|
91
|
+
const filled = tokens <= 0 ? 0 : Math.min(cells, Math.max(1, Math.round(tokens / limit * cells)));
|
|
92
|
+
return "\u2593".repeat(filled) + "\u2591".repeat(cells - filled);
|
|
93
|
+
}
|
|
78
94
|
function formatK(tokens) {
|
|
79
95
|
if (tokens < 1000)
|
|
80
96
|
return String(tokens);
|
|
@@ -82,7 +98,8 @@ function formatK(tokens) {
|
|
|
82
98
|
return `${k.endsWith(".0") ? k.slice(0, -2) : k}k`;
|
|
83
99
|
}
|
|
84
100
|
function formatContext(size, limit) {
|
|
85
|
-
|
|
101
|
+
const bar = limit ? `${contextBar(size.tokens, limit)} ` : "";
|
|
102
|
+
return `ctx ${bar}${size.estimated ? "~" : ""}${formatK(size.tokens)}${limit ? `/${formatK(limit)}` : ""} \xB7 ${bandFor(size.tokens, limit)}`;
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
// src/shared/store.ts
|
|
@@ -19113,6 +19130,8 @@ function debug(event, data) {
|
|
|
19113
19130
|
|
|
19114
19131
|
// src/tui/actions.ts
|
|
19115
19132
|
import { createSignal } from "solid-js";
|
|
19133
|
+
import fs4 from "fs";
|
|
19134
|
+
import path3 from "path";
|
|
19116
19135
|
|
|
19117
19136
|
// src/core/decision.ts
|
|
19118
19137
|
var DECISION_SYSTEM = "You write concise engineering decision records. You are given the transcript of a side branch of a coding session. Do NOT continue the conversation. Output ONLY the record in the exact markdown template requested, nothing else.";
|
|
@@ -19181,6 +19200,55 @@ function decisionMessageText(record2, branchName) {
|
|
|
19181
19200
|
return body.startsWith("## Decision:") ? `\u25C6 ${body}` : `\u25C6 ## Decision: ${branchName}
|
|
19182
19201
|
${body}`;
|
|
19183
19202
|
}
|
|
19203
|
+
var BULLET = /^(\s*)[-*\u2022]\s+/;
|
|
19204
|
+
var HEADING = /^(\s*(?:\u25C6\s*)?)#{1,6}\s*/;
|
|
19205
|
+
function stripMarkdown(text) {
|
|
19206
|
+
return text.replace(/[*`]/g, "");
|
|
19207
|
+
}
|
|
19208
|
+
function wrapLine(body, width, marker) {
|
|
19209
|
+
const room = Math.max(1, width - marker.length);
|
|
19210
|
+
const indent = " ".repeat(marker.length);
|
|
19211
|
+
const lines = [];
|
|
19212
|
+
let current = "";
|
|
19213
|
+
for (const raw of body.split(/\s+/).filter(Boolean)) {
|
|
19214
|
+
const word = raw.length > room ? `${raw.slice(0, Math.max(1, room - 1))}\u2026` : raw;
|
|
19215
|
+
if (current === "")
|
|
19216
|
+
current = word;
|
|
19217
|
+
else if (current.length + 1 + word.length <= room)
|
|
19218
|
+
current += ` ${word}`;
|
|
19219
|
+
else {
|
|
19220
|
+
lines.push(current);
|
|
19221
|
+
current = word;
|
|
19222
|
+
}
|
|
19223
|
+
}
|
|
19224
|
+
lines.push(current);
|
|
19225
|
+
return lines.map((line, i) => `${i === 0 ? marker : indent}${line}`.trimEnd());
|
|
19226
|
+
}
|
|
19227
|
+
function renderDecision(text, width) {
|
|
19228
|
+
const out = [];
|
|
19229
|
+
for (const raw of text.replace(/\r/g, "").trim().split(`
|
|
19230
|
+
`)) {
|
|
19231
|
+
const line = raw.trimEnd();
|
|
19232
|
+
if (line.trim() === "") {
|
|
19233
|
+
if (out.length > 0 && out[out.length - 1] !== "")
|
|
19234
|
+
out.push("");
|
|
19235
|
+
continue;
|
|
19236
|
+
}
|
|
19237
|
+
const bullet = BULLET.exec(line);
|
|
19238
|
+
const marker = bullet ? `${bullet[1]}- ` : "";
|
|
19239
|
+
const body = stripMarkdown(bullet ? line.slice(bullet[0].length) : line.replace(HEADING, "$1"));
|
|
19240
|
+
out.push(...wrapLine(body, width, marker));
|
|
19241
|
+
}
|
|
19242
|
+
return out;
|
|
19243
|
+
}
|
|
19244
|
+
function decisionSummary(text) {
|
|
19245
|
+
const lines = text.replace(/\r/g, "").split(`
|
|
19246
|
+
`).map((l) => stripMarkdown(l.replace(/^\s*\u25C6\s*/, "").replace(HEADING, "$1").replace(BULLET, "")).trim());
|
|
19247
|
+
const heading = lines.find((l) => /^Decision:\s*\S/i.test(l));
|
|
19248
|
+
const title = heading ? heading.replace(/^Decision:\s*/i, "").trim() : lines.find((l) => l !== "") ?? "";
|
|
19249
|
+
const outcome = lines.find((l) => /^Outcome:\s*\S/i.test(l))?.replace(/^Outcome:\s*/i, "").trim();
|
|
19250
|
+
return outcome ? { title, outcome } : { title };
|
|
19251
|
+
}
|
|
19184
19252
|
function openSiblings(state, sessionID) {
|
|
19185
19253
|
const me = state.sessions[sessionID];
|
|
19186
19254
|
if (!me)
|
|
@@ -19562,19 +19630,67 @@ async function draftWithHelper(ctx, input2) {
|
|
|
19562
19630
|
}
|
|
19563
19631
|
}
|
|
19564
19632
|
var MERGE_TRUST = "Your transcript is never rewritten; the record is appended to the trunk as a normal message.";
|
|
19633
|
+
var UNDO_KEY = "u";
|
|
19565
19634
|
var MERGE_GATE_NOTICE = `Edit the \u25C6 decision record, then save to confirm (empty file or a non-zero exit aborts the merge).
|
|
19566
19635
|
${MERGE_TRUST}`;
|
|
19567
|
-
|
|
19568
|
-
|
|
19636
|
+
var DISCARD_NOTICE = `${MERGE_TRUST}
|
|
19637
|
+
The branch is only marked rejected \u2014 ${UNDO_KEY} (alias x) undoes it.`;
|
|
19638
|
+
var TRUNK_LABEL = "trunk";
|
|
19639
|
+
function mergeTargetOf(label, messages) {
|
|
19640
|
+
const minimal = messages.map((m) => ({ info: m.role === "assistant" ? { role: "assistant", tokens: m.tokens } : { role: "user" }, parts: m.parts }));
|
|
19641
|
+
return { label, turns: messages.filter((m) => m.role === "user").length, tokens: contextSizeOf(minimal).tokens };
|
|
19642
|
+
}
|
|
19643
|
+
function ownTurnCount(messages, anchor2) {
|
|
19644
|
+
const anchorIndex = anchor2.messageID ? anchor2.parentMessageIDs.indexOf(anchor2.messageID) : -1;
|
|
19645
|
+
return messages.slice(anchorIndex + 1).filter((m) => m.role === "user").length;
|
|
19646
|
+
}
|
|
19647
|
+
function mergeDialogTitle(branchName, target2) {
|
|
19648
|
+
if (!target2 || typeof target2 === "string")
|
|
19649
|
+
return `Merge \u2387 ${branchName} \u2192 the trunk`;
|
|
19650
|
+
const where = target2.label === TRUNK_LABEL ? TRUNK_LABEL : `\u2387 ${clip(target2.label, 24)}`;
|
|
19651
|
+
return `Merge \u2387 ${branchName} \u2192 ${where} (${plural(target2.turns, "turn")}, ~${formatK(target2.tokens)})`;
|
|
19569
19652
|
}
|
|
19570
19653
|
function mergeDialogOptions(input2) {
|
|
19654
|
+
const folds = input2.turns === undefined ? "the branch" : plural(input2.turns, "turn");
|
|
19571
19655
|
return [
|
|
19572
|
-
{ title: "Squash", value: "squash", description:
|
|
19573
|
-
{ title: "Squash without LLM", value: "squash-no-llm", description: "you write
|
|
19574
|
-
{ title: "Discard", value: "discard", description: "rejected
|
|
19656
|
+
{ title: "Squash", value: "squash", description: `1 model call \xB7 folds ${folds} into one \u25C6 record` },
|
|
19657
|
+
{ title: "Squash without LLM", value: "squash-no-llm", description: "you write it \xB7 no model call" },
|
|
19658
|
+
{ title: "Discard", value: "discard", description: "rejected \xB7 nothing lands in the trunk" },
|
|
19575
19659
|
...input2.siblings > 0 ? [{ title: "Tournament", value: "tournament", description: "compare sibling branches and keep one" }] : []
|
|
19576
19660
|
];
|
|
19577
19661
|
}
|
|
19662
|
+
function confirmDialog(ctx, title, message) {
|
|
19663
|
+
return new Promise((resolve) => {
|
|
19664
|
+
ctx.api.ui.dialog.replace(() => ctx.api.ui.DialogConfirm({
|
|
19665
|
+
title,
|
|
19666
|
+
message,
|
|
19667
|
+
onConfirm: () => {
|
|
19668
|
+
resolve(true);
|
|
19669
|
+
ctx.api.ui.dialog.clear();
|
|
19670
|
+
},
|
|
19671
|
+
onCancel: () => {
|
|
19672
|
+
resolve(false);
|
|
19673
|
+
ctx.api.ui.dialog.clear();
|
|
19674
|
+
}
|
|
19675
|
+
}), () => resolve(false));
|
|
19676
|
+
});
|
|
19677
|
+
}
|
|
19678
|
+
function promptDialog(ctx, title, placeholder) {
|
|
19679
|
+
return new Promise((resolve) => {
|
|
19680
|
+
ctx.api.ui.dialog.replace(() => ctx.api.ui.DialogPrompt({
|
|
19681
|
+
title,
|
|
19682
|
+
placeholder,
|
|
19683
|
+
onConfirm: (value) => {
|
|
19684
|
+
resolve(value);
|
|
19685
|
+
ctx.api.ui.dialog.clear();
|
|
19686
|
+
},
|
|
19687
|
+
onCancel: () => {
|
|
19688
|
+
resolve(undefined);
|
|
19689
|
+
ctx.api.ui.dialog.clear();
|
|
19690
|
+
}
|
|
19691
|
+
}), () => resolve(undefined));
|
|
19692
|
+
});
|
|
19693
|
+
}
|
|
19578
19694
|
async function mergeBranch(ctx, input2) {
|
|
19579
19695
|
const treeId = ctx.store.ensureTree(input2.sessionID, "tui");
|
|
19580
19696
|
const state = ctx.store.stateFor(treeId);
|
|
@@ -19585,16 +19701,33 @@ async function mergeBranch(ctx, input2) {
|
|
|
19585
19701
|
const name = branch.name ?? "branch";
|
|
19586
19702
|
debug("merge.start", { mode: input2.mode, sessionID: input2.sessionID, parentID });
|
|
19587
19703
|
await abortIfBusy(ctx, input2.sessionID);
|
|
19704
|
+
const parentMsgs = await ctx.api.client.session.messages({ sessionID: parentID, directory: ctx.directory }).catch(() => {
|
|
19705
|
+
return;
|
|
19706
|
+
});
|
|
19707
|
+
const parentMessageIDs = (parentMsgs?.data ?? []).map((m) => String(m.info.id));
|
|
19708
|
+
const own2 = await fetchOwnTranscript(ctx, input2.sessionID);
|
|
19709
|
+
const turns = ownTurnCount(own2.messages, { messageID: branch.anchorMessageID, parentMessageIDs });
|
|
19588
19710
|
if (input2.mode === "discard") {
|
|
19589
|
-
|
|
19711
|
+
const ok = await confirmDialog(ctx, `Discard \u2387 ${name} (${plural(turns, "turn")})?`, DISCARD_NOTICE);
|
|
19712
|
+
if (!ok) {
|
|
19713
|
+
ctx.api.ui.toast({ variant: "warning", message: `\u2387 ${name} kept \u2014 nothing discarded` });
|
|
19714
|
+
return;
|
|
19715
|
+
}
|
|
19716
|
+
let note = input2.note;
|
|
19717
|
+
if (note === undefined) {
|
|
19718
|
+
const answer = await promptDialog(ctx, "Why? (optional note on the close marker)", "dead end");
|
|
19719
|
+
if (answer === undefined) {
|
|
19720
|
+
ctx.api.ui.toast({ variant: "warning", message: `\u2387 ${name} kept \u2014 nothing discarded` });
|
|
19721
|
+
return;
|
|
19722
|
+
}
|
|
19723
|
+
note = answer.trim() || undefined;
|
|
19724
|
+
}
|
|
19725
|
+
record2(ctx, treeId, "branch.closed", { sessionID: input2.sessionID, status: "rejected", note });
|
|
19590
19726
|
await mirrorMetadata(ctx, input2.sessionID, { status: "rejected" });
|
|
19591
19727
|
navigateToSession(ctx, parentID);
|
|
19592
19728
|
ctx.api.ui.toast({ variant: "success", message: `\u2387 ${name} discarded \u2014 back on the trunk` });
|
|
19593
19729
|
return parentID;
|
|
19594
19730
|
}
|
|
19595
|
-
const parentMsgs = await ctx.api.client.session.messages({ sessionID: parentID, directory: ctx.directory });
|
|
19596
|
-
const parentMessageIDs = (parentMsgs.data ?? []).map((m) => String(m.info.id));
|
|
19597
|
-
const own2 = await fetchOwnTranscript(ctx, input2.sessionID);
|
|
19598
19731
|
const transcript = branchTranscriptText(own2, { messageID: branch.anchorMessageID, parentMessageIDs });
|
|
19599
19732
|
const model = branch.branchModel ?? branch.trunkModel;
|
|
19600
19733
|
const modelRef = model ? { providerID: model.split("/")[0], modelID: model.split("/").slice(1).join("/") } : undefined;
|
|
@@ -19654,15 +19787,28 @@ async function fetchOwnTranscript(ctx, sessionID) {
|
|
|
19654
19787
|
return { sessionID, title: sessionID, status: "available", messages };
|
|
19655
19788
|
}
|
|
19656
19789
|
var BRANCH_DIALOG = { title: "Branch here \u2192 new OpenCode session", placeholder: "name, e.g. try-redis", modelTitle: "Model for this branch (Enter keeps the current one)" };
|
|
19790
|
+
var COPY_HINT = ".opencode/context-tree/last-copy.txt";
|
|
19791
|
+
function copyText(api2, text, directory) {
|
|
19792
|
+
const renderer = api2.renderer;
|
|
19793
|
+
if (text && renderer?.copyToClipboardOSC52?.(text))
|
|
19794
|
+
return { target: "clipboard", hint: "clipboard" };
|
|
19795
|
+
const file2 = path3.join(directory, COPY_HINT);
|
|
19796
|
+
fs4.mkdirSync(path3.dirname(file2), { recursive: true });
|
|
19797
|
+
fs4.writeFileSync(file2, text);
|
|
19798
|
+
return { target: "file", hint: COPY_HINT };
|
|
19799
|
+
}
|
|
19657
19800
|
function clip(text, max) {
|
|
19658
19801
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
19659
19802
|
}
|
|
19803
|
+
function plural(n, noun) {
|
|
19804
|
+
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
19805
|
+
}
|
|
19660
19806
|
|
|
19661
19807
|
// src/tui/route.tsx
|
|
19662
|
-
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
19663
|
-
import { effect as _$effect } from "@opentui/solid";
|
|
19664
|
-
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
19665
19808
|
import { memo as _$memo } from "@opentui/solid";
|
|
19809
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
19810
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
19811
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
19666
19812
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
19667
19813
|
import { insert as _$insert } from "@opentui/solid";
|
|
19668
19814
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
@@ -19671,6 +19817,8 @@ import { For, Show, createEffect, createMemo, createSignal as createSignal2, on,
|
|
|
19671
19817
|
|
|
19672
19818
|
// src/core/actions.ts
|
|
19673
19819
|
function planJump(row, ctx) {
|
|
19820
|
+
if (row.kind === "separator")
|
|
19821
|
+
return { kind: "noop", reason: "nothing to go to" };
|
|
19674
19822
|
if (row.kind === "branch")
|
|
19675
19823
|
return row.sessionID === ctx.currentSessionID ? { kind: "noop", reason: "you are here" } : { kind: "switch", sessionID: row.sessionID };
|
|
19676
19824
|
const tr = ctx.transcripts[row.sessionID];
|
|
@@ -19698,15 +19846,33 @@ function planJump(row, ctx) {
|
|
|
19698
19846
|
}
|
|
19699
19847
|
|
|
19700
19848
|
// src/core/navigation.ts
|
|
19849
|
+
function scan(rows, index, dir) {
|
|
19850
|
+
for (let i = index;i >= 0 && i < rows.length; i += dir)
|
|
19851
|
+
if (rows[i].kind !== "separator")
|
|
19852
|
+
return i;
|
|
19853
|
+
return -1;
|
|
19854
|
+
}
|
|
19701
19855
|
function moveSelection(rows, index, delta) {
|
|
19702
19856
|
if (rows.length === 0)
|
|
19703
19857
|
return -1;
|
|
19704
|
-
const
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19858
|
+
const dir = delta < 0 ? -1 : 1;
|
|
19859
|
+
let i = Math.min(Math.max(index, 0), rows.length - 1);
|
|
19860
|
+
for (let step = Math.abs(delta);step > 0; step--) {
|
|
19861
|
+
const next = scan(rows, i + dir, dir);
|
|
19862
|
+
if (next === -1)
|
|
19863
|
+
break;
|
|
19864
|
+
i = next;
|
|
19865
|
+
}
|
|
19866
|
+
if (rows[i].kind !== "separator")
|
|
19867
|
+
return i;
|
|
19868
|
+
const ahead = scan(rows, i, dir);
|
|
19869
|
+
return ahead === -1 ? scan(rows, i, dir === 1 ? -1 : 1) : ahead;
|
|
19870
|
+
}
|
|
19871
|
+
function firstIndex(rows) {
|
|
19872
|
+
return scan(rows, 0, 1);
|
|
19873
|
+
}
|
|
19874
|
+
function lastIndex(rows) {
|
|
19875
|
+
return scan(rows, rows.length - 1, -1);
|
|
19710
19876
|
}
|
|
19711
19877
|
function nextBranchIndex(rows, index, dir) {
|
|
19712
19878
|
if (rows.length === 0)
|
|
@@ -19729,39 +19895,40 @@ function toggleExpanded(expanded, sessionID) {
|
|
|
19729
19895
|
next.add(sessionID);
|
|
19730
19896
|
return next;
|
|
19731
19897
|
}
|
|
19732
|
-
var FILTER_ORDER = ["default", "no-tools", "user-only", "labeled", "all"];
|
|
19733
|
-
function cycleFilter(filter) {
|
|
19734
|
-
const idx = FILTER_ORDER.indexOf(filter);
|
|
19735
|
-
return FILTER_ORDER[(idx + 1) % FILTER_ORDER.length];
|
|
19736
|
-
}
|
|
19737
19898
|
function resolveSelection(view, preferredId, currentRowId, previousIndex) {
|
|
19738
19899
|
if (view.rows.length === 0)
|
|
19739
19900
|
return -1;
|
|
19901
|
+
const land = (i) => {
|
|
19902
|
+
const ahead = scan(view.rows, i, 1);
|
|
19903
|
+
return ahead === -1 ? scan(view.rows, i, -1) : ahead;
|
|
19904
|
+
};
|
|
19740
19905
|
if (preferredId !== undefined) {
|
|
19741
19906
|
const exact = view.indexById[preferredId];
|
|
19742
19907
|
if (exact !== undefined)
|
|
19743
|
-
return exact;
|
|
19908
|
+
return land(exact);
|
|
19744
19909
|
const owner = preferredId.split(":").slice(0, 2).join(":");
|
|
19745
19910
|
for (let i = 0;i < view.rows.length; i++) {
|
|
19746
19911
|
const row = view.rows[i];
|
|
19912
|
+
if (row.kind === "separator")
|
|
19913
|
+
continue;
|
|
19747
19914
|
const rowOwner = row.kind === "branch" ? row.id : `${row.sessionID}:${row.messageID}`;
|
|
19748
19915
|
if (rowOwner === owner)
|
|
19749
19916
|
return i;
|
|
19750
19917
|
}
|
|
19751
19918
|
}
|
|
19752
19919
|
if (previousIndex !== undefined && previousIndex >= 0)
|
|
19753
|
-
return Math.min(previousIndex, view.rows.length - 1);
|
|
19920
|
+
return land(Math.min(previousIndex, view.rows.length - 1));
|
|
19754
19921
|
if (currentRowId !== undefined) {
|
|
19755
19922
|
const idx = view.indexById[currentRowId];
|
|
19756
19923
|
if (idx !== undefined)
|
|
19757
|
-
return idx;
|
|
19924
|
+
return land(idx);
|
|
19758
19925
|
}
|
|
19759
19926
|
if (view.currentRowId !== undefined) {
|
|
19760
19927
|
const idx = view.indexById[view.currentRowId];
|
|
19761
19928
|
if (idx !== undefined)
|
|
19762
|
-
return idx;
|
|
19929
|
+
return land(idx);
|
|
19763
19930
|
}
|
|
19764
|
-
return 0;
|
|
19931
|
+
return land(0);
|
|
19765
19932
|
}
|
|
19766
19933
|
|
|
19767
19934
|
// src/core/transcript.ts
|
|
@@ -19833,6 +20000,7 @@ function messagePreview(message) {
|
|
|
19833
20000
|
}
|
|
19834
20001
|
|
|
19835
20002
|
// src/core/tree.ts
|
|
20003
|
+
var OFF_PATH_TEXT = "\u2500\u2500 not in this branch's context \u2500\u2500";
|
|
19836
20004
|
var WARN_TOKENS = 1e4;
|
|
19837
20005
|
function userText(message) {
|
|
19838
20006
|
return message.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
@@ -19959,7 +20127,7 @@ function branchAllowed(filter) {
|
|
|
19959
20127
|
function isCropped(ctx, messageID, partID) {
|
|
19960
20128
|
return ctx.crops.some((c) => c.messageID === messageID && (c.partID === undefined || c.partID === partID));
|
|
19961
20129
|
}
|
|
19962
|
-
function emitAssistantRows(ctx, sessionID, message, depth, gutter, out) {
|
|
20130
|
+
function emitAssistantRows(ctx, sessionID, message, depth, gutter, inContext, out) {
|
|
19963
20131
|
if (message.summary) {
|
|
19964
20132
|
if (!stepAllowed(ctx.filter, "text"))
|
|
19965
20133
|
return;
|
|
@@ -19985,19 +20153,29 @@ function emitAssistantRows(ctx, sessionID, message, depth, gutter, out) {
|
|
|
19985
20153
|
durationMs: durationOfParts(message.parts),
|
|
19986
20154
|
isError: false,
|
|
19987
20155
|
isCropped: false,
|
|
19988
|
-
warn: tokens >= WARN_TOKENS
|
|
20156
|
+
warn: tokens >= WARN_TOKENS,
|
|
20157
|
+
inContext
|
|
19989
20158
|
});
|
|
19990
20159
|
return;
|
|
19991
20160
|
}
|
|
20161
|
+
const collapseThinking = ctx.filter !== "all";
|
|
20162
|
+
const rows = [];
|
|
20163
|
+
let thinkingMs;
|
|
19992
20164
|
let first = true;
|
|
19993
20165
|
for (const part of message.parts) {
|
|
19994
20166
|
const kind = stepKind(part);
|
|
20167
|
+
if (collapseThinking && kind === "reasoning") {
|
|
20168
|
+
const ms = durationOfPart(part);
|
|
20169
|
+
if (ms !== undefined)
|
|
20170
|
+
thinkingMs = (thinkingMs ?? 0) + ms;
|
|
20171
|
+
continue;
|
|
20172
|
+
}
|
|
19995
20173
|
const label = first ? ctx.labels[message.id] : undefined;
|
|
19996
20174
|
if (!stepAllowed(ctx.filter, kind, Boolean(label)))
|
|
19997
20175
|
continue;
|
|
19998
20176
|
const { tokens, estimated } = stepTokensFor(part, message);
|
|
19999
20177
|
first = false;
|
|
20000
|
-
|
|
20178
|
+
rows.push({
|
|
20001
20179
|
kind: "step",
|
|
20002
20180
|
id: `${sessionID}:${message.id}:${part.id}`,
|
|
20003
20181
|
sessionID,
|
|
@@ -20013,9 +20191,40 @@ function emitAssistantRows(ctx, sessionID, message, depth, gutter, out) {
|
|
|
20013
20191
|
isError: part.state?.status === "error",
|
|
20014
20192
|
isCropped: isCropped(ctx, message.id, part.id),
|
|
20015
20193
|
warn: tokens >= WARN_TOKENS,
|
|
20016
|
-
label
|
|
20194
|
+
label,
|
|
20195
|
+
inContext
|
|
20017
20196
|
});
|
|
20018
20197
|
}
|
|
20198
|
+
if (rows.length > 0) {
|
|
20199
|
+
if (thinkingMs !== undefined)
|
|
20200
|
+
rows[0].thinkingMs = thinkingMs;
|
|
20201
|
+
} else if (collapseThinking) {
|
|
20202
|
+
const thinking = message.parts.filter((p) => stepKind(p) === "reasoning");
|
|
20203
|
+
const label = ctx.labels[message.id];
|
|
20204
|
+
if (thinking.length > 0 && stepAllowed(ctx.filter, "reasoning", Boolean(label))) {
|
|
20205
|
+
const tokens = thinking.reduce((sum, p) => sum + stepTokensFor(p, message).tokens, 0);
|
|
20206
|
+
rows.push({
|
|
20207
|
+
kind: "step",
|
|
20208
|
+
id: `${sessionID}:${message.id}:${thinking[0].id}`,
|
|
20209
|
+
sessionID,
|
|
20210
|
+
messageID: message.id,
|
|
20211
|
+
partID: thinking[0].id,
|
|
20212
|
+
depth,
|
|
20213
|
+
gutter,
|
|
20214
|
+
glyph: "\u25CB",
|
|
20215
|
+
preview: partPreview(thinking[0]),
|
|
20216
|
+
tokens,
|
|
20217
|
+
estimated: thinking.some((p) => stepTokensFor(p, message).estimated),
|
|
20218
|
+
durationMs: thinkingMs,
|
|
20219
|
+
isError: false,
|
|
20220
|
+
isCropped: isCropped(ctx, message.id, thinking[0].id),
|
|
20221
|
+
warn: tokens >= WARN_TOKENS,
|
|
20222
|
+
label,
|
|
20223
|
+
inContext
|
|
20224
|
+
});
|
|
20225
|
+
}
|
|
20226
|
+
}
|
|
20227
|
+
out.push(...rows);
|
|
20019
20228
|
}
|
|
20020
20229
|
function shownExpanded(ctx, sessionID) {
|
|
20021
20230
|
const flagged = ctx.expanded.has(sessionID);
|
|
@@ -20076,11 +20285,21 @@ function emitChildBranches(ctx, sessionID, anchorMessageID, depth, gutter, out)
|
|
|
20076
20285
|
pushBranch(ctx, branch, depth, gutter, i === visible.length - 1, showHeaders, out);
|
|
20077
20286
|
});
|
|
20078
20287
|
}
|
|
20288
|
+
function forkAnchorOf(ctx, sessionID) {
|
|
20289
|
+
const childID = ctx.onPathChild.get(sessionID);
|
|
20290
|
+
return childID ? ctx.state.sessions[childID]?.anchorMessageID : undefined;
|
|
20291
|
+
}
|
|
20079
20292
|
function walkSession(ctx, sessionID, messages, depth, gutter, turnStart, out) {
|
|
20080
20293
|
const lastUserIndex = findLastUserIndex(messages, ctx.filter);
|
|
20081
20294
|
const counter = { turn: turnStart };
|
|
20082
20295
|
let inPluginCommand = false;
|
|
20296
|
+
const forkAnchor = forkAnchorOf(ctx, sessionID);
|
|
20297
|
+
let inContext = ctx.onPath.has(sessionID);
|
|
20298
|
+
let separatorDue = inContext && forkAnchor === "";
|
|
20299
|
+
if (separatorDue)
|
|
20300
|
+
inContext = false;
|
|
20083
20301
|
messages.forEach((message, i) => {
|
|
20302
|
+
const before = out.length;
|
|
20084
20303
|
if (message.role === "user") {
|
|
20085
20304
|
inPluginCommand = hiddenPluginTurn(ctx.filter, message);
|
|
20086
20305
|
if (!inPluginCommand) {
|
|
@@ -20106,14 +20325,23 @@ function walkSession(ctx, sessionID, messages, depth, gutter, turnStart, out) {
|
|
|
20106
20325
|
isCurrent: sessionID === ctx.currentSessionID,
|
|
20107
20326
|
isTip: i === lastUserIndex,
|
|
20108
20327
|
isDecision,
|
|
20109
|
-
isSummary
|
|
20328
|
+
isSummary,
|
|
20329
|
+
inContext
|
|
20110
20330
|
});
|
|
20111
20331
|
}
|
|
20112
20332
|
}
|
|
20113
20333
|
} else if (!inPluginCommand) {
|
|
20114
|
-
emitAssistantRows(ctx, sessionID, message, depth, gutter, out);
|
|
20334
|
+
emitAssistantRows(ctx, sessionID, message, depth, gutter, inContext, out);
|
|
20115
20335
|
}
|
|
20116
20336
|
emitChildBranches(ctx, sessionID, message.id, depth, gutter, out);
|
|
20337
|
+
if (separatorDue && out.length > before) {
|
|
20338
|
+
out.splice(before, 0, { kind: "separator", id: `separator:${sessionID}`, depth, gutter, text: OFF_PATH_TEXT });
|
|
20339
|
+
separatorDue = false;
|
|
20340
|
+
}
|
|
20341
|
+
if (inContext && message.id === forkAnchor) {
|
|
20342
|
+
inContext = false;
|
|
20343
|
+
separatorDue = true;
|
|
20344
|
+
}
|
|
20117
20345
|
});
|
|
20118
20346
|
}
|
|
20119
20347
|
function rowSearchFields(row) {
|
|
@@ -20124,6 +20352,8 @@ function rowSearchFields(row) {
|
|
|
20124
20352
|
return row.label ? [row.preview, row.label] : [row.preview];
|
|
20125
20353
|
case "branch":
|
|
20126
20354
|
return row.model ? [row.name, row.model, row.status] : [row.name, row.status];
|
|
20355
|
+
case "separator":
|
|
20356
|
+
return [];
|
|
20127
20357
|
}
|
|
20128
20358
|
}
|
|
20129
20359
|
function rowMatches(row, needle) {
|
|
@@ -20156,17 +20386,17 @@ function applySearch(rows, search) {
|
|
|
20156
20386
|
return rows.filter((_, i) => keep[i]);
|
|
20157
20387
|
}
|
|
20158
20388
|
function computeTotalTokens(transcript) {
|
|
20159
|
-
let
|
|
20389
|
+
let lastIndex2 = -1;
|
|
20160
20390
|
let lastInput = 0;
|
|
20161
20391
|
transcript.messages.forEach((m, idx) => {
|
|
20162
20392
|
if (m.role === "assistant" && typeof m.tokens?.input === "number") {
|
|
20163
|
-
|
|
20393
|
+
lastIndex2 = idx;
|
|
20164
20394
|
lastInput = m.tokens.input;
|
|
20165
20395
|
}
|
|
20166
20396
|
});
|
|
20167
20397
|
let counted = 0;
|
|
20168
20398
|
let guessed = 0;
|
|
20169
|
-
for (let i = Math.max(
|
|
20399
|
+
for (let i = Math.max(lastIndex2, 0);i < transcript.messages.length; i++) {
|
|
20170
20400
|
const m = transcript.messages[i];
|
|
20171
20401
|
if (m.role === "user") {
|
|
20172
20402
|
guessed += estimateTokens(userText(m));
|
|
@@ -20179,7 +20409,7 @@ function computeTotalTokens(transcript) {
|
|
|
20179
20409
|
if (p.type === "tool" || typeof output2 !== "number")
|
|
20180
20410
|
guessed += estimateTokens(partText2(p));
|
|
20181
20411
|
}
|
|
20182
|
-
return { tokens: lastInput + counted + guessed, estimated:
|
|
20412
|
+
return { tokens: lastInput + counted + guessed, estimated: lastIndex2 === -1 || guessed > 0 };
|
|
20183
20413
|
}
|
|
20184
20414
|
function currentChainOf(state, currentSessionID) {
|
|
20185
20415
|
return [...ancestorChainOf(state, currentSessionID), currentSessionID];
|
|
@@ -20231,7 +20461,7 @@ function buildTreeView(o) {
|
|
|
20231
20461
|
let currentRowId;
|
|
20232
20462
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
20233
20463
|
const r = rows[i];
|
|
20234
|
-
if (r.kind
|
|
20464
|
+
if ((r.kind === "turn" || r.kind === "step") && r.sessionID === o.currentSessionID) {
|
|
20235
20465
|
currentRowId = r.id;
|
|
20236
20466
|
break;
|
|
20237
20467
|
}
|
|
@@ -20323,111 +20553,141 @@ function spanOf(m) {
|
|
|
20323
20553
|
const end = m.time.completed ?? m.parts.reduce((e, p) => Math.max(e, p.state?.time?.end ?? p.time?.end ?? 0), 0);
|
|
20324
20554
|
return end > m.time.created ? end - m.time.created : 0;
|
|
20325
20555
|
}
|
|
20326
|
-
|
|
20327
|
-
|
|
20556
|
+
var EVENT_GLYPH = "\u25AC";
|
|
20557
|
+
function ctreeKindOf2(message) {
|
|
20558
|
+
for (const p of message.parts) {
|
|
20559
|
+
const ctree = p.metadata?.["ctree"];
|
|
20560
|
+
if (ctree?.kind)
|
|
20561
|
+
return ctree.kind;
|
|
20562
|
+
}
|
|
20563
|
+
return;
|
|
20564
|
+
}
|
|
20565
|
+
function isContextMessage(message) {
|
|
20566
|
+
return message.summary === true || ctreeKindOf2(message) === "summary";
|
|
20567
|
+
}
|
|
20568
|
+
function messageEvent(message, turn) {
|
|
20569
|
+
const ms = spanOf(message);
|
|
20570
|
+
return {
|
|
20571
|
+
lane: "input",
|
|
20572
|
+
kind: isContextMessage(message) ? "context" : "user",
|
|
20573
|
+
messageID: message.id,
|
|
20574
|
+
turn,
|
|
20575
|
+
startMs: message.time.created,
|
|
20576
|
+
...ms > 0 ? { durationMs: ms } : {},
|
|
20577
|
+
tokens: message.parts.reduce((s, p) => s + estimateTokens(p.text ?? ""), 0)
|
|
20578
|
+
};
|
|
20579
|
+
}
|
|
20580
|
+
function partEvent(message, part, turn) {
|
|
20581
|
+
const t = part.state?.time ?? part.time;
|
|
20582
|
+
const base = {
|
|
20583
|
+
messageID: message.id,
|
|
20584
|
+
partID: part.id,
|
|
20585
|
+
turn,
|
|
20586
|
+
startMs: t?.start ?? message.time.created,
|
|
20587
|
+
...t?.start !== undefined && t?.end !== undefined ? { durationMs: Math.max(0, t.end - t.start) } : {}
|
|
20588
|
+
};
|
|
20589
|
+
if (part.type === "tool")
|
|
20590
|
+
return { ...base, lane: "tools", kind: "tool", error: part.state?.status === "error", tokens: estimateTokens(part.state?.output ?? "") + estimateTokens(JSON.stringify(part.state?.input ?? "")) };
|
|
20591
|
+
return { ...base, lane: "model", kind: part.type === "reasoning" ? "reasoning" : "text", tokens: estimateTokens(part.text ?? "") };
|
|
20592
|
+
}
|
|
20593
|
+
function eventsOf(transcript) {
|
|
20594
|
+
const out = [];
|
|
20328
20595
|
for (const turn of turnsOf(transcript.messages)) {
|
|
20329
|
-
if (
|
|
20330
|
-
|
|
20331
|
-
for (const m of turn.assistants) {
|
|
20332
|
-
for (const p of m.parts) {
|
|
20333
|
-
if (p.type !== "tool")
|
|
20334
|
-
continue;
|
|
20335
|
-
any2 = true;
|
|
20336
|
-
const out = p.state?.output ?? "";
|
|
20337
|
-
const t = p.state?.time;
|
|
20338
|
-
columns.push({ messageID: m.id, userMessageID: turn.user?.id, partID: p.id, turn: turn.index, input: m.tokens?.input ?? 0, output: 0, tool: estimateTokens(out), toolError: p.state?.status === "error", ms: t?.start !== undefined && t?.end !== undefined ? t.end - t.start : 0 });
|
|
20339
|
-
}
|
|
20340
|
-
}
|
|
20341
|
-
if (!any2) {
|
|
20342
|
-
const last2 = turn.assistants.at(-1);
|
|
20343
|
-
columns.push({ messageID: last2?.id ?? turn.user?.id ?? "", userMessageID: turn.user?.id, turn: turn.index, input: last2?.tokens?.input ?? 0, output: last2?.tokens?.output ?? 0, tool: 0, toolError: false, ms: last2 ? spanOf(last2) : 0 });
|
|
20344
|
-
}
|
|
20345
|
-
continue;
|
|
20346
|
-
}
|
|
20347
|
-
const last = turn.assistants.at(-1);
|
|
20348
|
-
let tool = 0;
|
|
20349
|
-
let toolError = false;
|
|
20350
|
-
let ms = 0;
|
|
20351
|
-
let output2 = 0;
|
|
20596
|
+
if (turn.user)
|
|
20597
|
+
out.push(messageEvent(turn.user, turn.index));
|
|
20352
20598
|
for (const m of turn.assistants) {
|
|
20353
|
-
|
|
20354
|
-
|
|
20599
|
+
const context = isContextMessage(m);
|
|
20600
|
+
if (context)
|
|
20601
|
+
out.push(messageEvent(m, turn.index));
|
|
20355
20602
|
for (const p of m.parts) {
|
|
20356
|
-
|
|
20603
|
+
const kind = stepKind(p);
|
|
20604
|
+
if (kind === "other" || context && kind !== "tool")
|
|
20357
20605
|
continue;
|
|
20358
|
-
|
|
20359
|
-
if (p.state?.status === "error")
|
|
20360
|
-
toolError = true;
|
|
20606
|
+
out.push(partEvent(m, p, turn.index));
|
|
20361
20607
|
}
|
|
20362
20608
|
}
|
|
20363
|
-
columns.push({ messageID: last?.id ?? turn.user?.id ?? "", userMessageID: turn.user?.id, turn: turn.index, input: last?.tokens?.input ?? 0, output: output2, tool, toolError, ms });
|
|
20364
20609
|
}
|
|
20365
|
-
return
|
|
20610
|
+
return out;
|
|
20366
20611
|
}
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
if (values.length === 0 || width <= 0)
|
|
20370
|
-
return "";
|
|
20371
|
-
const cells = fitColumns(values, width);
|
|
20372
|
-
const max = Math.max(1, scale ?? 0, ...cells);
|
|
20373
|
-
return cells.map((v) => v <= 0 ? " " : BLOCKS[Math.min(7, Math.floor(v / max * 7.999))]).join("");
|
|
20612
|
+
function gapBefore(events, i, mode) {
|
|
20613
|
+
return mode === "turns" && events[i].turn !== events[i - 1].turn ? 2 : 1;
|
|
20374
20614
|
}
|
|
20375
|
-
function
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
const per = values.length / width;
|
|
20383
|
-
for (let i = 0;i < width; i++) {
|
|
20384
|
-
const a = Math.floor(i * per);
|
|
20385
|
-
const b = Math.max(a + 1, Math.floor((i + 1) * per));
|
|
20386
|
-
out.push(Math.max(...values.slice(a, b)));
|
|
20387
|
-
}
|
|
20388
|
-
return out;
|
|
20615
|
+
function firstFitting(events, mode, width) {
|
|
20616
|
+
let cells = 0;
|
|
20617
|
+
for (let i = events.length - 1;i >= 0; i--) {
|
|
20618
|
+
const next = cells === 0 ? 1 : cells + 1 + gapBefore(events, i + 1, mode);
|
|
20619
|
+
if (next > width)
|
|
20620
|
+
return i + 1;
|
|
20621
|
+
cells = next;
|
|
20389
20622
|
}
|
|
20390
|
-
|
|
20391
|
-
for (let i = 0;i < width; i++)
|
|
20392
|
-
out.push(values[Math.min(values.length - 1, Math.floor(i / rep))]);
|
|
20393
|
-
return out;
|
|
20623
|
+
return 0;
|
|
20394
20624
|
}
|
|
20395
|
-
function
|
|
20396
|
-
const total =
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
const
|
|
20400
|
-
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
20625
|
+
function durationWidths(events, budget) {
|
|
20626
|
+
const total = events.reduce((s, e) => s + Math.max(0, e.durationMs ?? 0), 0);
|
|
20627
|
+
if (total <= 0)
|
|
20628
|
+
return events.map(() => 1);
|
|
20629
|
+
const widths = events.map((e) => Math.max(1, Math.round(Math.max(0, e.durationMs ?? 0) / total * budget)));
|
|
20630
|
+
let over = widths.reduce((s, v) => s + v, 0) - budget;
|
|
20631
|
+
while (over > 0) {
|
|
20632
|
+
let widest = -1;
|
|
20633
|
+
for (let i = 0;i < widths.length; i++)
|
|
20634
|
+
if ((widths[i] ?? 0) > 1 && (widest < 0 || (widths[i] ?? 0) > (widths[widest] ?? 0)))
|
|
20635
|
+
widest = i;
|
|
20636
|
+
if (widest < 0)
|
|
20637
|
+
break;
|
|
20638
|
+
widths[widest] = (widths[widest] ?? 1) - 1;
|
|
20639
|
+
over--;
|
|
20640
|
+
}
|
|
20641
|
+
return widths;
|
|
20642
|
+
}
|
|
20643
|
+
function buildEventStrip(transcript, mode, width) {
|
|
20644
|
+
const w = Math.max(0, Math.floor(width));
|
|
20645
|
+
const all = eventsOf(transcript);
|
|
20646
|
+
const truncatedLeft = firstFitting(all, mode, w);
|
|
20647
|
+
const events = all.slice(truncatedLeft);
|
|
20648
|
+
const gaps = events.map((_, i) => i === 0 ? 0 : gapBefore(events, i, mode));
|
|
20649
|
+
const widths = mode === "duration" ? durationWidths(events, w - gaps.reduce((s, g) => s + g, 0)) : events.map(() => 1);
|
|
20650
|
+
const blank = () => Array.from({ length: w }, () => null);
|
|
20651
|
+
const lanes = { input: blank(), model: blank(), tools: blank() };
|
|
20652
|
+
const empty = { input: true, model: true, tools: true };
|
|
20653
|
+
const spans = [];
|
|
20654
|
+
let cursor = 0;
|
|
20655
|
+
events.forEach((e, i) => {
|
|
20656
|
+
const start = cursor + (gaps[i] ?? 0);
|
|
20657
|
+
const end = Math.min(w, start + (widths[i] ?? 1));
|
|
20658
|
+
for (let c = start;c < end; c++)
|
|
20659
|
+
lanes[e.lane][c] = { lane: e.lane, eventIndex: i, glyph: EVENT_GLYPH, ...e.error ? { error: true } : {} };
|
|
20660
|
+
spans.push({ start, end });
|
|
20661
|
+
empty[e.lane] = false;
|
|
20662
|
+
cursor = start + (widths[i] ?? 1);
|
|
20411
20663
|
});
|
|
20412
|
-
return {
|
|
20664
|
+
return { events, width: w, lanes, spans, empty, truncatedLeft };
|
|
20413
20665
|
}
|
|
20414
|
-
function
|
|
20666
|
+
function stripIndexFor(strip, messageID, partID) {
|
|
20415
20667
|
if (partID) {
|
|
20416
|
-
const
|
|
20417
|
-
if (
|
|
20418
|
-
return
|
|
20668
|
+
const byPart = strip.events.findIndex((e) => e.partID === partID);
|
|
20669
|
+
if (byPart >= 0)
|
|
20670
|
+
return byPart;
|
|
20419
20671
|
}
|
|
20420
|
-
|
|
20421
|
-
return i >= 0 ? i : -1;
|
|
20672
|
+
return strip.events.findIndex((e) => e.messageID === messageID);
|
|
20422
20673
|
}
|
|
20423
20674
|
|
|
20424
20675
|
// src/core/consumers.ts
|
|
20676
|
+
var THINKING = "(thinking)";
|
|
20677
|
+
var THINKING_NOTE = "provider reasoning \xB7 not croppable";
|
|
20425
20678
|
function consumers(transcript, opts = {}) {
|
|
20426
20679
|
const acc = new Map;
|
|
20427
|
-
const add = (source, kind, tokens) => {
|
|
20428
|
-
const c = acc.get(source) ?? { source, kind, tokens: 0, count: 0, share: 0 };
|
|
20680
|
+
const add = (source, kind, tokens, message, part) => {
|
|
20681
|
+
const c = acc.get(source) ?? { source, kind, tokens: 0, count: 0, share: 0, entries: [] };
|
|
20429
20682
|
c.tokens += tokens;
|
|
20430
20683
|
c.count += 1;
|
|
20684
|
+
c.entries.push({
|
|
20685
|
+
messageID: message.id,
|
|
20686
|
+
partID: part.id,
|
|
20687
|
+
tokens,
|
|
20688
|
+
preview: partPreview(part),
|
|
20689
|
+
croppable: part.type === "tool" && part.state?.status === "completed"
|
|
20690
|
+
});
|
|
20431
20691
|
acc.set(source, c);
|
|
20432
20692
|
};
|
|
20433
20693
|
for (const m of transcript.messages) {
|
|
@@ -20435,25 +20695,32 @@ function consumers(transcript, opts = {}) {
|
|
|
20435
20695
|
if (opts.cropped?.has(p.id))
|
|
20436
20696
|
continue;
|
|
20437
20697
|
if (p.type === "tool")
|
|
20438
|
-
add(p.tool ?? "tool", "tool", estimateTokens(p.state?.output ?? "") + estimateTokens(JSON.stringify(p.state?.input ?? "")));
|
|
20698
|
+
add(p.tool ?? "tool", "tool", estimateTokens(p.state?.output ?? "") + estimateTokens(JSON.stringify(p.state?.input ?? "")), m, p);
|
|
20439
20699
|
else if (p.type === "text") {
|
|
20440
20700
|
const kind = p.metadata?.["ctree"]?.kind;
|
|
20441
20701
|
if (kind === "decision")
|
|
20442
|
-
add("\u25C6 decisions", "decision", estimateTokens(p.text ?? ""));
|
|
20702
|
+
add("\u25C6 decisions", "decision", estimateTokens(p.text ?? ""), m, p);
|
|
20443
20703
|
else if (kind === "summary")
|
|
20444
|
-
add("\u25C7 branch summaries", "summary", estimateTokens(p.text ?? ""));
|
|
20704
|
+
add("\u25C7 branch summaries", "summary", estimateTokens(p.text ?? ""), m, p);
|
|
20445
20705
|
else if (m.role === "user")
|
|
20446
|
-
add("\u25CF user prompts", "user", estimateTokens(p.text ?? ""));
|
|
20706
|
+
add("\u25CF user prompts", "user", estimateTokens(p.text ?? ""), m, p);
|
|
20447
20707
|
else if (m.summary)
|
|
20448
|
-
add("\u25C7 compaction summaries", "summary", estimateTokens(p.text ?? ""));
|
|
20708
|
+
add("\u25C7 compaction summaries", "summary", estimateTokens(p.text ?? ""), m, p);
|
|
20449
20709
|
else
|
|
20450
|
-
add("\u25CB assistant text", "assistant", estimateTokens(p.text ?? ""));
|
|
20710
|
+
add("\u25CB assistant text", "assistant", estimateTokens(p.text ?? ""), m, p);
|
|
20451
20711
|
} else if (p.type === "reasoning")
|
|
20452
|
-
add(
|
|
20712
|
+
add(THINKING, "reasoning", estimateTokens(p.text ?? ""), m, p);
|
|
20453
20713
|
}
|
|
20454
20714
|
}
|
|
20455
20715
|
const total = [...acc.values()].reduce((s, c) => s + c.tokens, 0) || 1;
|
|
20456
|
-
|
|
20716
|
+
const limit = opts.limit !== undefined && opts.limit > 0 ? opts.limit : undefined;
|
|
20717
|
+
return [...acc.values()].map((c) => ({
|
|
20718
|
+
...c,
|
|
20719
|
+
share: c.tokens / total,
|
|
20720
|
+
...limit === undefined ? {} : { shareOfWindow: c.tokens / limit },
|
|
20721
|
+
...c.kind === "reasoning" ? { note: THINKING_NOTE } : {},
|
|
20722
|
+
entries: c.entries.sort((a, b) => b.tokens - a.tokens)
|
|
20723
|
+
})).sort((a, b) => b.tokens - a.tokens);
|
|
20457
20724
|
}
|
|
20458
20725
|
function bar(share, width) {
|
|
20459
20726
|
const n = Math.round(share * width);
|
|
@@ -20461,8 +20728,8 @@ function bar(share, width) {
|
|
|
20461
20728
|
}
|
|
20462
20729
|
|
|
20463
20730
|
// src/tui/route.tsx
|
|
20464
|
-
import
|
|
20465
|
-
import
|
|
20731
|
+
import fs5 from "fs";
|
|
20732
|
+
import path4 from "path";
|
|
20466
20733
|
|
|
20467
20734
|
// src/core/cropplan.ts
|
|
20468
20735
|
function sha8(text) {
|
|
@@ -20692,7 +20959,14 @@ function textOf(row) {
|
|
|
20692
20959
|
return plain(row.preview);
|
|
20693
20960
|
return `assistant: ${plain(row.preview)}`;
|
|
20694
20961
|
}
|
|
20962
|
+
function thoughtOf(row) {
|
|
20963
|
+
if (row.kind !== "step" || row.thinkingMs === undefined)
|
|
20964
|
+
return "";
|
|
20965
|
+
return ` \xB7 ${(row.thinkingMs / 1000).toFixed(row.thinkingMs < 1e4 ? 1 : 0)}s thought`;
|
|
20966
|
+
}
|
|
20695
20967
|
function rowLine(row, width, here) {
|
|
20968
|
+
if (row.kind === "separator")
|
|
20969
|
+
return `${row.gutter}${row.text}`;
|
|
20696
20970
|
const tokens = `${row.kind !== "branch" && row.estimated ? "~" : ""}${formatK(row.tokens)}`;
|
|
20697
20971
|
const marker = here ? " \u2190 here" : "";
|
|
20698
20972
|
let body;
|
|
@@ -20705,32 +20979,82 @@ function rowLine(row, width, here) {
|
|
|
20705
20979
|
} else {
|
|
20706
20980
|
const flags = row.kind === "step" ? `${row.label ? ` [${row.label}]` : ""}${row.isCropped ? " \u2702" : ""}${row.warn ? " \u26A0" : ""}${row.isError ? " \u2717" : ""}` : row.label ? ` [${row.label}]` : "";
|
|
20707
20981
|
const dur = row.kind === "step" && row.durationMs !== undefined ? ` ${(row.durationMs / 1000).toFixed(row.durationMs < 1e4 ? 1 : 0)}s` : "";
|
|
20708
|
-
body = `${row.gutter}${glyphOf(row)} ${textOf(row)}${flags}${dur}${marker}`;
|
|
20982
|
+
body = `${row.gutter}${glyphOf(row)} ${textOf(row)}${flags}${dur}${thoughtOf(row)}${marker}`;
|
|
20709
20983
|
}
|
|
20710
20984
|
return fitRow(body, tokens, width);
|
|
20711
20985
|
}
|
|
20986
|
+
function segmentsOf(line, query, thought) {
|
|
20987
|
+
const ranges = [];
|
|
20988
|
+
if (query) {
|
|
20989
|
+
const at = line.toLowerCase().indexOf(query.toLowerCase());
|
|
20990
|
+
if (at >= 0)
|
|
20991
|
+
ranges.push({
|
|
20992
|
+
at,
|
|
20993
|
+
len: query.length,
|
|
20994
|
+
kind: "match"
|
|
20995
|
+
});
|
|
20996
|
+
}
|
|
20997
|
+
if (thought) {
|
|
20998
|
+
const at = line.lastIndexOf(thought);
|
|
20999
|
+
if (at >= 0)
|
|
21000
|
+
ranges.push({
|
|
21001
|
+
at,
|
|
21002
|
+
len: thought.length,
|
|
21003
|
+
kind: "dim"
|
|
21004
|
+
});
|
|
21005
|
+
}
|
|
21006
|
+
if (ranges.length === 0)
|
|
21007
|
+
return [{
|
|
21008
|
+
text: line,
|
|
21009
|
+
kind: "plain"
|
|
21010
|
+
}];
|
|
21011
|
+
const out = [];
|
|
21012
|
+
let cursor = 0;
|
|
21013
|
+
for (const r of ranges.sort((a, b) => a.at - b.at)) {
|
|
21014
|
+
if (r.at < cursor)
|
|
21015
|
+
continue;
|
|
21016
|
+
if (r.at > cursor)
|
|
21017
|
+
out.push({
|
|
21018
|
+
text: line.slice(cursor, r.at),
|
|
21019
|
+
kind: "plain"
|
|
21020
|
+
});
|
|
21021
|
+
out.push({
|
|
21022
|
+
text: line.slice(r.at, r.at + r.len),
|
|
21023
|
+
kind: r.kind
|
|
21024
|
+
});
|
|
21025
|
+
cursor = r.at + r.len;
|
|
21026
|
+
}
|
|
21027
|
+
if (cursor < line.length)
|
|
21028
|
+
out.push({
|
|
21029
|
+
text: line.slice(cursor),
|
|
21030
|
+
kind: "plain"
|
|
21031
|
+
});
|
|
21032
|
+
return out;
|
|
21033
|
+
}
|
|
20712
21034
|
var DEFAULT_KEYS = {
|
|
20713
21035
|
up: ["up", "k"],
|
|
20714
21036
|
down: ["down", "j"],
|
|
20715
21037
|
jump_up: ["shift+up", "shift+k"],
|
|
20716
21038
|
jump_down: ["shift+down", "shift+j"],
|
|
20717
|
-
|
|
21039
|
+
half_up: ["ctrl+u"],
|
|
21040
|
+
half_down: ["ctrl+d"],
|
|
21041
|
+
first: ["gg"],
|
|
20718
21042
|
last: ["shift+g"],
|
|
20719
21043
|
prev_branch: ["["],
|
|
20720
21044
|
next_branch: ["]"],
|
|
20721
21045
|
fold: ["left", "h"],
|
|
20722
21046
|
unfold: ["right", "l"],
|
|
20723
|
-
toggle: ["e"],
|
|
21047
|
+
toggle: ["tab", "e"],
|
|
20724
21048
|
go: ["return"],
|
|
20725
21049
|
branch: ["b"],
|
|
20726
21050
|
crop: ["c"],
|
|
20727
21051
|
crop_toggle_mode: ["t"],
|
|
20728
21052
|
mark: ["space"],
|
|
20729
21053
|
auto: ["a"],
|
|
20730
|
-
undo: ["x"],
|
|
21054
|
+
undo: ["u", "x"],
|
|
20731
21055
|
merge: ["m"],
|
|
20732
21056
|
inspector: ["i"],
|
|
20733
|
-
consumers: ["
|
|
21057
|
+
consumers: ["s"],
|
|
20734
21058
|
copy: ["y"],
|
|
20735
21059
|
mode_duration: ["1"],
|
|
20736
21060
|
mode_turns: ["2"],
|
|
@@ -20739,13 +21063,43 @@ var DEFAULT_KEYS = {
|
|
|
20739
21063
|
decisions: ["shift+d"],
|
|
20740
21064
|
export: ["shift+e"],
|
|
20741
21065
|
label: ["shift+l"],
|
|
20742
|
-
|
|
21066
|
+
filter_pick: ["f"],
|
|
21067
|
+
filter_prev: ["shift+f"],
|
|
20743
21068
|
search: ["/"],
|
|
21069
|
+
search_next: ["n"],
|
|
21070
|
+
search_prev: ["shift+n"],
|
|
20744
21071
|
help: ["?", "shift+/"],
|
|
20745
21072
|
back: ["q", "escape"]
|
|
20746
21073
|
};
|
|
21074
|
+
var EMPTY_TRANSCRIPT = {
|
|
21075
|
+
sessionID: "",
|
|
21076
|
+
title: "",
|
|
21077
|
+
status: "available",
|
|
21078
|
+
messages: []
|
|
21079
|
+
};
|
|
20747
21080
|
var NO_BRANCHES = "No branches yet \xB7 b forks here into a real OpenCode session; nothing is copied or deleted.";
|
|
20748
|
-
var HELP = ["? help \xB7 ? or esc closes", "
|
|
21081
|
+
var HELP = ["? help \xB7 ? or esc closes", "Move", " \u2191\u2193 j k \xB7 J K by 20 \xB7 ctrl+d ctrl+u half page \xB7 gg top \xB7 G bottom \xB7 [ ] branch rows", " h l \u2190 \u2192 fold/unfold a branch \xB7 Tab (or e) toggle \xB7 / live search \xB7 n N next/prev match", "Act", " \u23CE go \u2014 a \u2387 header switches to it \xB7 a user turn forks & prefills it \xB7 a step forks after it", " b branch \xB7 m merge \xB7 c crop mode (space mark \xB7 a auto \xB7 t result\u21C4turn \xB7 \u23CE apply \xB7 esc leave)", " u undo (alias x) \xB7 L label \xB7 y copy \xB7 E export decisions", "Views", " i inspector \xB7 1 2 3 lanes (duration/turns/calls) \xB7 0 off \xB7 s consumers \xB7 D decisions \xB7 f F filter", "Legend", " \u25CF user \xB7 \u25CB assistant \xB7 \u2699 tool step \xB7 \u25C6 decision \xB7 \u2263 summary \xB7 \u2387 branch (a real OpenCode session)", " \u2502 \u251C \u2570 draw the topology \xB7 \u25BE open \u25B8 folded \xB7 \u2190 here is the session you are in", " dim rows are not sent to the model; \u2500\u2500 not in this branch's context \u2500\u2500 is where your path forked", " right column is tokens; ~ estimated \xB7 \u26A0 \u226510k \xB7 \u2702 cropped \xB7 \u2717 tool error", " \u2387 colours: open green \xB7 squashed blue \xB7 rejected/discarded red \xB7 abandoned grey"];
|
|
21082
|
+
var FILTERS = [{
|
|
21083
|
+
title: "default",
|
|
21084
|
+
value: "default",
|
|
21085
|
+
description: "user turns, assistant text, tool steps"
|
|
21086
|
+
}, {
|
|
21087
|
+
title: "no-tools",
|
|
21088
|
+
value: "no-tools",
|
|
21089
|
+
description: "hide \u2699 tool steps"
|
|
21090
|
+
}, {
|
|
21091
|
+
title: "user-only",
|
|
21092
|
+
value: "user-only",
|
|
21093
|
+
description: "\u25CF user turns only"
|
|
21094
|
+
}, {
|
|
21095
|
+
title: "labeled",
|
|
21096
|
+
value: "labeled",
|
|
21097
|
+
description: "labelled rows only"
|
|
21098
|
+
}, {
|
|
21099
|
+
title: "all",
|
|
21100
|
+
value: "all",
|
|
21101
|
+
description: "everything, thinking parts included"
|
|
21102
|
+
}];
|
|
20749
21103
|
function bindingsFor(overrides) {
|
|
20750
21104
|
const out = [];
|
|
20751
21105
|
for (const [cmd, keys] of Object.entries(DEFAULT_KEYS))
|
|
@@ -20777,6 +21131,8 @@ function TreeRoute(props) {
|
|
|
20777
21131
|
const [expanded, setExpanded] = createSignal2(new Set(api2.kv.get(`ctree.expanded.${sessionID}`, [])));
|
|
20778
21132
|
const [filter, setFilter] = createSignal2(api2.kv.get("ctree.filter", "default"));
|
|
20779
21133
|
const [search, setSearch] = createSignal2("");
|
|
21134
|
+
const [searchMode, setSearchMode] = createSignal2(false);
|
|
21135
|
+
let searchBefore = "";
|
|
20780
21136
|
const [selected, setSelected] = createSignal2(0);
|
|
20781
21137
|
const [others, setOthers] = createSignal2({});
|
|
20782
21138
|
const [busy, setBusy] = createSignal2();
|
|
@@ -20786,7 +21142,9 @@ function TreeRoute(props) {
|
|
|
20786
21142
|
const [lanesOn, setLanesOn] = createSignal2(api2.kv.get("ctree.lanesOn", false));
|
|
20787
21143
|
const [inspector, setInspector] = createSignal2(api2.kv.get("ctree.inspector", false));
|
|
20788
21144
|
const [consumerIndex, setConsumerIndex] = createSignal2(0);
|
|
21145
|
+
const [consumerOpen, setConsumerOpen] = createSignal2(new Set);
|
|
20789
21146
|
const [decisionIndex, setDecisionIndex] = createSignal2(0);
|
|
21147
|
+
const [decisionScroll, setDecisionScroll] = createSignal2(0);
|
|
20790
21148
|
const [marked, setMarked] = createSignal2(new Set);
|
|
20791
21149
|
const state = createMemo(() => {
|
|
20792
21150
|
tick();
|
|
@@ -20901,19 +21259,23 @@ function TreeRoute(props) {
|
|
|
20901
21259
|
api2.renderer.on("resize", onResize);
|
|
20902
21260
|
onCleanup(() => void api2.renderer.off("resize", onResize));
|
|
20903
21261
|
const cols = () => size().cols;
|
|
20904
|
-
const
|
|
21262
|
+
const helpHeight = () => panel() === "help" ? Math.min(HELP.length, Math.max(0, size().rows - 12)) : 0;
|
|
21263
|
+
const height = () => Math.max(4, size().rows - 8 - (lanesOn() && size().rows >= 12 ? 3 : 0) - helpHeight());
|
|
20905
21264
|
const width = () => Math.max(60, cols() - 4);
|
|
21265
|
+
const overflow = () => view().rows.length > height() - 2;
|
|
21266
|
+
const rowsHeight = () => overflow() ? height() - 2 : height();
|
|
20906
21267
|
const windowStart = createMemo(() => {
|
|
20907
|
-
const h =
|
|
21268
|
+
const h = rowsHeight();
|
|
20908
21269
|
const s = selected();
|
|
20909
21270
|
const n = view().rows.length;
|
|
20910
|
-
|
|
20911
|
-
return start;
|
|
21271
|
+
return Math.max(0, Math.min(s - Math.floor(h / 2), n - h));
|
|
20912
21272
|
});
|
|
20913
|
-
const visible = createMemo(() => view().rows.slice(windowStart(), windowStart() +
|
|
21273
|
+
const visible = createMemo(() => view().rows.slice(windowStart(), windowStart() + rowsHeight()));
|
|
21274
|
+
const hiddenAbove = () => windowStart();
|
|
21275
|
+
const hiddenBelow = () => Math.max(0, view().rows.length - windowStart() - rowsHeight());
|
|
20914
21276
|
const live = () => sessionID ? liveTranscript(api2, sessionID) : undefined;
|
|
20915
21277
|
const currentMessageOf = (row) => {
|
|
20916
|
-
if (row.kind === "branch")
|
|
21278
|
+
if (row.kind === "branch" || row.kind === "separator")
|
|
20917
21279
|
return;
|
|
20918
21280
|
if (row.sessionID === sessionID)
|
|
20919
21281
|
return row.messageID;
|
|
@@ -20958,10 +21320,35 @@ function TreeRoute(props) {
|
|
|
20958
21320
|
const list = cropMode() === "result" ? resultCands() : turnCands();
|
|
20959
21321
|
return list.filter((c) => m.has(markKey(c)));
|
|
20960
21322
|
});
|
|
21323
|
+
function modeForRow(row) {
|
|
21324
|
+
if (row.kind === "step" && resultCands().some((c) => c.partID === (currentPartOf(row) ?? row.partID)))
|
|
21325
|
+
return "result";
|
|
21326
|
+
if (row.kind !== "branch" && row.kind !== "separator" && turnCands().some((c) => c.anchorMessageID === currentMessageOf(row)))
|
|
21327
|
+
return "turn";
|
|
21328
|
+
return;
|
|
21329
|
+
}
|
|
21330
|
+
const armed = () => {
|
|
21331
|
+
const row = current();
|
|
21332
|
+
const c = row ? candidateOf(row) : undefined;
|
|
21333
|
+
if (!c)
|
|
21334
|
+
return false;
|
|
21335
|
+
const key = markKey(c);
|
|
21336
|
+
return marked().has(`${key}:warned`) && !marked().has(key);
|
|
21337
|
+
};
|
|
20961
21338
|
function toggleMark() {
|
|
20962
21339
|
const row = current();
|
|
20963
21340
|
if (!row)
|
|
20964
21341
|
return;
|
|
21342
|
+
if (!cropMode()) {
|
|
21343
|
+
const mode = modeForRow(row);
|
|
21344
|
+
if (!mode) {
|
|
21345
|
+
api2.ui.toast({
|
|
21346
|
+
message: "nothing croppable on this row \u2014 c opens crop mode"
|
|
21347
|
+
});
|
|
21348
|
+
return;
|
|
21349
|
+
}
|
|
21350
|
+
setCropMode(mode);
|
|
21351
|
+
}
|
|
20965
21352
|
const c = candidateOf(row);
|
|
20966
21353
|
debug("crop.mark", {
|
|
20967
21354
|
row: row.id,
|
|
@@ -20993,6 +21380,13 @@ function TreeRoute(props) {
|
|
|
20993
21380
|
next.add(key);
|
|
20994
21381
|
setMarked(next);
|
|
20995
21382
|
}
|
|
21383
|
+
async function leaveCropMode() {
|
|
21384
|
+
const n = selectedCandidates().length;
|
|
21385
|
+
if (n > 0 && !await confirm(`Drop ${n} mark${n === 1 ? "" : "s"}?`, "Nothing has been cropped yet \u2014 the marks are lost, the transcript is untouched."))
|
|
21386
|
+
return;
|
|
21387
|
+
setCropMode(undefined);
|
|
21388
|
+
setMarked(new Set);
|
|
21389
|
+
}
|
|
20996
21390
|
function autoMarkAll() {
|
|
20997
21391
|
if (cropMode() !== "result")
|
|
20998
21392
|
return;
|
|
@@ -21070,69 +21464,54 @@ function TreeRoute(props) {
|
|
|
21070
21464
|
},
|
|
21071
21465
|
parts: m.parts
|
|
21072
21466
|
}))));
|
|
21073
|
-
const band = () => bandFor(contextSize().tokens);
|
|
21467
|
+
const band = () => bandFor(contextSize().tokens, contextLimit());
|
|
21074
21468
|
const branchOfCurrent = () => sessionID ? state().sessions[sessionID] : undefined;
|
|
21075
21469
|
const userTurns = () => (live()?.messages ?? []).filter((m) => m.role === "user").length;
|
|
21076
|
-
const lanes = createMemo(() => live() ? buildLanes(live(), laneMode() === "duration" ? "turns" : laneMode()) : {
|
|
21077
|
-
mode: laneMode(),
|
|
21078
|
-
columns: []
|
|
21079
|
-
});
|
|
21080
21470
|
const laneWidth = () => Math.max(10, Math.min(width() - 46, 80));
|
|
21081
|
-
const
|
|
21082
|
-
|
|
21083
|
-
if (laneMode() === "duration") {
|
|
21084
|
-
const w = durationWeighted(l, laneWidth());
|
|
21085
|
-
return {
|
|
21086
|
-
input: w.input,
|
|
21087
|
-
output: w.output,
|
|
21088
|
-
tool: w.tool,
|
|
21089
|
-
toolError: w.toolError,
|
|
21090
|
-
cellFor: (col) => w.input.findIndex((_, i) => w.columnAt(i) === col)
|
|
21091
|
-
};
|
|
21092
|
-
}
|
|
21093
|
-
const n = l.columns.length;
|
|
21094
|
-
const cellFor = (col) => n === 0 ? -1 : Math.floor(col * laneWidth() / Math.max(n, laneWidth())) + (n < laneWidth() ? Math.floor(laneWidth() / n / 2) : 0);
|
|
21095
|
-
return {
|
|
21096
|
-
input: l.columns.map((c) => c.input),
|
|
21097
|
-
output: l.columns.map((c) => c.output),
|
|
21098
|
-
tool: l.columns.map((c) => c.tool),
|
|
21099
|
-
toolError: l.columns.map((c) => c.toolError),
|
|
21100
|
-
cellFor
|
|
21101
|
-
};
|
|
21102
|
-
});
|
|
21103
|
-
const cursorCell = createMemo(() => {
|
|
21471
|
+
const strip = createMemo(() => buildEventStrip(live() ?? EMPTY_TRANSCRIPT, laneMode(), laneWidth()));
|
|
21472
|
+
const cursorEvent = createMemo(() => {
|
|
21104
21473
|
const row = current();
|
|
21105
|
-
if (!row || row.kind === "branch")
|
|
21474
|
+
if (!row || row.kind === "branch" || row.kind === "separator")
|
|
21106
21475
|
return -1;
|
|
21107
21476
|
const mid = currentMessageOf(row) ?? row.messageID;
|
|
21108
21477
|
const pid = row.kind === "step" ? currentPartOf(row) ?? row.partID : undefined;
|
|
21109
|
-
|
|
21110
|
-
return col < 0 ? -1 : laneSeries().cellFor(col);
|
|
21478
|
+
return stripIndexFor(strip(), mid, pid);
|
|
21111
21479
|
});
|
|
21112
|
-
const
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21480
|
+
const cellColor = (cell) => {
|
|
21481
|
+
if (cell.error)
|
|
21482
|
+
return t.error;
|
|
21483
|
+
const e = strip().events[cell.eventIndex];
|
|
21484
|
+
if (!e)
|
|
21485
|
+
return t.textMuted;
|
|
21486
|
+
if (e.lane === "tools")
|
|
21487
|
+
return t.warning;
|
|
21488
|
+
if (e.lane === "input")
|
|
21489
|
+
return e.kind === "user" ? t.success : t.textMuted;
|
|
21490
|
+
return e.kind === "reasoning" ? t.textMuted : t.accent;
|
|
21491
|
+
};
|
|
21492
|
+
const laneRuns = (lane) => {
|
|
21493
|
+
const cur = cursorEvent();
|
|
21122
21494
|
const runs = [];
|
|
21123
|
-
for (
|
|
21124
|
-
const
|
|
21495
|
+
for (const cell of strip().lanes[lane]) {
|
|
21496
|
+
const sel = cell !== null && cell.eventIndex === cur;
|
|
21497
|
+
const color = cell === null ? t.textMuted : cellColor(cell);
|
|
21498
|
+
const fg = sel ? t.background : color;
|
|
21499
|
+
const bg = sel ? color : undefined;
|
|
21125
21500
|
const last = runs[runs.length - 1];
|
|
21126
|
-
if (last && last.
|
|
21127
|
-
last.text +=
|
|
21501
|
+
if (last && last.fg === fg && last.bg === bg)
|
|
21502
|
+
last.text += cell?.glyph ?? " ";
|
|
21128
21503
|
else
|
|
21129
21504
|
runs.push({
|
|
21130
|
-
text:
|
|
21131
|
-
|
|
21505
|
+
text: cell?.glyph ?? " ",
|
|
21506
|
+
fg,
|
|
21507
|
+
bg
|
|
21132
21508
|
});
|
|
21133
21509
|
}
|
|
21134
21510
|
return runs;
|
|
21135
|
-
}
|
|
21511
|
+
};
|
|
21512
|
+
const inputRuns = createMemo(() => laneRuns("input"));
|
|
21513
|
+
const modelRuns = createMemo(() => laneRuns("model"));
|
|
21514
|
+
const toolRuns = createMemo(() => laneRuns("tools"));
|
|
21136
21515
|
const contextLimit = createMemo(() => sessionID ? modelContextLimit(api2, sessionID) : undefined);
|
|
21137
21516
|
const laneRoom = () => height() >= 12 && panel() === "tree";
|
|
21138
21517
|
const showLanes = () => laneRoom() && lanesOn() && userTurns() >= 3;
|
|
@@ -21154,7 +21533,7 @@ function TreeRoute(props) {
|
|
|
21154
21533
|
const noBranchesLines = () => NO_BRANCHES.length + 2 <= rowWidth() ? [NO_BRANCHES] : NO_BRANCHES.split(/(?<=;) /);
|
|
21155
21534
|
const inspectorLines = createMemo(() => {
|
|
21156
21535
|
const row = current();
|
|
21157
|
-
if (!row)
|
|
21536
|
+
if (!row || row.kind === "separator")
|
|
21158
21537
|
return [];
|
|
21159
21538
|
const w = inspectorWidth() - 3;
|
|
21160
21539
|
const clip2 = (x) => x.length > w ? `${x.slice(0, w - 1)}\u2026` : x;
|
|
@@ -21201,19 +21580,37 @@ function TreeRoute(props) {
|
|
|
21201
21580
|
const msg = tr?.messages.find((m) => m.id === row.messageID);
|
|
21202
21581
|
const turn = view().rows.slice(0, view().indexById[row.id] + 1).filter((r) => r.kind === "turn").at(-1);
|
|
21203
21582
|
if (row.kind === "turn") {
|
|
21204
|
-
|
|
21583
|
+
const text = msg?.parts.map((p) => p.text ?? "").join(`
|
|
21584
|
+
`) ?? row.preview;
|
|
21585
|
+
if (row.isDecision) {
|
|
21586
|
+
head(`\u25C6 ${decisionSummary(text).title}`);
|
|
21587
|
+
kv("Tokens", `~${formatK(row.tokens)}`);
|
|
21588
|
+
const lines = renderDecision(text, w);
|
|
21589
|
+
for (const l of lines.slice(0, 16))
|
|
21590
|
+
out.push({
|
|
21591
|
+
fg: t.text,
|
|
21592
|
+
text: l
|
|
21593
|
+
});
|
|
21594
|
+
if (lines.length > 16)
|
|
21595
|
+
muted(`\u2026 ${lines.length - 16} more lines (y to copy)`);
|
|
21596
|
+
return out;
|
|
21597
|
+
}
|
|
21598
|
+
head(`${row.isSummary ? "\u25C7 summary" : "\u25CF user"} \xB7 T${row.turn}`);
|
|
21205
21599
|
if (row.label)
|
|
21206
21600
|
kv("Label", row.label);
|
|
21207
21601
|
kv("Tokens", `~${formatK(row.tokens)}`);
|
|
21208
21602
|
kv("At", msg ? new Date(msg.time.created).toISOString().slice(11, 19) : "?");
|
|
21209
|
-
|
|
21210
|
-
|
|
21603
|
+
if (!row.inContext)
|
|
21604
|
+
muted("not in this branch's context");
|
|
21605
|
+
block("Text", text, 14);
|
|
21211
21606
|
return out;
|
|
21212
21607
|
}
|
|
21213
21608
|
const part = msg?.parts.find((p) => p.id === row.partID);
|
|
21214
21609
|
const stepNo = msg ? msg.parts.filter((p) => p.type === "tool" || p.type === "text").findIndex((p) => p.id === row.partID) + 1 : 0;
|
|
21215
21610
|
head(`${row.glyph} ${part?.type === "tool" ? part.tool : row.glyph === "\u25C7" ? "compaction" : "assistant"} \xB7 T${turn?.kind === "turn" ? turn.turn : "?"} \xB7 step ${stepNo}`);
|
|
21216
21611
|
kv("Hierarchy", `T${turn?.kind === "turn" ? turn.turn : "?"} \u203A assistant \u203A step ${stepNo}`);
|
|
21612
|
+
if (!row.inContext)
|
|
21613
|
+
muted("not in this branch's context");
|
|
21217
21614
|
if (part?.type === "tool") {
|
|
21218
21615
|
const st = part.state;
|
|
21219
21616
|
const dur = st?.time?.start !== undefined && st?.time?.end !== undefined ? `${st.time.end - st.time.start} ms` : "?";
|
|
@@ -21223,25 +21620,66 @@ function TreeRoute(props) {
|
|
|
21223
21620
|
block("Result", String(st?.output ?? ""), 10);
|
|
21224
21621
|
kv("Timing", st?.time?.start ? `started ${new Date(st.time.start).toISOString().slice(11, 23)} \xB7 ${dur} \xB7 session ts` : "n/a");
|
|
21225
21622
|
const cand = resultCands().find((c) => c.partID === (currentPartOf(row) ?? row.partID));
|
|
21226
|
-
kv("Crop", row.isCropped ?
|
|
21623
|
+
kv("Crop", row.isCropped ? `\u2702 cropped (${UNDO_KEY} to restore)` : cand ? cand.protections.length ? `protected: ${cand.protections.join(", ")}` : "c then space to stub this result" : "n/a");
|
|
21227
21624
|
} else {
|
|
21228
21625
|
kv("Tokens", `~${formatK(row.tokens)}`);
|
|
21229
21626
|
if (row.durationMs !== undefined)
|
|
21230
21627
|
kv("Duration", `${(row.durationMs / 1000).toFixed(1)} s`);
|
|
21628
|
+
if (row.thinkingMs !== undefined)
|
|
21629
|
+
kv("Thought", `${(row.thinkingMs / 1000).toFixed(1)} s`);
|
|
21231
21630
|
block("Text", part?.text ?? row.preview, 14);
|
|
21232
21631
|
}
|
|
21233
21632
|
return out;
|
|
21234
21633
|
});
|
|
21235
21634
|
const consumerRows = createMemo(() => live() ? consumers(live(), {
|
|
21236
|
-
cropped: alreadyCropped()
|
|
21635
|
+
cropped: alreadyCropped(),
|
|
21636
|
+
limit: contextLimit()
|
|
21237
21637
|
}) : []);
|
|
21638
|
+
const consumerLines = createMemo(() => consumerRows().flatMap((c) => [{
|
|
21639
|
+
bucket: c
|
|
21640
|
+
}, ...consumerOpen().has(c.source) ? c.entries.map((e) => ({
|
|
21641
|
+
bucket: c,
|
|
21642
|
+
entry: e
|
|
21643
|
+
})) : []]));
|
|
21644
|
+
const consumerLine = () => consumerLines()[Math.min(consumerIndex(), consumerLines().length - 1)];
|
|
21645
|
+
const consumerMax = () => Math.max(1, ...consumerRows().map((c) => c.tokens));
|
|
21646
|
+
function toggleConsumer(open2) {
|
|
21647
|
+
const line = consumerLine();
|
|
21648
|
+
if (!line)
|
|
21649
|
+
return;
|
|
21650
|
+
const next = new Set(consumerOpen());
|
|
21651
|
+
if (open2)
|
|
21652
|
+
next.add(line.bucket.source);
|
|
21653
|
+
else
|
|
21654
|
+
next.delete(line.bucket.source);
|
|
21655
|
+
setConsumerOpen(next);
|
|
21656
|
+
}
|
|
21657
|
+
function markConsumerEntry() {
|
|
21658
|
+
const line = consumerLine();
|
|
21659
|
+
if (!line)
|
|
21660
|
+
return;
|
|
21661
|
+
if (!line.entry) {
|
|
21662
|
+
toggleConsumer(true);
|
|
21663
|
+
return;
|
|
21664
|
+
}
|
|
21665
|
+
const cand = line.entry.croppable ? resultCands().find((r) => r.partID === line.entry?.partID) : undefined;
|
|
21666
|
+
if (!cand)
|
|
21667
|
+
return;
|
|
21668
|
+
setCropMode("result");
|
|
21669
|
+
const next = new Set(marked());
|
|
21670
|
+
if (next.has(cand.partID))
|
|
21671
|
+
next.delete(cand.partID);
|
|
21672
|
+
else
|
|
21673
|
+
next.add(cand.partID);
|
|
21674
|
+
setMarked(next);
|
|
21675
|
+
}
|
|
21238
21676
|
function cropConsumer() {
|
|
21239
|
-
const c =
|
|
21677
|
+
const c = consumerLine()?.bucket;
|
|
21240
21678
|
setPanel("tree");
|
|
21241
21679
|
if (!c || c.kind !== "tool") {
|
|
21242
21680
|
setCropMode("result");
|
|
21243
21681
|
api2.ui.toast({
|
|
21244
|
-
message: c ? `${c.source} is not a tool result; mark rows by hand` : "nothing to crop"
|
|
21682
|
+
message: c ? c.note ?? `${c.source} is not a tool result; mark rows by hand` : "nothing to crop"
|
|
21245
21683
|
});
|
|
21246
21684
|
return;
|
|
21247
21685
|
}
|
|
@@ -21254,20 +21692,18 @@ function TreeRoute(props) {
|
|
|
21254
21692
|
}
|
|
21255
21693
|
function copySelected() {
|
|
21256
21694
|
const row = current();
|
|
21257
|
-
if (!row || row.kind === "branch")
|
|
21695
|
+
if (!row || row.kind === "branch" || row.kind === "separator")
|
|
21258
21696
|
return;
|
|
21259
21697
|
const tr = row.sessionID === sessionID ? live() : others()[row.sessionID];
|
|
21260
21698
|
const msg = tr?.messages.find((m) => m.id === row.messageID);
|
|
21261
21699
|
const text = row.kind === "step" ? String(msg?.parts.find((p) => p.id === row.partID)?.state?.output ?? msg?.parts.find((p) => p.id === row.partID)?.text ?? "") : msg?.parts.map((p) => p.text ?? "").join(`
|
|
21262
21700
|
`) ?? "";
|
|
21263
|
-
const file2 = path3.join(directory, ".opencode", "context-tree", "last-copy.txt");
|
|
21264
21701
|
try {
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
});
|
|
21268
|
-
fs4.writeFileSync(file2, text);
|
|
21702
|
+
const {
|
|
21703
|
+
hint
|
|
21704
|
+
} = copyText(api2, text, directory);
|
|
21269
21705
|
api2.ui.toast({
|
|
21270
|
-
message: `
|
|
21706
|
+
message: `copied ${text.length} chars \u2192 ${hint}`
|
|
21271
21707
|
});
|
|
21272
21708
|
} catch (e) {
|
|
21273
21709
|
api2.ui.toast({
|
|
@@ -21393,6 +21829,10 @@ function TreeRoute(props) {
|
|
|
21393
21829
|
bump();
|
|
21394
21830
|
}
|
|
21395
21831
|
}
|
|
21832
|
+
const sessionLabel = (id) => {
|
|
21833
|
+
const name = state().sessions[id]?.name;
|
|
21834
|
+
return name ? `\u2387 ${name}` : others()[id]?.title ?? api2.state.session.get(id)?.title ?? id;
|
|
21835
|
+
};
|
|
21396
21836
|
async function jump() {
|
|
21397
21837
|
const row = current();
|
|
21398
21838
|
if (!row || !sessionID)
|
|
@@ -21415,16 +21855,19 @@ function TreeRoute(props) {
|
|
|
21415
21855
|
});
|
|
21416
21856
|
return;
|
|
21417
21857
|
}
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
}
|
|
21858
|
+
const from = sessionLabel(plan.sessionID);
|
|
21859
|
+
const ok = await confirm(plan.kind === "switch" ? `Switch to ${from}?` : plan.mode === "redo" ? "Fork & prefill this turn?" : "Fork after this step?", plan.kind === "switch" ? `The session you are on now stays exactly as it is. ${UNDO_KEY} undoes this.` : `A new OpenCode session forks from ${from} at this point; nothing is deleted. ${UNDO_KEY} undoes this.`);
|
|
21860
|
+
if (!ok)
|
|
21861
|
+
return;
|
|
21423
21862
|
const summary = await askSummary();
|
|
21424
|
-
await executeJump(ctx, plan, {
|
|
21863
|
+
const target2 = await executeJump(ctx, plan, {
|
|
21425
21864
|
currentSessionID: sessionID,
|
|
21426
21865
|
summary
|
|
21427
21866
|
});
|
|
21867
|
+
if (target2)
|
|
21868
|
+
api2.ui.toast({
|
|
21869
|
+
message: `moved to ${sessionLabel(target2)} \xB7 ${UNDO_KEY} undoes it`
|
|
21870
|
+
});
|
|
21428
21871
|
});
|
|
21429
21872
|
}
|
|
21430
21873
|
async function branch() {
|
|
@@ -21477,7 +21920,7 @@ function TreeRoute(props) {
|
|
|
21477
21920
|
}
|
|
21478
21921
|
async function label() {
|
|
21479
21922
|
const row = current();
|
|
21480
|
-
if (!row || row.kind === "branch")
|
|
21923
|
+
if (!row || row.kind === "branch" || row.kind === "separator")
|
|
21481
21924
|
return;
|
|
21482
21925
|
const st = state();
|
|
21483
21926
|
const existing = st.labels[row.messageID]?.label;
|
|
@@ -21495,7 +21938,7 @@ function TreeRoute(props) {
|
|
|
21495
21938
|
const row = current();
|
|
21496
21939
|
if (!row)
|
|
21497
21940
|
return;
|
|
21498
|
-
const target2 = row.kind === "
|
|
21941
|
+
const target2 = row.kind === "separator" ? undefined : row.kind === "branch" || row.depth > 0 ? row.sessionID : undefined;
|
|
21499
21942
|
if (!target2)
|
|
21500
21943
|
return;
|
|
21501
21944
|
const shown = row.kind === "branch" ? row.expanded : true;
|
|
@@ -21529,14 +21972,18 @@ function TreeRoute(props) {
|
|
|
21529
21972
|
return;
|
|
21530
21973
|
}
|
|
21531
21974
|
const siblings = Object.values(state().sessions).filter((x) => x.parentSessionID === b.parentSessionID && x.sessionID !== sessionID && x.status === "open").length;
|
|
21532
|
-
const
|
|
21533
|
-
|
|
21975
|
+
const parent = others()[b.parentSessionID];
|
|
21976
|
+
const target2 = mergeTargetOf(b.parentSessionID === state().root ? TRUNK_LABEL : state().sessions[b.parentSessionID]?.name ?? TRUNK_LABEL, parent?.messages ?? []);
|
|
21977
|
+
const turns = ownTurnCount(live()?.messages ?? [], {
|
|
21978
|
+
messageID: b.anchorMessageID,
|
|
21979
|
+
parentMessageIDs: parent?.messages.map((m) => m.id) ?? []
|
|
21980
|
+
});
|
|
21981
|
+
const mode = await select(mergeDialogTitle(b.name ?? "branch", target2), mergeDialogOptions({
|
|
21982
|
+
siblings,
|
|
21983
|
+
turns
|
|
21534
21984
|
}));
|
|
21535
21985
|
if (!mode)
|
|
21536
21986
|
return;
|
|
21537
|
-
let note;
|
|
21538
|
-
if (mode === "discard")
|
|
21539
|
-
note = await prompt("Why? (optional note on the close marker)", "dead end") ?? undefined;
|
|
21540
21987
|
const inApp = !hasEditor() ? async (draft) => {
|
|
21541
21988
|
const ok = await confirm("Accept the drafted record as-is?", `${draft.slice(0, 400)}${draft.length > 400 ? "\u2026" : ""}
|
|
21542
21989
|
|
|
@@ -21549,7 +21996,6 @@ ${MERGE_TRUST}
|
|
|
21549
21996
|
await mergeBranch(ctx, {
|
|
21550
21997
|
sessionID,
|
|
21551
21998
|
mode,
|
|
21552
|
-
note,
|
|
21553
21999
|
confirm: inApp
|
|
21554
22000
|
});
|
|
21555
22001
|
});
|
|
@@ -21561,9 +22007,9 @@ ${MERGE_TRUST}
|
|
|
21561
22007
|
sessionID: d.sessionID,
|
|
21562
22008
|
at: d.recordedAt
|
|
21563
22009
|
}));
|
|
21564
|
-
const file2 =
|
|
22010
|
+
const file2 = path4.join(directory, "ctree-decisions.md");
|
|
21565
22011
|
try {
|
|
21566
|
-
|
|
22012
|
+
fs5.writeFileSync(file2, exportDecisions(records));
|
|
21567
22013
|
api2.ui.toast({
|
|
21568
22014
|
variant: "success",
|
|
21569
22015
|
message: `wrote ${records.length} record${records.length === 1 ? "" : "s"} \u2192 ${file2}`
|
|
@@ -21579,7 +22025,7 @@ ${MERGE_TRUST}
|
|
|
21579
22025
|
const d = decisions()[decisionIndex()];
|
|
21580
22026
|
if (!d)
|
|
21581
22027
|
return;
|
|
21582
|
-
const idx = view().rows.findIndex((r) => r.kind
|
|
22028
|
+
const idx = view().rows.findIndex((r) => (r.kind === "turn" || r.kind === "step") && r.messageID === d.messageID);
|
|
21583
22029
|
setPanel("tree");
|
|
21584
22030
|
if (idx >= 0)
|
|
21585
22031
|
setSelected(idx);
|
|
@@ -21588,125 +22034,251 @@ ${MERGE_TRUST}
|
|
|
21588
22034
|
message: "that record lives in another session"
|
|
21589
22035
|
});
|
|
21590
22036
|
}
|
|
22037
|
+
const treePanel = () => panel() === "tree";
|
|
22038
|
+
const inCrop = () => cropMode() !== undefined;
|
|
22039
|
+
const treeIdle = () => treePanel() && !inCrop();
|
|
22040
|
+
const listPanel = () => treePanel() || panel() === "consumers";
|
|
22041
|
+
function setFilterTo(next) {
|
|
22042
|
+
setFilter(next);
|
|
22043
|
+
api2.kv.set("ctree.filter", next);
|
|
22044
|
+
}
|
|
22045
|
+
async function pickFilter() {
|
|
22046
|
+
const next = await select("Filter rows", FILTERS.map((f) => ({
|
|
22047
|
+
title: `${f.value === filter() ? "\u25CF" : " "} ${f.title}`,
|
|
22048
|
+
value: f.value,
|
|
22049
|
+
description: f.description
|
|
22050
|
+
})));
|
|
22051
|
+
if (next)
|
|
22052
|
+
setFilterTo(next);
|
|
22053
|
+
}
|
|
22054
|
+
function moveIndex(delta) {
|
|
22055
|
+
if (panel() === "decisions") {
|
|
22056
|
+
setDecisionIndex((i) => Math.min(Math.max(0, decisions().length - 1), Math.max(0, i + delta)));
|
|
22057
|
+
setDecisionScroll(0);
|
|
22058
|
+
return;
|
|
22059
|
+
}
|
|
22060
|
+
if (panel() === "consumers") {
|
|
22061
|
+
setConsumerIndex((i) => Math.min(Math.max(0, consumerLines().length - 1), Math.max(0, i + delta)));
|
|
22062
|
+
return;
|
|
22063
|
+
}
|
|
22064
|
+
setSelected((i) => moveSelection(view().rows, i, delta));
|
|
22065
|
+
}
|
|
22066
|
+
function halfPage(dir) {
|
|
22067
|
+
const half = Math.max(1, Math.floor(height() / 2));
|
|
22068
|
+
if (panel() === "decisions")
|
|
22069
|
+
setDecisionScroll((s) => Math.max(0, s + dir * half));
|
|
22070
|
+
else
|
|
22071
|
+
moveIndex(dir * half);
|
|
22072
|
+
}
|
|
22073
|
+
function gotoEdge(dir) {
|
|
22074
|
+
if (panel() === "decisions") {
|
|
22075
|
+
setDecisionIndex(dir === -1 ? 0 : Math.max(0, decisions().length - 1));
|
|
22076
|
+
setDecisionScroll(0);
|
|
22077
|
+
return;
|
|
22078
|
+
}
|
|
22079
|
+
if (panel() === "consumers") {
|
|
22080
|
+
setConsumerIndex(dir === -1 ? 0 : Math.max(0, consumerLines().length - 1));
|
|
22081
|
+
return;
|
|
22082
|
+
}
|
|
22083
|
+
const i = dir === -1 ? firstIndex(view().rows) : lastIndex(view().rows);
|
|
22084
|
+
if (i >= 0)
|
|
22085
|
+
setSelected(i);
|
|
22086
|
+
}
|
|
22087
|
+
const matchIn = (line) => {
|
|
22088
|
+
const q = search().trim().toLowerCase();
|
|
22089
|
+
return q ? line.toLowerCase().indexOf(q) : -1;
|
|
22090
|
+
};
|
|
22091
|
+
function moveMatch(dir) {
|
|
22092
|
+
const rows = view().rows;
|
|
22093
|
+
const q = search().trim();
|
|
22094
|
+
if (rows.length === 0 || !q)
|
|
22095
|
+
return;
|
|
22096
|
+
for (let step = 1;step <= rows.length; step++) {
|
|
22097
|
+
const i = ((selected() + dir * step) % rows.length + rows.length) % rows.length;
|
|
22098
|
+
if (rows[i].kind !== "separator" && matchIn(rowLine(rows[i], rowWidth(), false)) >= 0) {
|
|
22099
|
+
setSelected(i);
|
|
22100
|
+
return;
|
|
22101
|
+
}
|
|
22102
|
+
}
|
|
22103
|
+
api2.ui.toast({
|
|
22104
|
+
message: `no other row matches "${q}"`
|
|
22105
|
+
});
|
|
22106
|
+
}
|
|
22107
|
+
function enterSearch() {
|
|
22108
|
+
searchBefore = search();
|
|
22109
|
+
setSearchMode(true);
|
|
22110
|
+
}
|
|
22111
|
+
function exitSearch(commit) {
|
|
22112
|
+
if (!commit)
|
|
22113
|
+
setSearch(searchBefore);
|
|
22114
|
+
setSearchMode(false);
|
|
22115
|
+
}
|
|
22116
|
+
const stopTyping = api2.keymap.intercept("key", (input2) => {
|
|
22117
|
+
if (!searchMode())
|
|
22118
|
+
return;
|
|
22119
|
+
const ev = input2.event;
|
|
22120
|
+
if (ev.eventType === "release" || ev.ctrl || ev.meta)
|
|
22121
|
+
return;
|
|
22122
|
+
const take = () => input2.consume({
|
|
22123
|
+
preventDefault: true,
|
|
22124
|
+
stopPropagation: true
|
|
22125
|
+
});
|
|
22126
|
+
if (ev.name === "escape")
|
|
22127
|
+
return void (exitSearch(false), take());
|
|
22128
|
+
if (ev.name === "return" || ev.name === "enter")
|
|
22129
|
+
return void (exitSearch(true), take());
|
|
22130
|
+
if (ev.name === "backspace")
|
|
22131
|
+
return void (setSearch((q) => q.slice(0, -1)), take());
|
|
22132
|
+
const char = ev.sequence?.length === 1 && ev.sequence >= " " && ev.sequence !== "\x7F" ? ev.sequence : ev.name.length === 1 ? ev.name : undefined;
|
|
22133
|
+
if (char === undefined)
|
|
22134
|
+
return;
|
|
22135
|
+
setSearch((q) => q + char);
|
|
22136
|
+
take();
|
|
22137
|
+
});
|
|
22138
|
+
onCleanup(() => stopTyping());
|
|
21591
22139
|
const off = api2.keymap.registerLayer({
|
|
21592
22140
|
mode: "base",
|
|
21593
22141
|
commands: [{
|
|
21594
22142
|
name: "ctree.up",
|
|
21595
22143
|
hidden: true,
|
|
21596
|
-
run: () =>
|
|
22144
|
+
run: () => moveIndex(-1)
|
|
21597
22145
|
}, {
|
|
21598
22146
|
name: "ctree.down",
|
|
21599
22147
|
hidden: true,
|
|
21600
|
-
run: () =>
|
|
22148
|
+
run: () => moveIndex(1)
|
|
21601
22149
|
}, {
|
|
21602
22150
|
name: "ctree.jump_up",
|
|
21603
22151
|
hidden: true,
|
|
21604
|
-
|
|
22152
|
+
enabled: treePanel,
|
|
22153
|
+
run: () => moveIndex(-20)
|
|
21605
22154
|
}, {
|
|
21606
22155
|
name: "ctree.jump_down",
|
|
21607
22156
|
hidden: true,
|
|
21608
|
-
|
|
22157
|
+
enabled: treePanel,
|
|
22158
|
+
run: () => moveIndex(20)
|
|
22159
|
+
}, {
|
|
22160
|
+
name: "ctree.half_up",
|
|
22161
|
+
hidden: true,
|
|
22162
|
+
run: () => halfPage(-1)
|
|
22163
|
+
}, {
|
|
22164
|
+
name: "ctree.half_down",
|
|
22165
|
+
hidden: true,
|
|
22166
|
+
run: () => halfPage(1)
|
|
21609
22167
|
}, {
|
|
21610
22168
|
name: "ctree.first",
|
|
21611
22169
|
hidden: true,
|
|
21612
|
-
run: () =>
|
|
22170
|
+
run: () => gotoEdge(-1)
|
|
21613
22171
|
}, {
|
|
21614
22172
|
name: "ctree.last",
|
|
21615
22173
|
hidden: true,
|
|
21616
|
-
run: () =>
|
|
22174
|
+
run: () => gotoEdge(1)
|
|
21617
22175
|
}, {
|
|
21618
22176
|
name: "ctree.prev_branch",
|
|
21619
22177
|
hidden: true,
|
|
22178
|
+
enabled: treePanel,
|
|
21620
22179
|
run: () => setSelected((i) => nextBranchIndex(view().rows, i, -1))
|
|
21621
22180
|
}, {
|
|
21622
22181
|
name: "ctree.next_branch",
|
|
21623
22182
|
hidden: true,
|
|
22183
|
+
enabled: treePanel,
|
|
21624
22184
|
run: () => setSelected((i) => nextBranchIndex(view().rows, i, 1))
|
|
21625
22185
|
}, {
|
|
21626
22186
|
name: "ctree.fold",
|
|
21627
22187
|
hidden: true,
|
|
21628
|
-
|
|
22188
|
+
enabled: listPanel,
|
|
22189
|
+
run: () => panel() === "consumers" ? toggleConsumer(false) : foldOrUnfold(false)
|
|
21629
22190
|
}, {
|
|
21630
22191
|
name: "ctree.unfold",
|
|
21631
22192
|
hidden: true,
|
|
21632
|
-
|
|
22193
|
+
enabled: listPanel,
|
|
22194
|
+
run: () => panel() === "consumers" ? toggleConsumer(true) : foldOrUnfold(true)
|
|
21633
22195
|
}, {
|
|
21634
22196
|
name: "ctree.toggle",
|
|
21635
22197
|
hidden: true,
|
|
22198
|
+
enabled: treePanel,
|
|
21636
22199
|
run: () => foldOrUnfold(!(current()?.kind === "branch" && current().expanded))
|
|
21637
22200
|
}, {
|
|
21638
22201
|
name: "ctree.go",
|
|
21639
22202
|
hidden: true,
|
|
21640
|
-
run: () => void (panel() === "decisions" ? jumpToDecision() : panel() === "consumers" ?
|
|
22203
|
+
run: () => void (panel() === "decisions" ? jumpToDecision() : panel() === "consumers" ? toggleConsumer(!consumerOpen().has(consumerLine()?.bucket.source ?? "")) : cropMode() ? applyMarked() : jump())
|
|
21641
22204
|
}, {
|
|
21642
22205
|
name: "ctree.branch",
|
|
21643
22206
|
hidden: true,
|
|
22207
|
+
enabled: treeIdle,
|
|
21644
22208
|
run: () => void branch()
|
|
21645
22209
|
}, {
|
|
21646
22210
|
name: "ctree.label",
|
|
21647
22211
|
hidden: true,
|
|
22212
|
+
enabled: treeIdle,
|
|
21648
22213
|
run: () => void label()
|
|
21649
22214
|
}, {
|
|
21650
|
-
name: "ctree.
|
|
22215
|
+
name: "ctree.filter_pick",
|
|
21651
22216
|
hidden: true,
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
|
|
22217
|
+
enabled: () => !inCrop(),
|
|
22218
|
+
run: () => void pickFilter()
|
|
22219
|
+
}, {
|
|
22220
|
+
name: "ctree.filter_prev",
|
|
22221
|
+
hidden: true,
|
|
22222
|
+
enabled: () => !inCrop(),
|
|
22223
|
+
run: () => setFilterTo(FILTERS[(FILTERS.findIndex((f) => f.value === filter()) - 1 + FILTERS.length) % FILTERS.length].value)
|
|
21657
22224
|
}, {
|
|
21658
22225
|
name: "ctree.search",
|
|
21659
22226
|
hidden: true,
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
22227
|
+
enabled: treeIdle,
|
|
22228
|
+
run: () => enterSearch()
|
|
22229
|
+
}, {
|
|
22230
|
+
name: "ctree.search_next",
|
|
22231
|
+
hidden: true,
|
|
22232
|
+
enabled: treePanel,
|
|
22233
|
+
run: () => moveMatch(1)
|
|
22234
|
+
}, {
|
|
22235
|
+
name: "ctree.search_prev",
|
|
22236
|
+
hidden: true,
|
|
22237
|
+
enabled: treePanel,
|
|
22238
|
+
run: () => moveMatch(-1)
|
|
21664
22239
|
}, {
|
|
21665
22240
|
name: "ctree.crop",
|
|
21666
22241
|
hidden: true,
|
|
22242
|
+
enabled: listPanel,
|
|
21667
22243
|
run: () => {
|
|
21668
22244
|
if (panel() === "consumers") {
|
|
21669
22245
|
cropConsumer();
|
|
21670
22246
|
return;
|
|
21671
22247
|
}
|
|
21672
|
-
if (
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
setCropMode(undefined);
|
|
21676
|
-
setMarked(new Set);
|
|
21677
|
-
} else
|
|
22248
|
+
if (cropMode())
|
|
22249
|
+
leaveCropMode();
|
|
22250
|
+
else
|
|
21678
22251
|
setCropMode("result");
|
|
21679
22252
|
}
|
|
21680
22253
|
}, {
|
|
21681
22254
|
name: "ctree.crop_toggle_mode",
|
|
21682
22255
|
hidden: true,
|
|
21683
|
-
|
|
21684
|
-
|
|
21685
|
-
return;
|
|
21686
|
-
setCropMode(cropMode() === "result" ? "turn" : "result");
|
|
21687
|
-
setMarked(new Set);
|
|
21688
|
-
}
|
|
22256
|
+
enabled: inCrop,
|
|
22257
|
+
run: () => setCropMode(cropMode() === "result" ? "turn" : "result")
|
|
21689
22258
|
}, {
|
|
21690
22259
|
name: "ctree.mark",
|
|
21691
22260
|
hidden: true,
|
|
21692
|
-
enabled:
|
|
21693
|
-
run: () => toggleMark()
|
|
22261
|
+
enabled: listPanel,
|
|
22262
|
+
run: () => panel() === "consumers" ? markConsumerEntry() : toggleMark()
|
|
21694
22263
|
}, {
|
|
21695
22264
|
name: "ctree.auto",
|
|
21696
22265
|
hidden: true,
|
|
21697
|
-
enabled:
|
|
22266
|
+
enabled: inCrop,
|
|
21698
22267
|
run: () => autoMarkAll()
|
|
21699
22268
|
}, {
|
|
21700
22269
|
name: "ctree.undo",
|
|
21701
22270
|
hidden: true,
|
|
22271
|
+
enabled: treeIdle,
|
|
21702
22272
|
run: () => void undo()
|
|
21703
22273
|
}, {
|
|
21704
22274
|
name: "ctree.merge",
|
|
21705
22275
|
hidden: true,
|
|
22276
|
+
enabled: treeIdle,
|
|
21706
22277
|
run: () => void merge2()
|
|
21707
22278
|
}, {
|
|
21708
22279
|
name: "ctree.inspector",
|
|
21709
22280
|
hidden: true,
|
|
22281
|
+
enabled: () => !inCrop(),
|
|
21710
22282
|
run: () => {
|
|
21711
22283
|
setInspector(!inspector());
|
|
21712
22284
|
api2.kv.set("ctree.inspector", inspector());
|
|
@@ -21714,26 +22286,32 @@ ${MERGE_TRUST}
|
|
|
21714
22286
|
}, {
|
|
21715
22287
|
name: "ctree.consumers",
|
|
21716
22288
|
hidden: true,
|
|
22289
|
+
enabled: () => !inCrop(),
|
|
21717
22290
|
run: () => setPanel(panel() === "consumers" ? "tree" : "consumers")
|
|
21718
22291
|
}, {
|
|
21719
22292
|
name: "ctree.copy",
|
|
21720
22293
|
hidden: true,
|
|
22294
|
+
enabled: treeIdle,
|
|
21721
22295
|
run: () => copySelected()
|
|
21722
22296
|
}, {
|
|
21723
22297
|
name: "ctree.mode_duration",
|
|
21724
22298
|
hidden: true,
|
|
22299
|
+
enabled: treePanel,
|
|
21725
22300
|
run: () => setLane("duration")
|
|
21726
22301
|
}, {
|
|
21727
22302
|
name: "ctree.mode_turns",
|
|
21728
22303
|
hidden: true,
|
|
22304
|
+
enabled: treePanel,
|
|
21729
22305
|
run: () => setLane("turns")
|
|
21730
22306
|
}, {
|
|
21731
22307
|
name: "ctree.mode_calls",
|
|
21732
22308
|
hidden: true,
|
|
22309
|
+
enabled: treePanel,
|
|
21733
22310
|
run: () => setLane("calls")
|
|
21734
22311
|
}, {
|
|
21735
22312
|
name: "ctree.lanes_off",
|
|
21736
22313
|
hidden: true,
|
|
22314
|
+
enabled: treePanel,
|
|
21737
22315
|
run: () => {
|
|
21738
22316
|
setLanesOn(false);
|
|
21739
22317
|
api2.kv.set("ctree.lanesOn", false);
|
|
@@ -21741,6 +22319,7 @@ ${MERGE_TRUST}
|
|
|
21741
22319
|
}, {
|
|
21742
22320
|
name: "ctree.decisions",
|
|
21743
22321
|
hidden: true,
|
|
22322
|
+
enabled: () => !inCrop(),
|
|
21744
22323
|
run: () => setPanel(panel() === "decisions" ? "tree" : "decisions")
|
|
21745
22324
|
}, {
|
|
21746
22325
|
name: "ctree.export",
|
|
@@ -21760,8 +22339,11 @@ ${MERGE_TRUST}
|
|
|
21760
22339
|
return;
|
|
21761
22340
|
}
|
|
21762
22341
|
if (cropMode()) {
|
|
21763
|
-
|
|
21764
|
-
|
|
22342
|
+
leaveCropMode();
|
|
22343
|
+
return;
|
|
22344
|
+
}
|
|
22345
|
+
if (search()) {
|
|
22346
|
+
setSearch("");
|
|
21765
22347
|
return;
|
|
21766
22348
|
}
|
|
21767
22349
|
back();
|
|
@@ -21775,18 +22357,64 @@ ${MERGE_TRUST}
|
|
|
21775
22357
|
const root = state().root;
|
|
21776
22358
|
return (root && root !== sessionID ? others()[root]?.title : undefined) ?? sessionTitle();
|
|
21777
22359
|
};
|
|
21778
|
-
const
|
|
22360
|
+
const modeTag = () => cropMode() ? " \xB7 crop mode" : searchMode() ? " \xB7 search" : "";
|
|
22361
|
+
const headLine = () => {
|
|
21779
22362
|
const b = branchOfCurrent();
|
|
21780
|
-
|
|
21781
|
-
|
|
21782
|
-
|
|
22363
|
+
const lead = "\u250C Context tree \xB7 ";
|
|
22364
|
+
const where = b ? `\u2387 ${clip(b.name ?? sessionTitle(), 28)}${b.status === "open" ? "" : ` (${b.status})`} \u2190 ` : "";
|
|
22365
|
+
const tail = b ? "" : " \xB7 trunk";
|
|
22366
|
+
const room = cols() - 4 - formatContext(contextSize(), contextLimit()).length - modeTag().length - lead.length - where.length - tail.length - 3;
|
|
22367
|
+
return `${lead}${where}${clip(title(), Math.max(8, room))}${tail}${modeTag()} `;
|
|
22368
|
+
};
|
|
22369
|
+
const statusLine = () => {
|
|
22370
|
+
const n = view().rows.length;
|
|
22371
|
+
const pos = `${n ? Math.min(selected() + 1, n) : 0}/${n}`;
|
|
22372
|
+
if (cropMode()) {
|
|
22373
|
+
const a = armed();
|
|
22374
|
+
return `\u2702 crop mode (${cropMode()}) \xB7 space mark \xB7 a auto \xB7 t result\u21C4turn \xB7 \u23CE apply \xB7 esc leave \xB7 marked ${selectedCandidates().length} ~${formatK(reclaimed(selectedCandidates()))}${a ? " \xB7 armed \u2014 space again to override" : ""}`;
|
|
22375
|
+
}
|
|
22376
|
+
if (searchMode())
|
|
22377
|
+
return `search: ${search()}\u258F \xB7 ${pos} rows \xB7 \u23CE keeps it \xB7 esc clears`;
|
|
22378
|
+
return `filter: ${filter()}${search() ? ` search: "${search()}"` : ""}${busy() ? ` \u2026 ${busy()}` : ""} ${pos} rows`;
|
|
22379
|
+
};
|
|
22380
|
+
const goVerb = () => {
|
|
22381
|
+
const row = current();
|
|
22382
|
+
if (!row)
|
|
22383
|
+
return "\u23CE go";
|
|
22384
|
+
if (row.kind === "branch")
|
|
22385
|
+
return row.isCurrent ? "\u23CE you are here" : `\u23CE switch to \u2387 ${clip(row.name, 20)}`;
|
|
22386
|
+
if (row.kind === "separator")
|
|
22387
|
+
return "\u23CE go";
|
|
22388
|
+
if (row.id === view().currentRowId)
|
|
22389
|
+
return "\u23CE you are here";
|
|
22390
|
+
return row.kind === "turn" ? "\u23CE fork & prefill this turn" : "\u23CE fork after this step";
|
|
22391
|
+
};
|
|
22392
|
+
const footer = () => {
|
|
22393
|
+
if (cropMode())
|
|
22394
|
+
return "space mark a auto t result\u21C4turn \u23CE apply esc leave";
|
|
22395
|
+
if (panel() === "decisions")
|
|
22396
|
+
return "\u23CE jump to record E export q back";
|
|
22397
|
+
if (panel() === "consumers")
|
|
22398
|
+
return "\u23CE expand space mark c crop q back";
|
|
22399
|
+
if (panel() === "help")
|
|
22400
|
+
return "esc/q back";
|
|
22401
|
+
return `${goVerb()} b branch m merge c crop ${UNDO_KEY} undo s consumers ? help q back`;
|
|
22402
|
+
};
|
|
22403
|
+
const showsTree = () => panel() === "tree" || panel() === "help";
|
|
22404
|
+
const emptyText = () => {
|
|
22405
|
+
const q = search().trim();
|
|
22406
|
+
if (q)
|
|
22407
|
+
return `no rows match "${q}" \xB7 esc clears`;
|
|
22408
|
+
if (filter() !== "default")
|
|
22409
|
+
return `no rows match filter: ${filter()} \xB7 f changes it`;
|
|
22410
|
+
return "(no messages yet \u2014 chat first, then open the tree)";
|
|
21783
22411
|
};
|
|
21784
22412
|
return (() => {
|
|
21785
|
-
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("text"), _el$
|
|
22413
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("text"), _el$16 = _$createElement("text"), _el$17 = _$createTextNode(`\u2502 `), _el$28 = _$createElement("box"), _el$29 = _$createElement("box"), _el$35 = _$createElement("text"), _el$36 = _$createTextNode(`\u2514 `);
|
|
21786
22414
|
_$insertNode(_el$, _el$2);
|
|
21787
|
-
_$insertNode(_el$, _el$
|
|
21788
|
-
_$insertNode(_el$, _el$
|
|
21789
|
-
_$insertNode(_el$, _el$
|
|
22415
|
+
_$insertNode(_el$, _el$16);
|
|
22416
|
+
_$insertNode(_el$, _el$28);
|
|
22417
|
+
_$insertNode(_el$, _el$35);
|
|
21790
22418
|
_$setProp(_el$, "flexDirection", "column");
|
|
21791
22419
|
_$setProp(_el$, "padding", 1);
|
|
21792
22420
|
_$setProp(_el$, "width", "100%");
|
|
@@ -21794,7 +22422,7 @@ ${MERGE_TRUST}
|
|
|
21794
22422
|
_$insertNode(_el$2, _el$3);
|
|
21795
22423
|
_$insertNode(_el$2, _el$4);
|
|
21796
22424
|
_$setProp(_el$2, "flexDirection", "row");
|
|
21797
|
-
_$insert(_el$3,
|
|
22425
|
+
_$insert(_el$3, headLine);
|
|
21798
22426
|
_$insert(_el$4, () => formatContext(contextSize(), contextLimit()));
|
|
21799
22427
|
_$insert(_el$, _$createComponent(Show, {
|
|
21800
22428
|
get when() {
|
|
@@ -21802,16 +22430,55 @@ ${MERGE_TRUST}
|
|
|
21802
22430
|
},
|
|
21803
22431
|
get children() {
|
|
21804
22432
|
return [(() => {
|
|
21805
|
-
var _el$5 = _$createElement("
|
|
22433
|
+
var _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$7 = _$createElement("text");
|
|
21806
22434
|
_$insertNode(_el$5, _el$6);
|
|
21807
22435
|
_$insertNode(_el$5, _el$7);
|
|
21808
|
-
_$
|
|
21809
|
-
_$
|
|
21810
|
-
_$insert(_el$5, (
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21814
|
-
|
|
22436
|
+
_$setProp(_el$5, "flexDirection", "row");
|
|
22437
|
+
_$insert(_el$6, () => `\u2502 Input ${strip().truncatedLeft > 0 ? `\u2026${strip().truncatedLeft}` : ""}`);
|
|
22438
|
+
_$insert(_el$5, _$createComponent(Show, {
|
|
22439
|
+
get when() {
|
|
22440
|
+
return !strip().empty.input;
|
|
22441
|
+
},
|
|
22442
|
+
get fallback() {
|
|
22443
|
+
return (() => {
|
|
22444
|
+
var _el$37 = _$createElement("text");
|
|
22445
|
+
_$insert(_el$37, () => "no input".padEnd(laneWidth()));
|
|
22446
|
+
_$effect((_$p) => _$setProp(_el$37, "fg", t.textMuted, _$p));
|
|
22447
|
+
return _el$37;
|
|
22448
|
+
})();
|
|
22449
|
+
},
|
|
22450
|
+
get children() {
|
|
22451
|
+
return _$createComponent(For, {
|
|
22452
|
+
get each() {
|
|
22453
|
+
return inputRuns();
|
|
22454
|
+
},
|
|
22455
|
+
children: (r) => (() => {
|
|
22456
|
+
var _el$38 = _$createElement("text");
|
|
22457
|
+
_$insert(_el$38, () => r.text);
|
|
22458
|
+
_$effect((_p$) => {
|
|
22459
|
+
var { fg: _v$0, bg: _v$1 } = r;
|
|
22460
|
+
_v$0 !== _p$.e && (_p$.e = _$setProp(_el$38, "fg", _v$0, _p$.e));
|
|
22461
|
+
_v$1 !== _p$.t && (_p$.t = _$setProp(_el$38, "bg", _v$1, _p$.t));
|
|
22462
|
+
return _p$;
|
|
22463
|
+
}, {
|
|
22464
|
+
e: undefined,
|
|
22465
|
+
t: undefined
|
|
22466
|
+
});
|
|
22467
|
+
return _el$38;
|
|
22468
|
+
})()
|
|
22469
|
+
});
|
|
22470
|
+
}
|
|
22471
|
+
}), _el$7);
|
|
22472
|
+
_$insert(_el$7, () => ` ${laneMode() === "duration" ? "[1] Duration" : " 1 duration"} \xB7 ${laneMode() === "turns" ? "[2] Turns" : " 2 turns"} \xB7 ${laneMode() === "calls" ? "[3] Calls" : " 3 calls"} \xB7 0 off`);
|
|
22473
|
+
_$effect((_p$) => {
|
|
22474
|
+
var { textMuted: _v$, textMuted: _v$2 } = t;
|
|
22475
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$, _p$.e));
|
|
22476
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "fg", _v$2, _p$.t));
|
|
22477
|
+
return _p$;
|
|
22478
|
+
}, {
|
|
22479
|
+
e: undefined,
|
|
22480
|
+
t: undefined
|
|
22481
|
+
});
|
|
21815
22482
|
return _el$5;
|
|
21816
22483
|
})(), _$createComponent(Show, {
|
|
21817
22484
|
get when() {
|
|
@@ -21819,92 +22486,136 @@ ${MERGE_TRUST}
|
|
|
21819
22486
|
},
|
|
21820
22487
|
get children() {
|
|
21821
22488
|
return [(() => {
|
|
21822
|
-
var _el$
|
|
21823
|
-
_$insertNode(_el$
|
|
21824
|
-
_$
|
|
21825
|
-
_$
|
|
21826
|
-
|
|
22489
|
+
var _el$8 = _$createElement("box"), _el$9 = _$createElement("text");
|
|
22490
|
+
_$insertNode(_el$8, _el$9);
|
|
22491
|
+
_$setProp(_el$8, "flexDirection", "row");
|
|
22492
|
+
_$insertNode(_el$9, _$createTextNode(`\u2502 Model `));
|
|
22493
|
+
_$insert(_el$8, _$createComponent(Show, {
|
|
22494
|
+
get when() {
|
|
22495
|
+
return !strip().empty.model;
|
|
22496
|
+
},
|
|
22497
|
+
get fallback() {
|
|
22498
|
+
return (() => {
|
|
22499
|
+
var _el$39 = _$createElement("text");
|
|
22500
|
+
_$insert(_el$39, () => "no model steps".padEnd(laneWidth()));
|
|
22501
|
+
_$effect((_$p) => _$setProp(_el$39, "fg", t.textMuted, _$p));
|
|
22502
|
+
return _el$39;
|
|
22503
|
+
})();
|
|
22504
|
+
},
|
|
22505
|
+
get children() {
|
|
22506
|
+
return _$createComponent(For, {
|
|
22507
|
+
get each() {
|
|
22508
|
+
return modelRuns();
|
|
22509
|
+
},
|
|
22510
|
+
children: (r) => (() => {
|
|
22511
|
+
var _el$40 = _$createElement("text");
|
|
22512
|
+
_$insert(_el$40, () => r.text);
|
|
22513
|
+
_$effect((_p$) => {
|
|
22514
|
+
var { fg: _v$10, bg: _v$11 } = r;
|
|
22515
|
+
_v$10 !== _p$.e && (_p$.e = _$setProp(_el$40, "fg", _v$10, _p$.e));
|
|
22516
|
+
_v$11 !== _p$.t && (_p$.t = _$setProp(_el$40, "bg", _v$11, _p$.t));
|
|
22517
|
+
return _p$;
|
|
22518
|
+
}, {
|
|
22519
|
+
e: undefined,
|
|
22520
|
+
t: undefined
|
|
22521
|
+
});
|
|
22522
|
+
return _el$40;
|
|
22523
|
+
})()
|
|
22524
|
+
});
|
|
22525
|
+
}
|
|
22526
|
+
}), null);
|
|
22527
|
+
_$effect((_$p) => _$setProp(_el$9, "fg", t.textMuted, _$p));
|
|
22528
|
+
return _el$8;
|
|
21827
22529
|
})(), (() => {
|
|
21828
|
-
var _el$
|
|
21829
|
-
_$insertNode(_el$
|
|
21830
|
-
_$insertNode(_el$
|
|
21831
|
-
_$setProp(_el$
|
|
21832
|
-
_$insertNode(_el$
|
|
21833
|
-
_$insert(_el$
|
|
21834
|
-
get
|
|
21835
|
-
return
|
|
22530
|
+
var _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$12 = _$createElement("text");
|
|
22531
|
+
_$insertNode(_el$1, _el$10);
|
|
22532
|
+
_$insertNode(_el$1, _el$12);
|
|
22533
|
+
_$setProp(_el$1, "flexDirection", "row");
|
|
22534
|
+
_$insertNode(_el$10, _$createTextNode(`\u2502 Tools `));
|
|
22535
|
+
_$insert(_el$1, _$createComponent(Show, {
|
|
22536
|
+
get when() {
|
|
22537
|
+
return !strip().empty.tools;
|
|
21836
22538
|
},
|
|
21837
|
-
|
|
21838
|
-
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
|
|
22539
|
+
get fallback() {
|
|
22540
|
+
return (() => {
|
|
22541
|
+
var _el$41 = _$createElement("text");
|
|
22542
|
+
_$insert(_el$41, () => "no tool calls".padEnd(laneWidth()));
|
|
22543
|
+
_$effect((_$p) => _$setProp(_el$41, "fg", t.textMuted, _$p));
|
|
22544
|
+
return _el$41;
|
|
22545
|
+
})();
|
|
22546
|
+
},
|
|
22547
|
+
get children() {
|
|
22548
|
+
return _$createComponent(For, {
|
|
22549
|
+
get each() {
|
|
22550
|
+
return toolRuns();
|
|
22551
|
+
},
|
|
22552
|
+
children: (r) => (() => {
|
|
22553
|
+
var _el$42 = _$createElement("text");
|
|
22554
|
+
_$insert(_el$42, () => r.text);
|
|
22555
|
+
_$effect((_p$) => {
|
|
22556
|
+
var { fg: _v$12, bg: _v$13 } = r;
|
|
22557
|
+
_v$12 !== _p$.e && (_p$.e = _$setProp(_el$42, "fg", _v$12, _p$.e));
|
|
22558
|
+
_v$13 !== _p$.t && (_p$.t = _$setProp(_el$42, "bg", _v$13, _p$.t));
|
|
22559
|
+
return _p$;
|
|
22560
|
+
}, {
|
|
22561
|
+
e: undefined,
|
|
22562
|
+
t: undefined
|
|
22563
|
+
});
|
|
22564
|
+
return _el$42;
|
|
22565
|
+
})()
|
|
22566
|
+
});
|
|
22567
|
+
}
|
|
22568
|
+
}), _el$12);
|
|
22569
|
+
_$insertNode(_el$12, _$createTextNode(` i inspector \xB7 s consumers`));
|
|
21845
22570
|
_$effect((_p$) => {
|
|
21846
|
-
var {
|
|
21847
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$
|
|
21848
|
-
_v$
|
|
22571
|
+
var { textMuted: _v$3, textMuted: _v$4 } = t;
|
|
22572
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$3, _p$.e));
|
|
22573
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$12, "fg", _v$4, _p$.t));
|
|
21849
22574
|
return _p$;
|
|
21850
22575
|
}, {
|
|
21851
22576
|
e: undefined,
|
|
21852
22577
|
t: undefined
|
|
21853
22578
|
});
|
|
21854
|
-
return _el$
|
|
22579
|
+
return _el$1;
|
|
21855
22580
|
})()];
|
|
21856
22581
|
}
|
|
21857
22582
|
})];
|
|
21858
22583
|
}
|
|
21859
|
-
}), _el$
|
|
22584
|
+
}), _el$16);
|
|
21860
22585
|
_$insert(_el$, _$createComponent(Show, {
|
|
21861
22586
|
get when() {
|
|
21862
22587
|
return _$memo(() => !!(laneRoom() && lanesOn()))() && !showLanes();
|
|
21863
22588
|
},
|
|
21864
22589
|
get children() {
|
|
21865
|
-
var _el$
|
|
21866
|
-
_$insertNode(_el$
|
|
21867
|
-
_$effect((_$p) => _$setProp(_el$
|
|
21868
|
-
return _el$
|
|
21869
|
-
}
|
|
21870
|
-
}), _el$
|
|
21871
|
-
_$insertNode(_el$
|
|
21872
|
-
_$
|
|
21873
|
-
_$insertNode(_el$17, _el$20);
|
|
21874
|
-
_$insert(_el$17, (() => {
|
|
21875
|
-
var _c$ = _$memo(() => !!cropMode());
|
|
21876
|
-
return () => _c$() ? `\u2702 crop mode (${cropMode()}) \xB7 space mark \xB7 a auto \xB7 t result\u21C4turn \xB7 \u23CE apply \xB7 esc leave \xB7 marked ${selectedCandidates().length} ~${formatK(reclaimed(selectedCandidates()))}` : `filter: ${filter()}`;
|
|
21877
|
-
})(), _el$19);
|
|
21878
|
-
_$insert(_el$17, (() => {
|
|
21879
|
-
var _c$2 = _$memo(() => !!search());
|
|
21880
|
-
return () => _c$2() ? ` search: "${search()}"` : "";
|
|
21881
|
-
})(), _el$19);
|
|
21882
|
-
_$insert(_el$17, (() => {
|
|
21883
|
-
var _c$3 = _$memo(() => !!busy());
|
|
21884
|
-
return () => _c$3() ? ` \u2026 ${busy()}` : "";
|
|
21885
|
-
})(), _el$19);
|
|
21886
|
-
_$insert(_el$17, () => view().rows.length, _el$20);
|
|
22590
|
+
var _el$14 = _$createElement("text");
|
|
22591
|
+
_$insertNode(_el$14, _$createTextNode(`\u2502 lanes appear after 3 turns`));
|
|
22592
|
+
_$effect((_$p) => _$setProp(_el$14, "fg", t.textMuted, _$p));
|
|
22593
|
+
return _el$14;
|
|
22594
|
+
}
|
|
22595
|
+
}), _el$16);
|
|
22596
|
+
_$insertNode(_el$16, _el$17);
|
|
22597
|
+
_$insert(_el$16, statusLine, null);
|
|
21887
22598
|
_$insert(_el$, _$createComponent(Show, {
|
|
21888
22599
|
get when() {
|
|
21889
22600
|
return panel() === "decisions";
|
|
21890
22601
|
},
|
|
21891
22602
|
get children() {
|
|
21892
22603
|
return [(() => {
|
|
21893
|
-
var _el$
|
|
21894
|
-
_$insertNode(_el$
|
|
21895
|
-
_$insertNode(_el$
|
|
21896
|
-
_$insert(_el$
|
|
21897
|
-
_$effect((_$p) => _$setProp(_el$
|
|
21898
|
-
return _el$
|
|
22604
|
+
var _el$18 = _$createElement("text"), _el$19 = _$createTextNode(`\u2502 \u25C6 decisions on this tree (`), _el$20 = _$createTextNode(`) \xB7 \u23CE jump to record \xB7 E export markdown \xB7 q back`);
|
|
22605
|
+
_$insertNode(_el$18, _el$19);
|
|
22606
|
+
_$insertNode(_el$18, _el$20);
|
|
22607
|
+
_$insert(_el$18, () => decisions().length, _el$20);
|
|
22608
|
+
_$effect((_$p) => _$setProp(_el$18, "fg", t.accent, _$p));
|
|
22609
|
+
return _el$18;
|
|
21899
22610
|
})(), _$createComponent(Show, {
|
|
21900
22611
|
get when() {
|
|
21901
22612
|
return decisions().length === 0;
|
|
21902
22613
|
},
|
|
21903
22614
|
get children() {
|
|
21904
|
-
var _el$
|
|
21905
|
-
_$insertNode(_el$
|
|
21906
|
-
_$effect((_$p) => _$setProp(_el$
|
|
21907
|
-
return _el$
|
|
22615
|
+
var _el$21 = _$createElement("text");
|
|
22616
|
+
_$insertNode(_el$21, _$createTextNode(`\u2502 (none yet \u2014 /merge a branch to write one)`));
|
|
22617
|
+
_$effect((_$p) => _$setProp(_el$21, "fg", t.textMuted, _$p));
|
|
22618
|
+
return _el$21;
|
|
21908
22619
|
}
|
|
21909
22620
|
}), _$createComponent(For, {
|
|
21910
22621
|
get each() {
|
|
@@ -21912,132 +22623,142 @@ ${MERGE_TRUST}
|
|
|
21912
22623
|
},
|
|
21913
22624
|
children: (d, i) => {
|
|
21914
22625
|
const sel = () => i() === decisionIndex();
|
|
21915
|
-
const
|
|
21916
|
-
|
|
22626
|
+
const body = () => renderDecision(d.text ?? "", width() - 6);
|
|
22627
|
+
const room = () => Math.max(3, height() - decisions().length);
|
|
22628
|
+
const start = () => Math.min(decisionScroll(), Math.max(0, body().length - room()));
|
|
22629
|
+
const more = () => Math.max(0, body().length - start() - room());
|
|
21917
22630
|
return (() => {
|
|
21918
|
-
var _el$
|
|
21919
|
-
_$insertNode(_el$
|
|
21920
|
-
_$setProp(_el$
|
|
21921
|
-
_$insertNode(_el$
|
|
21922
|
-
_$insertNode(_el$
|
|
21923
|
-
_$insert(_el$
|
|
21924
|
-
_$insert(_el$
|
|
21925
|
-
_$insert(_el$
|
|
21926
|
-
_$insert(_el$
|
|
21927
|
-
_$insert(_el$
|
|
21928
|
-
var _c$
|
|
21929
|
-
return () => _c$
|
|
22631
|
+
var _el$43 = _$createElement("box"), _el$44 = _$createElement("text"), _el$45 = _$createTextNode(` `), _el$46 = _$createTextNode(` \xB7 `);
|
|
22632
|
+
_$insertNode(_el$43, _el$44);
|
|
22633
|
+
_$setProp(_el$43, "flexDirection", "column");
|
|
22634
|
+
_$insertNode(_el$44, _el$45);
|
|
22635
|
+
_$insertNode(_el$44, _el$46);
|
|
22636
|
+
_$insert(_el$44, () => sel() ? "\u203A" : "\u2502", _el$45);
|
|
22637
|
+
_$insert(_el$44, () => d.hidden ? "\u25C7 (hidden from model) " : "\u25C6 ", _el$46);
|
|
22638
|
+
_$insert(_el$44, () => clip(decisionSummary(d.text ?? "").title || d.branchName, 48), _el$46);
|
|
22639
|
+
_$insert(_el$44, () => new Date(d.recordedAt).toISOString().slice(0, 16).replace("T", " "), null);
|
|
22640
|
+
_$insert(_el$44, (() => {
|
|
22641
|
+
var _c$3 = _$memo(() => !!d.siblings.length);
|
|
22642
|
+
return () => _c$3() ? ` \xB7 \u2717 ${d.siblings.map((x) => x.name).join(", ")}` : "";
|
|
21930
22643
|
})(), null);
|
|
21931
|
-
_$insert(_el$
|
|
22644
|
+
_$insert(_el$43, _$createComponent(For, {
|
|
21932
22645
|
get each() {
|
|
21933
|
-
return _$memo(() => !!sel())() ?
|
|
22646
|
+
return _$memo(() => !!sel())() ? body().slice(start(), start() + room()) : [];
|
|
21934
22647
|
},
|
|
21935
22648
|
children: (l) => (() => {
|
|
21936
|
-
var _el$
|
|
21937
|
-
_$
|
|
21938
|
-
_$
|
|
21939
|
-
|
|
21940
|
-
return _el$41;
|
|
22649
|
+
var _el$48 = _$createElement("text");
|
|
22650
|
+
_$insert(_el$48, `\u2502 ${l}`);
|
|
22651
|
+
_$effect((_$p) => _$setProp(_el$48, "fg", t.text, _$p));
|
|
22652
|
+
return _el$48;
|
|
21941
22653
|
})()
|
|
21942
22654
|
}), null);
|
|
22655
|
+
_$insert(_el$43, _$createComponent(Show, {
|
|
22656
|
+
get when() {
|
|
22657
|
+
return _$memo(() => !!sel())() && more() > 0;
|
|
22658
|
+
},
|
|
22659
|
+
get children() {
|
|
22660
|
+
var _el$47 = _$createElement("text");
|
|
22661
|
+
_$insert(_el$47, () => `\u2502 \u2026 ${more()} more lines \u2193 (ctrl+d)`);
|
|
22662
|
+
_$effect((_$p) => _$setProp(_el$47, "fg", t.textMuted, _$p));
|
|
22663
|
+
return _el$47;
|
|
22664
|
+
}
|
|
22665
|
+
}), null);
|
|
21943
22666
|
_$effect((_p$) => {
|
|
21944
|
-
var _v$
|
|
21945
|
-
_v$
|
|
21946
|
-
_v$
|
|
22667
|
+
var _v$14 = sel() ? t.background : t.accent, _v$15 = sel() ? t.primary : undefined;
|
|
22668
|
+
_v$14 !== _p$.e && (_p$.e = _$setProp(_el$44, "fg", _v$14, _p$.e));
|
|
22669
|
+
_v$15 !== _p$.t && (_p$.t = _$setProp(_el$44, "bg", _v$15, _p$.t));
|
|
21947
22670
|
return _p$;
|
|
21948
22671
|
}, {
|
|
21949
22672
|
e: undefined,
|
|
21950
22673
|
t: undefined
|
|
21951
22674
|
});
|
|
21952
|
-
return _el$
|
|
22675
|
+
return _el$43;
|
|
21953
22676
|
})();
|
|
21954
22677
|
}
|
|
21955
22678
|
})];
|
|
21956
22679
|
}
|
|
21957
|
-
}), _el$
|
|
22680
|
+
}), _el$28);
|
|
21958
22681
|
_$insert(_el$, _$createComponent(Show, {
|
|
21959
22682
|
get when() {
|
|
21960
22683
|
return panel() === "consumers";
|
|
21961
22684
|
},
|
|
21962
22685
|
get children() {
|
|
21963
22686
|
return [(() => {
|
|
21964
|
-
var _el$
|
|
21965
|
-
_$insertNode(_el$
|
|
21966
|
-
_$insertNode(_el$
|
|
21967
|
-
_$insert(_el$
|
|
21968
|
-
_$effect((_$p) => _$setProp(_el$
|
|
21969
|
-
return _el$
|
|
22687
|
+
var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(`\u2502 what's filling the context \xB7 `), _el$25 = _$createTextNode(` total \xB7 source \xB7 %tree \xB7 %window \xB7 tokens \xB7 entries`);
|
|
22688
|
+
_$insertNode(_el$23, _el$24);
|
|
22689
|
+
_$insertNode(_el$23, _el$25);
|
|
22690
|
+
_$insert(_el$23, () => formatK(view().totalTokens), _el$25);
|
|
22691
|
+
_$effect((_$p) => _$setProp(_el$23, "fg", t.accent, _$p));
|
|
22692
|
+
return _el$23;
|
|
21970
22693
|
})(), _$createComponent(For, {
|
|
21971
22694
|
get each() {
|
|
21972
|
-
return
|
|
22695
|
+
return consumerLines();
|
|
21973
22696
|
},
|
|
21974
|
-
children: (
|
|
22697
|
+
children: (line, i) => {
|
|
21975
22698
|
const sel = () => i() === consumerIndex();
|
|
22699
|
+
const c = line.bucket;
|
|
22700
|
+
const fg = () => sel() ? t.background : line.entry ? t.textMuted : c.kind === "tool" ? t.warning : t.text;
|
|
22701
|
+
const window = () => c.shareOfWindow === undefined ? "\u2013" : `${(c.shareOfWindow * 100).toFixed(0)}%`;
|
|
22702
|
+
const entry = line.entry;
|
|
21976
22703
|
return (() => {
|
|
21977
|
-
var _el$
|
|
21978
|
-
_$insertNode(_el$
|
|
21979
|
-
_$
|
|
21980
|
-
_$
|
|
21981
|
-
_$insertNode(_el$43, _el$47);
|
|
21982
|
-
_$insertNode(_el$43, _el$48);
|
|
21983
|
-
_$insertNode(_el$43, _el$49);
|
|
21984
|
-
_$insert(_el$43, () => sel() ? "\u203A" : "\u2502", _el$44);
|
|
21985
|
-
_$insert(_el$43, () => c.source.padEnd(22).slice(0, 22), _el$45);
|
|
21986
|
-
_$insert(_el$43, () => `${(c.share * 100).toFixed(0)}%`.padStart(4), _el$46);
|
|
21987
|
-
_$insert(_el$43, () => bar(c.share, 24), _el$47);
|
|
21988
|
-
_$insert(_el$43, () => formatK(c.tokens).padStart(6), _el$48);
|
|
21989
|
-
_$insert(_el$43, () => c.count, _el$49);
|
|
21990
|
-
_$insert(_el$43, () => c.count === 1 ? "y" : "ies", null);
|
|
22704
|
+
var _el$49 = _$createElement("text"), _el$50 = _$createTextNode(` `);
|
|
22705
|
+
_$insertNode(_el$49, _el$50);
|
|
22706
|
+
_$insert(_el$49, () => sel() ? "\u203A" : "\u2502", _el$50);
|
|
22707
|
+
_$insert(_el$49, () => entry ? fitRow(` ${entry.croppable ? marked().has(entry.partID ?? "") ? "[x]" : "[ ]" : " "} ${plain(entry.preview)}${entry.croppable ? "" : ` \xB7 ${c.note ?? "not a completed tool result"}`}`, formatK(entry.tokens), width() - 4) : `${consumerOpen().has(c.source) ? "\u25BE" : "\u25B8"} ${c.source.padEnd(20).slice(0, 20)} ${`${(c.share * 100).toFixed(0)}%`.padStart(4)} ${window().padStart(5)} ${bar(c.tokens / consumerMax(), 18)} ${formatK(c.tokens).padStart(6)} \xB7 ${c.count} entr${c.count === 1 ? "y" : "ies"}${c.note ? ` \xB7 ${c.note}` : ""}`, null);
|
|
21991
22708
|
_$effect((_p$) => {
|
|
21992
|
-
var _v$
|
|
21993
|
-
_v$
|
|
21994
|
-
_v$
|
|
22709
|
+
var _v$16 = fg(), _v$17 = sel() ? t.primary : undefined;
|
|
22710
|
+
_v$16 !== _p$.e && (_p$.e = _$setProp(_el$49, "fg", _v$16, _p$.e));
|
|
22711
|
+
_v$17 !== _p$.t && (_p$.t = _$setProp(_el$49, "bg", _v$17, _p$.t));
|
|
21995
22712
|
return _p$;
|
|
21996
22713
|
}, {
|
|
21997
22714
|
e: undefined,
|
|
21998
22715
|
t: undefined
|
|
21999
22716
|
});
|
|
22000
|
-
return _el$
|
|
22717
|
+
return _el$49;
|
|
22001
22718
|
})();
|
|
22002
22719
|
}
|
|
22003
22720
|
})];
|
|
22004
22721
|
}
|
|
22005
|
-
}), _el$
|
|
22006
|
-
_$insert(_el$, _$createComponent(For, {
|
|
22007
|
-
get each() {
|
|
22008
|
-
return _$memo(() => panel() === "help")() ? HELP.slice(0, Math.max(6, size().rows - 5)) : [];
|
|
22009
|
-
},
|
|
22010
|
-
children: (l) => (() => {
|
|
22011
|
-
var _el$50 = _$createElement("text"), _el$51 = _$createTextNode(`\u2502 `);
|
|
22012
|
-
_$insertNode(_el$50, _el$51);
|
|
22013
|
-
_$insert(_el$50, l, null);
|
|
22014
|
-
_$effect((_$p) => _$setProp(_el$50, "fg", l.startsWith(" ") ? t.textMuted : t.accent, _$p));
|
|
22015
|
-
return _el$50;
|
|
22016
|
-
})()
|
|
22017
|
-
}), _el$31);
|
|
22722
|
+
}), _el$28);
|
|
22018
22723
|
_$insert(_el$, _$createComponent(Show, {
|
|
22019
22724
|
get when() {
|
|
22020
|
-
return _$memo(() =>
|
|
22725
|
+
return _$memo(() => !!showsTree())() && view().rows.length === 0;
|
|
22726
|
+
},
|
|
22727
|
+
get children() {
|
|
22728
|
+
var _el$26 = _$createElement("text"), _el$27 = _$createTextNode(`\u2502 `);
|
|
22729
|
+
_$insertNode(_el$26, _el$27);
|
|
22730
|
+
_$insert(_el$26, emptyText, null);
|
|
22731
|
+
_$effect((_$p) => _$setProp(_el$26, "fg", t.textMuted, _$p));
|
|
22732
|
+
return _el$26;
|
|
22733
|
+
}
|
|
22734
|
+
}), _el$28);
|
|
22735
|
+
_$insertNode(_el$28, _el$29);
|
|
22736
|
+
_$setProp(_el$28, "flexDirection", "row");
|
|
22737
|
+
_$setProp(_el$28, "flexGrow", 1);
|
|
22738
|
+
_$setProp(_el$29, "flexDirection", "column");
|
|
22739
|
+
_$setProp(_el$29, "flexGrow", 1);
|
|
22740
|
+
_$insert(_el$29, _$createComponent(Show, {
|
|
22741
|
+
get when() {
|
|
22742
|
+
return _$memo(() => !!showsTree())() && overflow();
|
|
22021
22743
|
},
|
|
22022
22744
|
get children() {
|
|
22023
|
-
var _el$
|
|
22024
|
-
_$insertNode(_el$
|
|
22025
|
-
_$
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
_$
|
|
22034
|
-
_$insert(_el$32, _$createComponent(For, {
|
|
22745
|
+
var _el$30 = _$createElement("text"), _el$31 = _$createTextNode(`\u2502 `);
|
|
22746
|
+
_$insertNode(_el$30, _el$31);
|
|
22747
|
+
_$insert(_el$30, (() => {
|
|
22748
|
+
var _c$ = _$memo(() => hiddenAbove() > 0);
|
|
22749
|
+
return () => _c$() ? `\u2191 ${hiddenAbove()} more` : "";
|
|
22750
|
+
})(), null);
|
|
22751
|
+
_$effect((_$p) => _$setProp(_el$30, "fg", t.textMuted, _$p));
|
|
22752
|
+
return _el$30;
|
|
22753
|
+
}
|
|
22754
|
+
}), null);
|
|
22755
|
+
_$insert(_el$29, _$createComponent(For, {
|
|
22035
22756
|
get each() {
|
|
22036
|
-
return _$memo(() =>
|
|
22757
|
+
return _$memo(() => !!showsTree())() ? visible() : [];
|
|
22037
22758
|
},
|
|
22038
22759
|
children: (row, i) => {
|
|
22039
22760
|
const isSel = () => windowStart() + i() === selected();
|
|
22040
|
-
const color = () => row.kind === "branch" ? statusColor(t, row) : row.kind === "turn" ? row.isDecision ? t.accent : t.text : row.isError ? t.error : row.warn ? t.warning : t.textMuted;
|
|
22761
|
+
const color = () => row.kind === "separator" ? t.textMuted : row.kind === "branch" ? statusColor(t, row) : !row.inContext ? t.textMuted : row.kind === "turn" ? row.isDecision ? t.accent : t.text : row.isError ? t.error : row.warn ? t.warning : t.textMuted;
|
|
22041
22762
|
const mark = () => {
|
|
22042
22763
|
if (!cropMode())
|
|
22043
22764
|
return "";
|
|
@@ -22048,70 +22769,139 @@ ${MERGE_TRUST}
|
|
|
22048
22769
|
const prot = c.protections.filter((p) => p !== "too-small");
|
|
22049
22770
|
return `${on2 ? "[x]" : "[ ]"}${prot.length ? "!" : " "}`;
|
|
22050
22771
|
};
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22772
|
+
const prefix = () => `${isSel() ? "\u203A" : "\u2502"} ${mark()}`;
|
|
22773
|
+
const segs = () => segmentsOf(rowLine(row, rowWidth(), row.id === view().currentRowId), search().trim(), thoughtOf(row));
|
|
22774
|
+
return _$createComponent(Show, {
|
|
22775
|
+
get when() {
|
|
22776
|
+
return segs().length > 1;
|
|
22777
|
+
},
|
|
22778
|
+
get fallback() {
|
|
22779
|
+
return (() => {
|
|
22780
|
+
var _el$53 = _$createElement("text");
|
|
22781
|
+
_$insert(_el$53, prefix, null);
|
|
22782
|
+
_$insert(_el$53, () => segs()[0]?.text, null);
|
|
22783
|
+
_$effect((_p$) => {
|
|
22784
|
+
var _v$20 = isSel() ? t.background : color(), _v$21 = isSel() ? t.primary : undefined;
|
|
22785
|
+
_v$20 !== _p$.e && (_p$.e = _$setProp(_el$53, "fg", _v$20, _p$.e));
|
|
22786
|
+
_v$21 !== _p$.t && (_p$.t = _$setProp(_el$53, "bg", _v$21, _p$.t));
|
|
22787
|
+
return _p$;
|
|
22788
|
+
}, {
|
|
22789
|
+
e: undefined,
|
|
22790
|
+
t: undefined
|
|
22791
|
+
});
|
|
22792
|
+
return _el$53;
|
|
22793
|
+
})();
|
|
22794
|
+
},
|
|
22795
|
+
get children() {
|
|
22796
|
+
var _el$51 = _$createElement("box"), _el$52 = _$createElement("text");
|
|
22797
|
+
_$insertNode(_el$51, _el$52);
|
|
22798
|
+
_$setProp(_el$51, "flexDirection", "row");
|
|
22799
|
+
_$insert(_el$52, prefix);
|
|
22800
|
+
_$insert(_el$51, _$createComponent(For, {
|
|
22801
|
+
get each() {
|
|
22802
|
+
return segs();
|
|
22803
|
+
},
|
|
22804
|
+
children: (s) => (() => {
|
|
22805
|
+
var _el$54 = _$createElement("text");
|
|
22806
|
+
_$insert(_el$54, () => s.text);
|
|
22807
|
+
_$effect((_p$) => {
|
|
22808
|
+
var _v$22 = s.kind === "match" ? t.background : s.kind === "dim" ? t.textMuted : isSel() ? t.background : color(), _v$23 = s.kind === "match" ? t.accent : isSel() ? t.primary : undefined;
|
|
22809
|
+
_v$22 !== _p$.e && (_p$.e = _$setProp(_el$54, "fg", _v$22, _p$.e));
|
|
22810
|
+
_v$23 !== _p$.t && (_p$.t = _$setProp(_el$54, "bg", _v$23, _p$.t));
|
|
22811
|
+
return _p$;
|
|
22812
|
+
}, {
|
|
22813
|
+
e: undefined,
|
|
22814
|
+
t: undefined
|
|
22815
|
+
});
|
|
22816
|
+
return _el$54;
|
|
22817
|
+
})()
|
|
22818
|
+
}), null);
|
|
22819
|
+
_$effect((_p$) => {
|
|
22820
|
+
var _v$18 = isSel() ? t.background : color(), _v$19 = isSel() ? t.primary : undefined;
|
|
22821
|
+
_v$18 !== _p$.e && (_p$.e = _$setProp(_el$52, "fg", _v$18, _p$.e));
|
|
22822
|
+
_v$19 !== _p$.t && (_p$.t = _$setProp(_el$52, "bg", _v$19, _p$.t));
|
|
22823
|
+
return _p$;
|
|
22824
|
+
}, {
|
|
22825
|
+
e: undefined,
|
|
22826
|
+
t: undefined
|
|
22827
|
+
});
|
|
22828
|
+
return _el$51;
|
|
22829
|
+
}
|
|
22830
|
+
});
|
|
22831
|
+
}
|
|
22832
|
+
}), null);
|
|
22833
|
+
_$insert(_el$29, _$createComponent(Show, {
|
|
22834
|
+
get when() {
|
|
22835
|
+
return _$memo(() => !!showsTree())() && overflow();
|
|
22836
|
+
},
|
|
22837
|
+
get children() {
|
|
22838
|
+
var _el$32 = _$createElement("text"), _el$33 = _$createTextNode(`\u2502 `);
|
|
22839
|
+
_$insertNode(_el$32, _el$33);
|
|
22840
|
+
_$insert(_el$32, (() => {
|
|
22841
|
+
var _c$2 = _$memo(() => hiddenBelow() > 0);
|
|
22842
|
+
return () => _c$2() ? `\u2026 ${hiddenBelow()} more \u2193` : "";
|
|
22843
|
+
})(), null);
|
|
22844
|
+
_$effect((_$p) => _$setProp(_el$32, "fg", t.textMuted, _$p));
|
|
22845
|
+
return _el$32;
|
|
22068
22846
|
}
|
|
22069
22847
|
}), null);
|
|
22070
|
-
_$insert(_el$
|
|
22848
|
+
_$insert(_el$29, _$createComponent(For, {
|
|
22849
|
+
get each() {
|
|
22850
|
+
return _$memo(() => !!(showsTree() && view().rows.length > 0 && !view().rows.some((r) => r.kind === "branch")))() ? noBranchesLines() : [];
|
|
22851
|
+
},
|
|
22852
|
+
children: (l) => (() => {
|
|
22853
|
+
var _el$55 = _$createElement("text"), _el$56 = _$createTextNode(`\u2502 `);
|
|
22854
|
+
_$insertNode(_el$55, _el$56);
|
|
22855
|
+
_$insert(_el$55, l, null);
|
|
22856
|
+
_$effect((_$p) => _$setProp(_el$55, "fg", t.textMuted, _$p));
|
|
22857
|
+
return _el$55;
|
|
22858
|
+
})()
|
|
22859
|
+
}), null);
|
|
22860
|
+
_$insert(_el$29, _$createComponent(For, {
|
|
22071
22861
|
get each() {
|
|
22072
|
-
return _$memo(() =>
|
|
22862
|
+
return _$memo(() => panel() === "help")() ? HELP.slice(0, helpHeight()) : [];
|
|
22073
22863
|
},
|
|
22074
22864
|
children: (l) => (() => {
|
|
22075
|
-
var _el$
|
|
22076
|
-
_$insertNode(_el$
|
|
22077
|
-
_$insert(_el$
|
|
22078
|
-
_$effect((_$p) => _$setProp(_el$
|
|
22079
|
-
return _el$
|
|
22865
|
+
var _el$57 = _$createElement("text"), _el$58 = _$createTextNode(`\u2502 `);
|
|
22866
|
+
_$insertNode(_el$57, _el$58);
|
|
22867
|
+
_$insert(_el$57, l, null);
|
|
22868
|
+
_$effect((_$p) => _$setProp(_el$57, "fg", l.startsWith(" ") ? t.textMuted : t.accent, _$p));
|
|
22869
|
+
return _el$57;
|
|
22080
22870
|
})()
|
|
22081
22871
|
}), null);
|
|
22082
|
-
_$insert(_el$
|
|
22872
|
+
_$insert(_el$28, _$createComponent(Show, {
|
|
22083
22873
|
get when() {
|
|
22084
22874
|
return showInspector();
|
|
22085
22875
|
},
|
|
22086
22876
|
get children() {
|
|
22087
|
-
var _el$
|
|
22088
|
-
_$setProp(_el$
|
|
22089
|
-
_$setProp(_el$
|
|
22090
|
-
_$insert(_el$
|
|
22877
|
+
var _el$34 = _$createElement("box");
|
|
22878
|
+
_$setProp(_el$34, "flexDirection", "column");
|
|
22879
|
+
_$setProp(_el$34, "paddingLeft", 1);
|
|
22880
|
+
_$insert(_el$34, _$createComponent(For, {
|
|
22091
22881
|
get each() {
|
|
22092
22882
|
return inspectorLines();
|
|
22093
22883
|
},
|
|
22094
22884
|
children: (l) => (() => {
|
|
22095
|
-
var _el$
|
|
22096
|
-
_$insertNode(_el$
|
|
22097
|
-
_$insert(_el$
|
|
22098
|
-
_$effect((_$p) => _$setProp(_el$
|
|
22099
|
-
return _el$
|
|
22885
|
+
var _el$59 = _$createElement("text"), _el$60 = _$createTextNode(`\u2503 `);
|
|
22886
|
+
_$insertNode(_el$59, _el$60);
|
|
22887
|
+
_$insert(_el$59, () => l.text, null);
|
|
22888
|
+
_$effect((_$p) => _$setProp(_el$59, "fg", l.fg, _$p));
|
|
22889
|
+
return _el$59;
|
|
22100
22890
|
})()
|
|
22101
22891
|
}));
|
|
22102
|
-
_$effect((_$p) => _$setProp(_el$
|
|
22103
|
-
return _el$
|
|
22892
|
+
_$effect((_$p) => _$setProp(_el$34, "width", inspectorWidth(), _$p));
|
|
22893
|
+
return _el$34;
|
|
22104
22894
|
}
|
|
22105
22895
|
}), null);
|
|
22106
|
-
_$insertNode(_el$
|
|
22107
|
-
_$insert(_el$
|
|
22896
|
+
_$insertNode(_el$35, _el$36);
|
|
22897
|
+
_$insert(_el$35, footer, null);
|
|
22108
22898
|
_$effect((_p$) => {
|
|
22109
|
-
var { background: _v$
|
|
22110
|
-
_v$
|
|
22111
|
-
_v$
|
|
22112
|
-
_v$
|
|
22113
|
-
_v$
|
|
22114
|
-
_v$
|
|
22899
|
+
var { background: _v$5, primary: _v$6 } = t, _v$7 = t[BAND_KEY[band()]], _v$8 = cropMode() ? t.warning : searchMode() ? t.accent : t.textMuted, _v$9 = cropMode() ? t.warning : t.textMuted;
|
|
22900
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$, "backgroundColor", _v$5, _p$.e));
|
|
22901
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$3, "fg", _v$6, _p$.t));
|
|
22902
|
+
_v$7 !== _p$.a && (_p$.a = _$setProp(_el$4, "fg", _v$7, _p$.a));
|
|
22903
|
+
_v$8 !== _p$.o && (_p$.o = _$setProp(_el$16, "fg", _v$8, _p$.o));
|
|
22904
|
+
_v$9 !== _p$.i && (_p$.i = _$setProp(_el$35, "fg", _v$9, _p$.i));
|
|
22115
22905
|
return _p$;
|
|
22116
22906
|
}, {
|
|
22117
22907
|
e: undefined,
|
|
@@ -22342,7 +23132,7 @@ var tui = async (api2, rawOptions) => {
|
|
|
22342
23132
|
adoptSoon();
|
|
22343
23133
|
});
|
|
22344
23134
|
api2.lifecycle?.onDispose(offCreated);
|
|
22345
|
-
const
|
|
23135
|
+
const promptDialog2 = (title, placeholder) => new Promise((resolve) => {
|
|
22346
23136
|
api2.ui.dialog.replace(() => api2.ui.DialogPrompt({
|
|
22347
23137
|
title,
|
|
22348
23138
|
placeholder,
|
|
@@ -22399,7 +23189,7 @@ var tui = async (api2, rawOptions) => {
|
|
|
22399
23189
|
if (!sessionID)
|
|
22400
23190
|
return;
|
|
22401
23191
|
await new Promise((r) => setTimeout(r, 30));
|
|
22402
|
-
const name = await
|
|
23192
|
+
const name = await promptDialog2(BRANCH_DIALOG.title, BRANCH_DIALOG.placeholder);
|
|
22403
23193
|
debug("branch.named", {
|
|
22404
23194
|
name
|
|
22405
23195
|
});
|
|
@@ -22437,7 +23227,7 @@ var tui = async (api2, rawOptions) => {
|
|
|
22437
23227
|
const last = api2.state.session.messages(sessionID).at(-1);
|
|
22438
23228
|
if (!last)
|
|
22439
23229
|
return;
|
|
22440
|
-
const value = await
|
|
23230
|
+
const value = await promptDialog2("Label (empty to remove)", "checkpoint");
|
|
22441
23231
|
if (value === undefined)
|
|
22442
23232
|
return;
|
|
22443
23233
|
setLabel({
|
|
@@ -22482,11 +23272,20 @@ var tui = async (api2, rawOptions) => {
|
|
|
22482
23272
|
});
|
|
22483
23273
|
return;
|
|
22484
23274
|
}
|
|
23275
|
+
const parent = await fetchTranscript(api2, branch.parentSessionID, directory).catch(() => {
|
|
23276
|
+
return;
|
|
23277
|
+
});
|
|
23278
|
+
const parentLabel = branch.parentSessionID === state.root ? TRUNK_LABEL : state.sessions[branch.parentSessionID]?.name ?? TRUNK_LABEL;
|
|
23279
|
+
const turns = ownTurnCount(api2.state.session.messages(sessionID), {
|
|
23280
|
+
messageID: branch.anchorMessageID,
|
|
23281
|
+
parentMessageIDs: parent?.messages.map((m) => m.id) ?? []
|
|
23282
|
+
});
|
|
22485
23283
|
const mode = await new Promise((resolve) => {
|
|
22486
23284
|
api2.ui.dialog.replace(() => api2.ui.DialogSelect({
|
|
22487
|
-
title: mergeDialogTitle(branch.name ?? "branch",
|
|
23285
|
+
title: mergeDialogTitle(branch.name ?? "branch", parent ? mergeTargetOf(parentLabel, parent.messages) : undefined),
|
|
22488
23286
|
options: mergeDialogOptions({
|
|
22489
|
-
siblings: openSiblings(state, sessionID).length
|
|
23287
|
+
siblings: openSiblings(state, sessionID).length,
|
|
23288
|
+
turns
|
|
22490
23289
|
}),
|
|
22491
23290
|
onSelect: (o) => {
|
|
22492
23291
|
resolve(o.value);
|
|
@@ -22581,6 +23380,8 @@ ${MERGE_TRUST}`,
|
|
|
22581
23380
|
return store.stateForSession(props.session_id);
|
|
22582
23381
|
});
|
|
22583
23382
|
const branch = () => st()?.sessions[props.session_id];
|
|
23383
|
+
const size = createMemo2(() => contextSizeOf(toMinimalMessages(api2.state.session.messages(props.session_id), api2.state.part)));
|
|
23384
|
+
const limit = createMemo2(() => modelContextLimit(api2, props.session_id));
|
|
22584
23385
|
const crops = () => st()?.activeCrops(props.session_id) ?? [];
|
|
22585
23386
|
const hidden = () => crops().reduce((s, c) => s + c.targets.reduce((x, y) => x + y.estTokens, 0), 0);
|
|
22586
23387
|
const siblings = () => Object.values(st()?.sessions ?? {}).filter((b) => b.parentSessionID === props.session_id && b.status === "open").length;
|
|
@@ -22591,9 +23392,10 @@ ${MERGE_TRUST}`,
|
|
|
22591
23392
|
return `${b.status}${title && room > 3 ? ` \xB7 from "${clip(title, room)}"` : ""}`;
|
|
22592
23393
|
};
|
|
22593
23394
|
return (() => {
|
|
22594
|
-
var _el$ = _$createElement2("box"), _el$2 = _$createElement2("text"), _el$3 = _$createElement2("b"), _el$
|
|
23395
|
+
var _el$ = _$createElement2("box"), _el$2 = _$createElement2("text"), _el$3 = _$createElement2("b"), _el$7 = _$createElement2("text"), _el$9 = _$createElement2("text");
|
|
22595
23396
|
_$insertNode2(_el$, _el$2);
|
|
22596
|
-
_$insertNode2(_el$, _el$
|
|
23397
|
+
_$insertNode2(_el$, _el$7);
|
|
23398
|
+
_$insertNode2(_el$, _el$9);
|
|
22597
23399
|
_$setProp2(_el$, "flexDirection", "column");
|
|
22598
23400
|
_$insertNode2(_el$2, _el$3);
|
|
22599
23401
|
_$insertNode2(_el$3, _$createTextNode2(`Context tree`));
|
|
@@ -22603,10 +23405,10 @@ ${MERGE_TRUST}`,
|
|
|
22603
23405
|
},
|
|
22604
23406
|
get fallback() {
|
|
22605
23407
|
return (() => {
|
|
22606
|
-
var _el$
|
|
22607
|
-
_$insert2(_el$
|
|
22608
|
-
_$effect2((_$p) => _$setProp2(_el$
|
|
22609
|
-
return _el$
|
|
23408
|
+
var _el$1 = _$createElement2("text");
|
|
23409
|
+
_$insert2(_el$1, () => `trunk${siblings() ? ` \xB7 ${siblings()} branch${siblings() === 1 ? "" : "es"}` : ""}`);
|
|
23410
|
+
_$effect2((_$p) => _$setProp2(_el$1, "fg", t.text, _$p));
|
|
23411
|
+
return _el$1;
|
|
22610
23412
|
})();
|
|
22611
23413
|
},
|
|
22612
23414
|
get children() {
|
|
@@ -22622,27 +23424,30 @@ ${MERGE_TRUST}`,
|
|
|
22622
23424
|
return _el$6;
|
|
22623
23425
|
})()];
|
|
22624
23426
|
}
|
|
22625
|
-
}), _el$
|
|
23427
|
+
}), _el$7);
|
|
23428
|
+
_$insert2(_el$7, () => formatContext(size(), limit()));
|
|
22626
23429
|
_$insert2(_el$, _$createComponent2(Show2, {
|
|
22627
23430
|
get when() {
|
|
22628
23431
|
return crops().length;
|
|
22629
23432
|
},
|
|
22630
23433
|
get children() {
|
|
22631
|
-
var _el$
|
|
22632
|
-
_$insert2(_el$
|
|
22633
|
-
_$effect2((_$p) => _$setProp2(_el$
|
|
22634
|
-
return _el$
|
|
23434
|
+
var _el$8 = _$createElement2("text");
|
|
23435
|
+
_$insert2(_el$8, () => `\u2702 ${crops().length} crop${crops().length === 1 ? "" : "s"} \xB7 ~${formatK(hidden())} hidden`);
|
|
23436
|
+
_$effect2((_$p) => _$setProp2(_el$8, "fg", t.warning, _$p));
|
|
23437
|
+
return _el$8;
|
|
22635
23438
|
}
|
|
22636
|
-
}), _el$
|
|
22637
|
-
_$insertNode2(_el$
|
|
23439
|
+
}), _el$9);
|
|
23440
|
+
_$insertNode2(_el$9, _$createTextNode2(`/tree \xB7 ctrl+q`));
|
|
22638
23441
|
_$effect2((_p$) => {
|
|
22639
|
-
var
|
|
23442
|
+
var _v$ = t.text, _v$2 = t[BAND_COLOR[bandFor(size().tokens, limit())]], _v$3 = t.textMuted;
|
|
22640
23443
|
_v$ !== _p$.e && (_p$.e = _$setProp2(_el$2, "fg", _v$, _p$.e));
|
|
22641
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp2(_el$
|
|
23444
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp2(_el$7, "fg", _v$2, _p$.t));
|
|
23445
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp2(_el$9, "fg", _v$3, _p$.a));
|
|
22642
23446
|
return _p$;
|
|
22643
23447
|
}, {
|
|
22644
23448
|
e: undefined,
|
|
22645
|
-
t: undefined
|
|
23449
|
+
t: undefined,
|
|
23450
|
+
a: undefined
|
|
22646
23451
|
});
|
|
22647
23452
|
return _el$;
|
|
22648
23453
|
})();
|
|
@@ -22650,12 +23455,12 @@ ${MERGE_TRUST}`,
|
|
|
22650
23455
|
session_prompt_right: (_ctx, props) => {
|
|
22651
23456
|
const t = api2.theme.current;
|
|
22652
23457
|
const size = createMemo2(() => contextSizeOf(toMinimalMessages(api2.state.session.messages(props.session_id), api2.state.part)));
|
|
22653
|
-
const band = () => bandFor(size().tokens);
|
|
22654
23458
|
const branch = () => {
|
|
22655
23459
|
journalRevision();
|
|
22656
23460
|
return store.stateForSession(props.session_id)?.sessions[props.session_id];
|
|
22657
23461
|
};
|
|
22658
23462
|
const limit = createMemo2(() => modelContextLimit(api2, props.session_id));
|
|
23463
|
+
const band = () => bandFor(size().tokens, limit());
|
|
22659
23464
|
const reserve = () => api2.state.config.compaction?.reserved ?? 16384;
|
|
22660
23465
|
const [trend, setTrend] = createSignal3("");
|
|
22661
23466
|
let prevTokens = 0;
|
|
@@ -22711,7 +23516,7 @@ ${MERGE_TRUST}`,
|
|
|
22711
23516
|
redNudged = true;
|
|
22712
23517
|
api2.ui.toast({
|
|
22713
23518
|
variant: "warning",
|
|
22714
|
-
message:
|
|
23519
|
+
message: `context is in the red band (${limit() ? "\u226585% of the window" : "\u226564k"}) \u2014 consider /tree \u2192 c crop, or /merge a branch`,
|
|
22715
23520
|
duration: 6000
|
|
22716
23521
|
});
|
|
22717
23522
|
} else if (b === "low" || b === "healthy")
|
|
@@ -22728,10 +23533,10 @@ ${MERGE_TRUST}`,
|
|
|
22728
23533
|
guardNudged = false;
|
|
22729
23534
|
});
|
|
22730
23535
|
return (() => {
|
|
22731
|
-
var _el$
|
|
22732
|
-
_$insert2(_el$
|
|
22733
|
-
_$effect2((_$p) => _$setProp2(_el$
|
|
22734
|
-
return _el$
|
|
23536
|
+
var _el$10 = _$createElement2("text");
|
|
23537
|
+
_$insert2(_el$10, () => `${branch() ? `\u2387 ${branchLabel(api2, props.session_id, branch().name, 24)} \xB7 ` : ""}${formatContext(size(), limit())}${trend()}`);
|
|
23538
|
+
_$effect2((_$p) => _$setProp2(_el$10, "fg", t[BAND_COLOR[band()]], _$p));
|
|
23539
|
+
return _el$10;
|
|
22735
23540
|
})();
|
|
22736
23541
|
}
|
|
22737
23542
|
}
|