@pellux/goodvibes-tui 1.21.1 → 1.23.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 (38) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +714 -177
  4. package/docs/foundation-artifacts/peer-contract.json +8 -4
  5. package/package.json +9 -7
  6. package/src/cli/entrypoint.ts +6 -0
  7. package/src/cli/service-posture.ts +6 -2
  8. package/src/config/run-daemon-config-migration.ts +46 -0
  9. package/src/core/conversation-compaction-render.ts +88 -0
  10. package/src/core/conversation-line-cache.ts +120 -92
  11. package/src/core/conversation-rendering.ts +290 -233
  12. package/src/core/conversation-turn-structure.ts +464 -0
  13. package/src/core/conversation-types.ts +8 -9
  14. package/src/daemon/cli.ts +71 -5
  15. package/src/input/commands/local-runtime.ts +14 -14
  16. package/src/input/commands/operator-rpc.ts +15 -4
  17. package/src/input/daemon-owned-settings-descriptions.ts +53 -0
  18. package/src/input/handler-content-actions.ts +4 -2
  19. package/src/input/handler-onboarding-daemon-adopt.ts +10 -0
  20. package/src/input/onboarding/onboarding-feature-units.ts +38 -0
  21. package/src/input/onboarding/onboarding-wizard-types.ts +1 -0
  22. package/src/input/search.ts +1 -1
  23. package/src/input/settings-modal-data.ts +15 -85
  24. package/src/input/settings-modal-search.ts +99 -0
  25. package/src/input/settings-modal-types.ts +2 -1
  26. package/src/renderer/conversation-surface.ts +37 -11
  27. package/src/renderer/conversation-tree.ts +179 -0
  28. package/src/renderer/settings-modal-helpers.ts +5 -3
  29. package/src/renderer/settings-modal.ts +8 -0
  30. package/src/renderer/tool-call.ts +38 -5
  31. package/src/runtime/cloudflare-control-plane.ts +4 -1
  32. package/src/runtime/feature-settings.ts +7 -0
  33. package/src/runtime/index.ts +22 -1
  34. package/src/runtime/legacy-daemon-migration.ts +2 -0
  35. package/src/runtime/services.ts +2 -2
  36. package/src/verification/verification-ledger.ts +115 -2
  37. package/src/version.ts +1 -1
  38. package/src/core/conversation-tool-groups.ts +0 -152
