@edeng23/pines 0.1.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.
Files changed (98) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +37 -0
  3. package/dist/cli.js +83 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/client/ansi.js +37 -0
  6. package/dist/client/ansi.js.map +1 -0
  7. package/dist/client/app.js +1868 -0
  8. package/dist/client/app.js.map +1 -0
  9. package/dist/client/boot.js +221 -0
  10. package/dist/client/boot.js.map +1 -0
  11. package/dist/client/commands.js +133 -0
  12. package/dist/client/commands.js.map +1 -0
  13. package/dist/client/daemon-client.js +99 -0
  14. package/dist/client/daemon-client.js.map +1 -0
  15. package/dist/client/forest/camera.js +88 -0
  16. package/dist/client/forest/camera.js.map +1 -0
  17. package/dist/client/forest/canvas.js +175 -0
  18. package/dist/client/forest/canvas.js.map +1 -0
  19. package/dist/client/forest/sidebar.js +182 -0
  20. package/dist/client/forest/sidebar.js.map +1 -0
  21. package/dist/client/forest/status.js +39 -0
  22. package/dist/client/forest/status.js.map +1 -0
  23. package/dist/client/forest/style.js +70 -0
  24. package/dist/client/forest/style.js.map +1 -0
  25. package/dist/client/forest/styles.js +429 -0
  26. package/dist/client/forest/styles.js.map +1 -0
  27. package/dist/client/forest/view.js +181 -0
  28. package/dist/client/forest/view.js.map +1 -0
  29. package/dist/client/forest/wordmark.js +26 -0
  30. package/dist/client/forest/wordmark.js.map +1 -0
  31. package/dist/client/input.js +89 -0
  32. package/dist/client/input.js.map +1 -0
  33. package/dist/client/theme.js +27 -0
  34. package/dist/client/theme.js.map +1 -0
  35. package/dist/client/tree/asciitree.js +277 -0
  36. package/dist/client/tree/asciitree.js.map +1 -0
  37. package/dist/client/tree/convmap.js +260 -0
  38. package/dist/client/tree/convmap.js.map +1 -0
  39. package/dist/client/tree/lanegraph.js +260 -0
  40. package/dist/client/tree/lanegraph.js.map +1 -0
  41. package/dist/client/tree/merge.js +161 -0
  42. package/dist/client/tree/merge.js.map +1 -0
  43. package/dist/client/tree/sessionview.js +163 -0
  44. package/dist/client/tree/sessionview.js.map +1 -0
  45. package/dist/client/tree/view.js +238 -0
  46. package/dist/client/tree/view.js.map +1 -0
  47. package/dist/client/uistate.js +45 -0
  48. package/dist/client/uistate.js.map +1 -0
  49. package/dist/daemon/agents.js +102 -0
  50. package/dist/daemon/agents.js.map +1 -0
  51. package/dist/daemon/branch.js +149 -0
  52. package/dist/daemon/branch.js.map +1 -0
  53. package/dist/daemon/embed-worker.js +74 -0
  54. package/dist/daemon/embed-worker.js.map +1 -0
  55. package/dist/daemon/essence.js +194 -0
  56. package/dist/daemon/essence.js.map +1 -0
  57. package/dist/daemon/extbridge.js +138 -0
  58. package/dist/daemon/extbridge.js.map +1 -0
  59. package/dist/daemon/lifecycle.js +134 -0
  60. package/dist/daemon/lifecycle.js.map +1 -0
  61. package/dist/daemon/pty-compat.js +53 -0
  62. package/dist/daemon/pty-compat.js.map +1 -0
  63. package/dist/daemon/semantic.js +337 -0
  64. package/dist/daemon/semantic.js.map +1 -0
  65. package/dist/daemon/server.js +808 -0
  66. package/dist/daemon/server.js.map +1 -0
  67. package/dist/daemon/supervisor.js +311 -0
  68. package/dist/daemon/supervisor.js.map +1 -0
  69. package/dist/extension/pines-extension.js +191 -0
  70. package/dist/extension/pines-extension.js.map +1 -0
  71. package/dist/layout/lexical.js +104 -0
  72. package/dist/layout/lexical.js.map +1 -0
  73. package/dist/layout/pca.js +91 -0
  74. package/dist/layout/pca.js.map +1 -0
  75. package/dist/layout/relax.js +47 -0
  76. package/dist/layout/relax.js.map +1 -0
  77. package/dist/session/ingest.js +95 -0
  78. package/dist/session/ingest.js.map +1 -0
  79. package/dist/session/parser.js +254 -0
  80. package/dist/session/parser.js.map +1 -0
  81. package/dist/shared/config.js +30 -0
  82. package/dist/shared/config.js.map +1 -0
  83. package/dist/shared/paths.js +47 -0
  84. package/dist/shared/paths.js.map +1 -0
  85. package/dist/shared/protocol.js +9 -0
  86. package/dist/shared/protocol.js.map +1 -0
  87. package/dist/shared/types.js +3 -0
  88. package/dist/shared/types.js.map +1 -0
  89. package/dist/shared/wire.js +51 -0
  90. package/dist/shared/wire.js.map +1 -0
  91. package/dist/store/db.js +203 -0
  92. package/dist/store/db.js.map +1 -0
  93. package/dist/store/watcher.js +69 -0
  94. package/dist/store/watcher.js.map +1 -0
  95. package/package.json +80 -0
  96. package/scripts/fix-node-pty.mjs +34 -0
  97. package/src/extension/pines-extension.ts +192 -0
  98. package/src/store/schema.sql +60 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The family a tree belongs to: walk up parentSessionPath to the original,
