@pellux/goodvibes-tui 1.21.1 → 1.22.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 +11 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +714 -177
- package/docs/foundation-artifacts/peer-contract.json +8 -4
- package/package.json +9 -7
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/service-posture.ts +6 -2
- package/src/config/run-daemon-config-migration.ts +46 -0
- package/src/core/conversation-compaction-render.ts +88 -0
- package/src/core/conversation-line-cache.ts +120 -92
- package/src/core/conversation-rendering.ts +290 -233
- package/src/core/conversation-turn-structure.ts +464 -0
- package/src/core/conversation-types.ts +8 -9
- package/src/daemon/cli.ts +71 -5
- package/src/input/commands/local-runtime.ts +14 -14
- package/src/input/commands/operator-rpc.ts +15 -4
- package/src/input/daemon-owned-settings-descriptions.ts +53 -0
- package/src/input/handler-content-actions.ts +4 -2
- package/src/input/handler-onboarding-daemon-adopt.ts +10 -0
- package/src/input/onboarding/onboarding-feature-units.ts +38 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +1 -0
- package/src/input/search.ts +1 -1
- package/src/input/settings-modal-data.ts +15 -85
- package/src/input/settings-modal-search.ts +99 -0
- package/src/input/settings-modal-types.ts +2 -1
- package/src/renderer/conversation-surface.ts +37 -11
- package/src/renderer/conversation-tree.ts +179 -0
- package/src/renderer/settings-modal-helpers.ts +5 -3
- package/src/renderer/settings-modal.ts +8 -0
- package/src/renderer/tool-call.ts +38 -5
- package/src/runtime/cloudflare-control-plane.ts +4 -1
- package/src/runtime/feature-settings.ts +7 -0
- package/src/runtime/index.ts +22 -1
- package/src/runtime/legacy-daemon-migration.ts +2 -0
- package/src/verification/verification-ledger.ts +115 -2
- package/src/version.ts +1 -1
- 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
|
-
}
|