@@ -1,152 +0,0 @@
1
- /**
2
- * conversation-tool-groups.ts — detects runs of consecutive tool-result
3
- * messages that belong to one assistant turn, so the transcript can fold them
4
- * under a single collapsible group header instead of rendering one
5
- * "tool result" header per call. Before this module, an assistant turn with
6
- * N tool calls produced N independent header+body blocks in a row — visual
7
- * spam once N is more than one or two.
8
- *
9
- * A run only counts as a group when it has >= 2 matched tool-result messages:
10
- * a single result renders exactly as it always has (see
11
- * renderConversationToolMessage in conversation-rendering.ts, which only
12
- * consults this module's output when a membership entry exists).
13
- *
14
- * Pure function of the message slice — no rendering, no width, no side
15
- * effects. Called once per render pass (appendConversationMessages /
16
- * MessageLineCache.renderInto) over the currently-visible message slice, with
17
- * the absolute index of the slice's first message so group keys and member
18
- * indexes line up with the same collapseKey / blockRegistry indices a cold
19
- * render would produce (messages may be a post-clearDisplay slice, not the
20
- * full snapshot — see appendConversationMessages's msgIndexOffset doc).
21
- */
22
-
23
- import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
24
- import { renderExpandedToolResultLines } from '../renderer/tool-result-expanded-lines.ts';
25
-
26
- type Message = ConversationMessageSnapshot;
27
-
28
- /** One consolidated run of tool-result messages sharing one assistant turn. */
29
- export interface ToolGroup {
30
- /** Absolute index of the assistant message that owns this group. */
31
- readonly assistantIdx: number;
32
- /** Absolute indexes of the matched tool-result messages, in order. */
33
- readonly toolMessageIndexes: readonly number[];
34
- }
35
-
36
- /**
37
- * Per-tool-message membership, keyed by the ABSOLUTE index of the tool-result
38
- * message (the owning assistant message is never a key — only its tool-result
39
- * children are). `isFirst` marks the member whose render site owns the
40
- * group's header line and BlockMeta; every other member renders nothing while
41
- * the group is collapsed (see renderConversationToolMessage).
42
- */
43
- export interface ToolGroupMembership {
44
- readonly groupKey: string;
45
- readonly isFirst: boolean;
46
- /** Matched tool-result messages in the group (the header's "N tools"). */
47
- readonly toolCount: number;
48
- /** Sum of each member's EXPANDED render line count (the header's "N lines") —
49
- * the same post-render count each member's own "N lines" badge shows, so
50
- * the group total matches what expanding every member actually reveals,
51
- * not a raw-content estimate (a folded JSON result can pretty-print to far
52
- * more lines than its raw content has). */
53
- readonly totalLines: number;
54
- /** Each member's tool name, in order (falls back to 'tool' when a result
55
- * carries no name) — the header dedupes these into e.g. "read×3 exec". */
56
- readonly toolNames: readonly string[];
57
- /**
58
- * Absolute indexes of every tool-result message in the group, in order —
59
- * the same list for every member. Carried onto the group's BlockMeta so a
60
- * single expand pass can also open each member's own collapse key
61
- * (`msg_<idx>`), since a folded member pushes no BlockMeta of its own to
62
- * toggle individually (see local-runtime.ts's toggleBlocks).
63
- */
64
- readonly memberIndexes: readonly number[];
65
- }
66
-
67
- /**
68
- * Scan `messages` for assistant-message runs of >=2 consecutive matching
69
- * tool-result messages. A run breaks on the first message that isn't a
70
- * matching tool result: any non-tool message (interleaved user/system/
71
- * assistant content), or a tool message whose callId isn't one of the
72
- * assistant's own toolCalls ids (a result belonging to a different turn never
73
- * gets folded into a group it doesn't belong to).
74
- */
75
- export function detectToolGroups(
76
- messages: readonly Message[],
77
- indexOffset = 0,
78
- ): ToolGroup[] {
79
- const groups: ToolGroup[] = [];
80
-
81
- for (let i = 0; i < messages.length; i++) {
82
- const message = messages[i];
83
- if (message.role !== 'assistant' || !message.toolCalls || message.toolCalls.length === 0) continue;
84
- const callIds = new Set(
85
- message.toolCalls
86
- .map((tc) => tc.id)
87
- .filter((id): id is string => id !== undefined),
88
- );
89
- if (callIds.size === 0) continue;
90
-
91
- const toolMessageIndexes: number[] = [];
92
- let j = i + 1;
93
- while (j < messages.length) {
94
- const next = messages[j];
95
- if (next.role !== 'tool' || !next.callId || !callIds.has(next.callId)) break;
96
- toolMessageIndexes.push(indexOffset + j);
97
- j++;
98
- }
99
-
100
- if (toolMessageIndexes.length >= 2) {
101
- groups.push({ assistantIdx: indexOffset + i, toolMessageIndexes });
102
- }
103
- }
104
-
105
- return groups;
106
- }
107
-
108
- /** Build the per-tool-message membership lookup used by the renderer/cache.
109
- * `width` must match the width the transcript is actually rendered at — it
110
- * feeds the same expanded-render line count each member's own badge uses
111
- * (see renderExpandedToolResultLines), so the group total is never computed
112
- * against a different width than what the member badges show. */
113
- export function buildToolGroupMembership(
114
- groups: readonly ToolGroup[],
115
- messages: readonly Message[],
116
- indexOffset: number,
117
- width: number,
118
- ): ReadonlyMap<number, ToolGroupMembership> {
119
- const membership = new Map<number, ToolGroupMembership>();
120
- for (const group of groups) {
121
- const groupKey = `group_${group.assistantIdx}`;
122
- let totalLines = 0;
123
- const toolNames: string[] = [];
124
- for (const absIdx of group.toolMessageIndexes) {
125
- const message = messages[absIdx - indexOffset];
126
- if (message && message.role === 'tool') {
127
- totalLines += renderExpandedToolResultLines(message.content, width).length;
128
- toolNames.push(message.toolName ?? 'tool');
129
- }
130
- }
131
- group.toolMessageIndexes.forEach((absIdx, idx) => {
132
- membership.set(absIdx, {
133
- groupKey,
134
- isFirst: idx === 0,
135
- toolCount: group.toolMessageIndexes.length,
136
- totalLines,
137
- toolNames,
138
- memberIndexes: group.toolMessageIndexes,
139
- });
140
- });
141
- }
142
- return membership;
143
- }
144
-
145
- /** Convenience wrapper: detect groups, then build the membership map in one call. */
146
- export function computeToolGroupMembership(
147
- messages: readonly Message[],
148
- indexOffset = 0,
149
- width = 80,
150
- ): ReadonlyMap<number, ToolGroupMembership> {
151
- return buildToolGroupMembership(detectToolGroups(messages, indexOffset), messages, indexOffset, width);
152
- }