3
+ * then collect every descendant depth-first. Sibling order is by session
4
+ * path — a stable key, so merge order (and with it node ownership) never
5
+ * shuffles just because activity bumped a file's mtime.
6
+ */
7
+ export function familyOf(treeId, summaryOf, childrenOf, parentOf) {
8
+ const start = summaryOf(treeId);
9
+ if (!start)
10
+ return [];
11
+ let root = start;
12
+ const up = new Set([root.treeId]);
13
+ for (let guard = 0; guard < 64; guard++) {
14
+ const parent = root.parentSessionPath ? parentOf(root.parentSessionPath) : undefined;
15
+ if (!parent || up.has(parent.treeId))
16
+ break;
17
+ up.add(parent.treeId);
18
+ root = parent;
19
+ }
20
+ const out = [];
21
+ const seen = new Set();
22
+ const walk = (t) => {
23
+ if (seen.has(t.treeId))
24
+ return;
25
+ seen.add(t.treeId);
26
+ out.push(t);
27
+ for (const kid of [...childrenOf(t.sessionPath)].sort((a, b) => a.sessionPath < b.sessionPath ? -1 : a.sessionPath > b.sessionPath ? 1 : 0)) {
28
+ walk(kid);
29
+ }
30
+ };
31
+ walk(root);
32
+ return out;
33
+ }
34
+ /**
35
+ * Union the family's session trees into one. First writer wins per node id
36
+ * (the original is merged first, so shared prefixes keep their canonical
37
+ * content); children lists union, then sort by the child message's own
38
+ * timestamp — branches sit in the order they were CREATED, an order nothing
39
+ * (attaching, labeling, agent activity) can later disturb.
40
+ *
41
+ * The merged leafId — the path the ascii tree treats as "the story so far" —
42
+ * is the tip of the most recently active loaded session.
43
+ */
44
+ export function mergeConversation(members) {
45
+ const loaded = members.filter((m) => m.detail);
46
+ const nodes = {};
47
+ const childOrder = new Map();
48
+ const rootIds = [];
49
+ for (const m of loaded) {
50
+ const d = m.detail;
51
+ for (const r of d.rootIds)
52
+ if (!rootIds.includes(r))
53
+ rootIds.push(r);
54
+ for (const [id, n] of Object.entries(d.nodes)) {
55
+ const existing = nodes[id];
56
+ if (!existing) {
57
+ nodes[id] = { ...n, children: [...n.children] };
58
+ childOrder.set(id, [...n.children]);
59
+ }
60
+ else {
61
+ const order = childOrder.get(id);
62
+ for (const c of n.children)
63
+ if (!order.includes(c))
64
+ order.push(c);
65
+ }
66
+ }
67
+ }
68
+ for (const [id, order] of childOrder) {
69
+ // Drop dangling child ids (a member's detail may still be in flight);
70
+ // creation order among siblings (stable sort — union order breaks ties).
71
+ nodes[id].children = order
72
+ .filter((c) => nodes[c])
73
+ .sort((a, b) => {
74
+ const ta = nodes[a].timestamp;
75
+ const tb = nodes[b].timestamp;
76
+ return ta < tb ? -1 : ta > tb ? 1 : 0;
77
+ });
78
+ for (const c of nodes[id].children) {
79
+ if (nodes[c] && nodes[c].parentId == null && !rootIds.includes(c)) {
80
+ nodes[c] = { ...nodes[c], parentId: id };
81
+ }
82
+ }
83
+ }
84
+ const tips = [];
85
+ const tipsAt = new Map();
86
+ for (const m of loaded) {
87
+ const leaf = m.detail.leafId;
88
+ if (!leaf || !nodes[leaf])
89
+ continue;
90
+ tips.push({ nodeId: leaf, summary: m.summary, parked: nodes[leaf].children.length > 0 });
91
+ const at = tipsAt.get(leaf);
92
+ if (at)
93
+ at.push(m.summary);
94
+ else
95
+ tipsAt.set(leaf, [m.summary]);
96
+ }
97
+ // Story ends at the busiest recent branch: running beats waiting beats
98
+ // dormant; recency breaks ties. Parked tips sit mid-tree and can never be
99
+ // "the end of the story" — a label append touching an old file must not
100
+ // yank the green ● into the middle of the conversation.
101
+ const rank = (t) => t.parked
102
+ ? -1
103
+ : t.summary.status === "running"
104
+ ? 3
105
+ : !t.summary.seen
106
+ ? 2
107
+ : t.summary.live
108
+ ? 1
109
+ : 0;
110
+ const activeTip = [...tips].sort((a, b) => rank(b) - rank(a) || b.summary.mtime - a.summary.mtime)[0];
111
+ return {
112
+ detail: {
113
+ treeId: members[0]?.summary.treeId ?? "",
114
+ leafId: activeTip?.nodeId ?? loaded[0]?.detail.leafId ?? null,
115
+ rootIds,
116
+ nodes,
117
+ },
118
+ tips,
119
+ tipsAt,
120
+ };
121
+ }
122
+ /**
123
+ * Aggregate a family into one forest-facing summary (the root's identity,
124
+ * the family's most attention-worthy state). The forest shows conversations,
125
+ * not session files.
126
+ */
127
+ export function aggregateFamily(members) {
128
+ const root = members[0];
129
+ const pick = (pred) => members.filter(pred).sort((a, b) => b.mtime - a.mtime)[0];
130
+ const crashed = pick((t) => t.status === "crashed");
131
+ const needsInput = pick((t) => t.status === "waiting" && !t.seen);
132
+ const working = pick((t) => t.status === "running");
133
+ const waiting = pick((t) => t.status === "waiting");
134
+ const lead = crashed ?? needsInput ?? working ?? waiting ?? pick(() => true);
135
+ return {
136
+ ...root,
137
+ status: lead.status,
138
+ seen: lead.seen,
139
+ live: members.some((t) => t.live),
140
+ mtime: Math.max(...members.map((t) => t.mtime)),
141
+ nodeCount: members.reduce((n, t) => n + t.nodeCount, 0),
142
+ lastExitCode: lead.lastExitCode,
143
+ };
144
+ }
145
+ /**
146
+ * The member to attach to when the user targets the conversation as a whole:
147
+ * whoever needs attention, else the most recently active.
148
+ */
149
+ export function attentionMemberOf(members) {
150
+ const rank = (t) => t.status === "crashed"
151
+ ? 0
152
+ : t.status === "waiting" && !t.seen
153
+ ? 1
154
+ : t.status === "running"
155
+ ? 2
156
+ : t.status === "waiting"
157
+ ? 3
158
+ : 4;
159
+ return [...members].sort((a, b) => rank(a) - rank(b) || b.mtime - a.mtime)[0];
160
+ }
161
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/client/tree/merge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ import { aContainerOf, aRowIndexOf, buildAsciiTree, } from "./asciitree.js";
2
+ import { familyOf, mergeConversation } from "./merge.js";
3
+ import { buildSkeleton } from "./convmap.js";
4
+ /**
5
+ * The first node unique to a forked session: the child of its fork point on
6
+ * its own path — the user message that began the branch. Null for parked
7
+ * forks (nothing unique yet) and for the original session.
8
+ */
9
+ export function branchHeadOf(m) {
10
+ const forkPoint = m.summary.parentEntryId;
11
+ const detail = m.detail;
12
+ if (!forkPoint || !detail?.leafId)
13
+ return null;
14
+ let cur = detail.leafId;
15
+ for (let guard = 0; cur && guard < 10_000; guard++) {
16
+ const node = detail.nodes[cur];
17
+ if (!node)
18
+ return null;
19
+ if (node.parentId === forkPoint)
20
+ return cur;
21
+ cur = node.parentId;
22
+ }
23
+ return null;
24
+ }
25
+ /**
26
+ * Follow the current exchange to its end: from `nodeId`, walk the original
27
+ * continuation (first child) through tool calls and replies, stopping before
28
+ * the next user turn. Branching from a question then lands AFTER its answer —
29
+ * a user message begins the new branch; it never displaces the reply.
30
+ */
31
+ export function exchangeEndOf(detail, nodeId) {
32
+ let cur = nodeId;
33
+ for (let guard = 0; guard < 10_000; guard++) {
34
+ const first = detail.nodes[cur]?.children?.[0];
35
+ const next = first ? detail.nodes[first] : undefined;
36
+ if (!next)
37
+ return cur;
38
+ if (next.kind === "user" || next.kind === "compaction" || next.kind === "branch_summary") {
39
+ return cur;
40
+ }
41
+ cur = first;
42
+ }
43
+ return cur;
44
+ }
45
+ export function buildConvView(input) {
46
+ const family = familyOf(input.rootTreeId, input.summaryOf, input.childrenOf, input.parentOf);
47
+ const members = family.map((summary) => {
48
+ const detail = input.detailOf(summary.treeId);
49
+ if (!detail)
50
+ input.wantDetail?.(summary.treeId);
51
+ return { summary, detail };
52
+ });
53
+ const pendingMembers = members.filter((m) => !m.detail).length;
54
+ const merged = mergeConversation(members);
55
+ // A dormant fork parked mid-tree adds no content — it is bookkeeping, not
56
+ // conversation. It leaves the transcript, the panel and the mini entirely;
57
+ // Enter on its node quietly RESUMES it instead of minting another file.
58
+ const shown = merged.tips.filter((t) => !t.parked || t.summary.live || !t.summary.seen);
59
+ const parkedAt = new Map();
60
+ let parkedCount = 0;
61
+ for (const t of merged.tips) {
62
+ if (shown.includes(t))
63
+ continue;
64
+ parkedCount++;
65
+ const at = parkedAt.get(t.nodeId);
66
+ if (at)
67
+ at.push(t.summary);
68
+ else
69
+ parkedAt.set(t.nodeId, [t.summary]);
70
+ }
71
+ const shownAt = new Map();
72
+ for (const t of shown) {
73
+ const at = shownAt.get(t.nodeId);
74
+ if (at)
75
+ at.push(t.summary);
76
+ else
77
+ shownAt.set(t.nodeId, [t.summary]);
78
+ }
79
+ const atree = buildAsciiTree(merged.detail, {
80
+ expandedRuns: input.expandedRuns,
81
+ unfolded: input.unfolded,
82
+ viewportH: input.viewportH,
83
+ now: input.now,
84
+ keep: new Set(shownAt.keys()),
85
+ });
86
+ const rows = atree.rows.map((a) => ({
87
+ a,
88
+ tips: a.nodeId ? shownAt.get(a.nodeId) : undefined,
89
+ }));
90
+ // Panel order = transcript order: row k in the panel is the k-th agent
91
+ // you meet reading the tree top to bottom (creation order is history,
92
+ // not geography).
93
+ const rowIndexOf = new Map();
94
+ atree.rows.forEach((r, i) => {
95
+ if (r.nodeId && !rowIndexOf.has(r.nodeId))
96
+ rowIndexOf.set(r.nodeId, i);
97
+ });
98
+ const headByTree = new Map();
99
+ for (const m of members) {
100
+ const head = branchHeadOf(m);
101
+ if (head)
102
+ headByTree.set(m.summary.treeId, head);
103
+ }
104
+ const tips = [...shown]
105
+ .sort((a, b) => (rowIndexOf.get(a.nodeId) ?? Number.MAX_SAFE_INTEGER) -
106
+ (rowIndexOf.get(b.nodeId) ?? Number.MAX_SAFE_INTEGER))
107
+ .map((t, i) => {
108
+ const headNodeId = headByTree.get(t.summary.treeId);
109
+ const head = headNodeId ? merged.detail.nodes[headNodeId] : undefined;
110
+ // A resumed-but-untyped branch has no head yet: name it by where it
111
+ // sits, not by the root's title (which would read as a duplicate).
112
+ const atNode = t.summary.parentEntryId
113
+ ? merged.detail.nodes[t.summary.parentEntryId]
114
+ : undefined;
115
+ return {
116
+ ...t,
117
+ num: i + 1,
118
+ headNodeId,
119
+ headExcerpt: head
120
+ ? head.excerpt || head.kind
121
+ : atNode
122
+ ? `at: ${atNode.excerpt || atNode.kind}`
123
+ : undefined,
124
+ };
125
+ });
126
+ // Branch heads of the agents on display: each splits a map segment so its
127
+ // agent's dot hangs off its own branch, nested under its parent's.
128
+ const shownTreeIds = new Set(shown.map((t) => t.summary.treeId));
129
+ const heads = new Set([...headByTree].filter(([id]) => shownTreeIds.has(id)).map(([, h]) => h));
130
+ const skel = buildSkeleton(merged.detail, shownAt, heads);
131
+ return {
132
+ rows,
133
+ leafId: merged.detail.leafId,
134
+ tips,
135
+ parkedAt,
136
+ parkedCount,
137
+ atree,
138
+ detail: merged.detail,
139
+ skel,
140
+ memberOrder: family.map((t) => t.treeId),
141
+ pendingMembers,
142
+ };
143
+ }
144
+ /** Row index of a node id (cursor placement, tip jumps, search). */
145
+ export function cRowIndexOf(view, nodeId) {
146
+ if (!nodeId)
147
+ return -1;
148
+ return view.rows.findIndex((r) => r.a.nodeId === nodeId);
149
+ }
150
+ /**
151
+ * Which family session's file contains `nodeId` (labels/branches target it).
152
+ * Walks MERGE order (root first): the first writer's copy is the one the
153
+ * merged view displays, so writes must land there to be visible.
154
+ */
155
+ export function ownerOf(view, nodeId, detailOf) {
156
+ for (const id of view.memberOrder) {
157
+ if (detailOf(id)?.nodes[nodeId])
158
+ return id;
159
+ }
160
+ return undefined;
161
+ }
162
+ export { aContainerOf, aRowIndexOf };
163
+ //# sourceMappingURL=sessionview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionview.js","sourceRoot":"","sources":["../../../src/client/tree/sessionview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Conversation rendering: one ascii message tree with agent tips, plus the
3
+ * agents panel (metro mini + one row per branch tip).
4
+ *
5
+ * ○ build me a parser for the pines forest
6
+ * └─ · here's a first pass…
7
+ * ├─ ○ make it incremental — · 2m
8
+ * └─ ○ use embeddings instead — ● needs input · 40s
9
+ */
10
+ import { clipAnsi, visibleLength } from "../ansi.js";
11
+ import { FAINT, MUTED } from "../theme.js";
12
+ import { exchangeEndOf } from "./sessionview.js";
13
+ import { statusGlyph, statusSgr } from "../forest/view.js";
14
+ const RESET = "\x1b[0m";
15
+ import { renderConvMini, skelNodeFor } from "./convmap.js";
16
+ import { treeTitle, humanAge } from "../forest/sidebar.js";
17
+ function stateWord(t) {
18
+ return t.status === "running"
19
+ ? "working"
20
+ : t.status === "waiting"
21
+ ? t.seen
22
+ ? "waiting"
23
+ : "needs input"
24
+ : t.status;
25
+ }
26
+ /**
27
+ * Where the agents panel sits. `panelW` is the app's shared sidebar width
28
+ * (same setting, same divider as the forest sidebar) — 0 hides the panel;
29
+ * it also stays hidden while the tree has no forks worth mapping.
30
+ */
31
+ export function convLayout(view, width, height, panelW) {
32
+ // Narrow terminals cede more of the row to the transcript: the panel
33
+ // duplicates status the tip suffixes already carry, the words do not.
34
+ const cap = Math.max(24, Math.floor(width / (width < 100 ? 3 : 2)));
35
+ const sideW = view.tips.length > 1 && panelW > 0 ? Math.min(panelW, cap) : 0;
36
+ // The floating mini sits between the header and the agent rows; it only
37
+ // draws when there's honest room for both.
38
+ const wanted = Math.min(16, Math.max(6, view.tips.length * 2 + 2));
39
+ const spare = height - (view.tips.length + 6);
40
+ const mapH = sideW > 0 && spare >= 6 ? Math.min(wanted, spare) : 0;
41
+ const mapTop = 2;
42
+ const agentsTop = mapH > 0 ? mapTop + mapH + 1 : 2;
43
+ return { sideW, textH: height, mapTop, mapH, agentsTop };
44
+ }
45
+ /**
46
+ * Render the conversation: one ascii message tree; nodes where a session's
47
+ * branch currently ends ("tips") carry that agent's status glyph and state.
48
+ * The optional left panel lists the tips — agents, not messages.
49
+ */
50
+ export function renderConversation(view, opts) {
51
+ const lay = convLayout(view, opts.width, opts.height, opts.panelW);
52
+ const withMap = view.rows.length > lay.textH;
53
+ const textW = opts.width - (lay.sideW ? lay.sideW + 2 : 0) - (withMap ? 2 : 0);
54
+ const lines = [];
55
+ const end = Math.min(view.rows.length, opts.scroll + lay.textH);
56
+ for (let i = opts.scroll; i < end; i++) {
57
+ const { a: row, tips } = view.rows[i];
58
+ const isSel = i === opts.selected;
59
+ const prefix = `\x1b[${FAINT}m${row.prefix}${RESET}`;
60
+ let body = "";
61
+ if (row.kind === "node" && row.node) {
62
+ const n = row.node;
63
+ const tip = tips?.[0];
64
+ // Messages keep their role glyph ALWAYS — a user turn stays ○ no
65
+ // matter what agents are parked on it. Agent status is a suffix.
66
+ const glyph = row.isLeaf
67
+ ? "\x1b[32m●" + RESET
68
+ : n.label
69
+ ? "\x1b[33m◆" + RESET
70
+ : n.kind === "user"
71
+ ? "\x1b[36m○" + RESET
72
+ : `\x1b[${MUTED}m·${RESET}`;
73
+ const excerpt = n.excerpt || n.kind;
74
+ const textSgr = isSel
75
+ ? "7"
76
+ : row.onActive
77
+ ? n.kind === "user" || row.isLeaf
78
+ ? "1"
79
+ : "0"
80
+ : tip && !tip.seen
81
+ ? "1"
82
+ : n.kind === "user"
83
+ ? "36"
84
+ : MUTED;
85
+ body = `${glyph} \x1b[${textSgr}m${excerpt}${RESET}`;
86
+ if (n.label)
87
+ body += ` \x1b[33m[${n.label}]${RESET}`;
88
+ if (tips?.length) {
89
+ const t = tips[0];
90
+ const g = `\x1b[${statusSgr(t)}m${statusGlyph(t, opts.spinnerFrame)}${RESET}`;
91
+ // The default state stays quiet: a dormant, seen agent is just a
92
+ // glyph and an age. Only states that want something (working,
93
+ // needs input, crashed) earn a word — so the words mean something.
94
+ const quiet = t.status === "dormant" && t.seen;
95
+ const boldSt = !t.seen ? "\x1b[1m" : "";
96
+ const word = quiet
97
+ ? ""
98
+ : `${boldSt}\x1b[${!t.seen ? "0" : MUTED}m${stateWord(t)}${RESET}\x1b[${MUTED}m · `;
99
+ body += ` \x1b[${FAINT}m—${RESET} ${g} ${word}\x1b[${MUTED}m${humanAge(t.mtime, opts.now)}${RESET}`;
100
+ if (tips.length > 1)
101
+ body += ` \x1b[${MUTED}m(+${tips.length - 1})${RESET}`;
102
+ if (isSel)
103
+ body += ` \x1b[${MUTED}m(⏎ ${t.live ? "attach" : "resume"})${RESET}`;
104
+ }
105
+ else if (isSel && row.nodeId) {
106
+ const snapped = exchangeEndOf(view.detail, row.nodeId);
107
+ const parkedHere = view.parkedAt.get(snapped)?.length ?? view.parkedAt.get(row.nodeId)?.length;
108
+ body += parkedHere
109
+ ? ` \x1b[${MUTED}m(⏎ continue from here — reuses a parked agent)${RESET}`
110
+ : ` \x1b[${MUTED}m(⏎ new branch + agent from here)${RESET}`;
111
+ }
112
+ }
113
+ else if (row.kind === "elision") {
114
+ const sgr = isSel ? "7" : MUTED;
115
+ const tools = row.toolCount ?? 0;
116
+ const replies = (row.count ?? 0) - tools;
117
+ const what = tools && replies
118
+ ? `${tools} tool ${tools === 1 ? "call" : "calls"} · ${replies} ${replies === 1 ? "reply" : "replies"}`
119
+ : tools
120
+ ? `${tools} tool ${tools === 1 ? "call" : "calls"}`
121
+ : `${replies} ${replies === 1 ? "reply" : "replies"}`;
122
+ body = `\x1b[${sgr}m⚙ ⋯ ${what}${RESET}`;
123
+ if (isSel)
124
+ body += ` \x1b[${MUTED}m(⏎ expands)${RESET}`;
125
+ }
126
+ else if (row.kind === "fold") {
127
+ const sgr = isSel ? "7" : MUTED;
128
+ const age = row.foldAge ? ` · ${row.foldAge}` : "";
129
+ body =
130
+ `\x1b[${sgr}m○ ${row.foldExcerpt}${RESET} ` +
131
+ `\x1b[${MUTED}m· ${row.foldMsgs} msgs${age}${RESET} \x1b[${FAINT}m[+]${RESET}`;
132
+ }
133
+ lines.push(clipAnsi(` ${prefix}${body}`, textW));
134
+ }
135
+ while (lines.length < lay.textH)
136
+ lines.push("");
137
+ if (withMap) {
138
+ const total = view.rows.length;
139
+ for (let j = 0; j < lay.textH; j++) {
140
+ const lo = Math.floor((j / lay.textH) * total);
141
+ const hi = Math.max(lo + 1, Math.floor(((j + 1) / lay.textH) * total));
142
+ let ch = "│";
143
+ let sgr = FAINT;
144
+ for (let d = lo; d < hi; d++) {
145
+ const r = view.rows[d];
146
+ if (r.tips?.length) {
147
+ ch = "●";
148
+ sgr = statusSgr(r.tips[0]);
149
+ break;
150
+ }
151
+ if (r.a.kind === "fold" || r.a.prefix.includes("├─")) {
152
+ ch = "┿";
153
+ sgr = MUTED;
154
+ }
155
+ }
156
+ const inView = hi > opts.scroll && lo < opts.scroll + lay.textH;
157
+ if (inView && ch === "│") {
158
+ ch = "█";
159
+ sgr = "48;5;238;" + MUTED;
160
+ }
161
+ const line = lines[j];
162
+ const pad = Math.max(0, textW - visibleLength(line));
163
+ lines[j] = line + " ".repeat(pad) + ` \x1b[${sgr}m${ch}${RESET}`;
164
+ }
165
+ }
166
+ // Agents panel: the floating mini of the tree (skeleton: root, branch
167
+ // points, agent tips — the forest's visual language) above one row per
168
+ // branch tip. Agents, not messages: selecting jumps to that exact node.
169
+ let miniHits;
170
+ if (lay.sideW > 0) {
171
+ const side = [];
172
+ const n = view.tips.length;
173
+ side.push(clipAnsi(`\x1b[${MUTED}m agents on this tree — ${n}${RESET}`, lay.sideW));
174
+ side.push("");
175
+ const selRow = view.rows[opts.selected]?.a;
176
+ const selNode = selRow?.nodeId;
177
+ const selIdx = view.tips.findIndex((t) => t.nodeId === selNode);
178
+ if (lay.mapH > 0) {
179
+ const cursorSkelId = skelNodeFor(view.detail, view.skel, selRow?.nodeId ?? selRow?.runId ?? selRow?.foldId);
180
+ const mini = renderConvMini(view.skel, {
181
+ width: lay.sideW,
182
+ height: lay.mapH,
183
+ spinnerFrame: opts.spinnerFrame,
184
+ cursorSkelId,
185
+ });
186
+ miniHits = mini.hits;
187
+ for (const l of mini.lines)
188
+ side.push(l);
189
+ side.push("");
190
+ }
191
+ view.tips.forEach((t, i) => {
192
+ const s = t.summary;
193
+ const here = i === selIdx;
194
+ const glyph = `\x1b[${statusSgr(s)}m${statusGlyph(s, opts.spinnerFrame)}${RESET}`;
195
+ // A branch is named by the user message that began it, not by its
196
+ // latest reply — divergence is identity.
197
+ const label = t.headExcerpt ?? treeTitle(s).title;
198
+ const quiet = s.status === "dormant" && s.seen;
199
+ const info = quiet
200
+ ? `\x1b[${MUTED}m· ${humanAge(s.mtime, opts.now)}${RESET}`
201
+ : `\x1b[${MUTED}m· ${stateWord(s)} · ${humanAge(s.mtime, opts.now)}${RESET}`;
202
+ const nameSgr = !s.seen ? "1" : here ? "1" : s.live ? "0" : MUTED;
203
+ const line = ` ${glyph} \x1b[${nameSgr}m${label}${RESET} ${info}`;
204
+ if (here) {
205
+ const clipped = clipAnsi(line, lay.sideW);
206
+ const fill = Math.max(0, lay.sideW - visibleLength(clipped));
207
+ side.push(`\x1b[48;5;236m${clipped}${" ".repeat(fill)}${RESET}`);
208
+ }
209
+ else {
210
+ side.push(clipAnsi(line, lay.sideW));
211
+ }
212
+ });
213
+ if (view.parkedCount > 0) {
214
+ side.push(clipAnsi(`\x1b[${FAINT}m ⊘ ${view.parkedCount} parked agent${view.parkedCount === 1 ? "" : "s"} mid-tree${RESET}`, lay.sideW));
215
+ }
216
+ if (view.pendingMembers > 0) {
217
+ side.push("");
218
+ side.push(clipAnsi(`\x1b[${FAINT}m loading ${view.pendingMembers} branch…${RESET}`, lay.sideW));
219
+ }
220
+ side.push("");
221
+ side.push(clipAnsi(`\x1b[${FAINT}m click a branch to go there${RESET}`, lay.sideW));
222
+ for (let j = 0; j < lay.textH; j++) {
223
+ const sd = side[j] ?? "";
224
+ const fill = Math.max(0, lay.sideW - visibleLength(sd));
225
+ lines[j] = `${sd}${" ".repeat(fill)}\x1b[${FAINT}m│${RESET} ${lines[j] ?? ""}`;
226
+ }
227
+ }
228
+ return { lines, miniHits };
229
+ }
230
+ /** Clamp scroll so `selected` stays inside the viewport. */
231
+ export function scrollTo(selected, scroll, height) {
232
+ if (selected < scroll)
233
+ return selected;
234
+ if (selected >= scroll + height)
235
+ return selected - height + 1;
236
+ return scroll;
237
+ }
238
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../../src/client/tree/view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /** Persisted client UI state (~/.pines/ui.json): tolerant load, atomic-ish save. */
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { ensurePinesHome } from "../shared/paths.js";
4
+ import { uiStatePath } from "../shared/paths.js";
5
+ import { DEFAULT_STYLE_ID, FOREST_STYLES } from "./forest/styles.js";
6
+ export const DEFAULT_UI_STATE = {
7
+ sidebarVisible: true,
8
+ sidebarWidth: 32,
9
+ forestStyle: DEFAULT_STYLE_ID,
10
+ };
11
+ export function loadUiState() {
12
+ try {
13
+ // On disk the style is whatever an older version wrote, not today's union.
14
+ const raw = JSON.parse(readFileSync(uiStatePath(), "utf8"));
15
+ return {
16
+ sidebarVisible: typeof raw.sidebarVisible === "boolean"
17
+ ? raw.sidebarVisible
18
+ : DEFAULT_UI_STATE.sidebarVisible,
19
+ sidebarWidth: typeof raw.sidebarWidth === "number" && Number.isFinite(raw.sidebarWidth)
20
+ ? raw.sidebarWidth
21
+ : DEFAULT_UI_STATE.sidebarWidth,
22
+ // An unknown style (hand-edited file, or one dropped in a later
23
+ // version) falls back rather than rendering nothing. blueprint was
24
+ // absorbed into canopy — saved picks follow it there.
25
+ forestStyle: raw.forestStyle === "blueprint"
26
+ ? "canopy"
27
+ : FOREST_STYLES.some((s) => s.id === raw.forestStyle)
28
+ ? raw.forestStyle
29
+ : DEFAULT_UI_STATE.forestStyle,
30
+ };
31
+ }
32
+ catch {
33
+ return { ...DEFAULT_UI_STATE };
34
+ }
35
+ }
36
+ export function saveUiState(state) {
37
+ try {
38
+ ensurePinesHome();
39
+ writeFileSync(uiStatePath(), JSON.stringify(state, null, 2) + "\n");
40
+ }
41
+ catch {
42
+ // Persistence is best-effort; the session keeps its in-memory state.
43
+ }
44
+ }
45
+ //# sourceMappingURL=uistate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uistate.js","sourceRoot":"","sources":["../../src/client/uistate.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUrE,MAAM,CAAC,MAAM,gBAAgB,GAAY;IACvC,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,EAAE;IAChB,WAAW,EAAE,gBAAgB;CAC9B,CAAC;AAEF,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,2EAA2E;QAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAEzD,CAAC;QACF,OAAO;YACL,cAAc,EACZ,OAAO,GAAG,CAAC,cAAc,KAAK,SAAS;gBACrC,CAAC,CAAC,GAAG,CAAC,cAAc;gBACpB,CAAC,CAAC,gBAAgB,CAAC,cAAc;YACrC,YAAY,EACV,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC;gBACvE,CAAC,CAAC,GAAG,CAAC,YAAY;gBAClB,CAAC,CAAC,gBAAgB,CAAC,YAAY;YACnC,gEAAgE;YAChE,mEAAmE;YACnE,sDAAsD;YACtD,WAAW,EACT,GAAG,CAAC,WAAW,KAAK,WAAW;gBAC7B,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,WAAW,CAAC;oBACnD,CAAC,CAAE,GAAG,CAAC,WAA6B;oBACpC,CAAC,CAAC,gBAAgB,CAAC,WAAW;SACrC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC;QACH,eAAe,EAAE,CAAC;QAClB,aAAa,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;IACvE,CAAC;AACH,CAAC"}