@pellux/goodvibes-tui 0.18.12 → 0.18.13
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 +50 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/config/index.ts +1 -138
- package/src/config/subscription-providers.ts +1 -127
- package/src/core/conversation-rendering.ts +3 -3
- package/src/core/conversation.ts +176 -423
- package/src/core/history.ts +45 -0
- package/src/core/orchestrator.ts +3 -735
- package/src/core/system-message-router.ts +19 -58
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed.ts +1 -1
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +1 -1
- package/src/input/search.ts +1 -1
- package/src/input/selection.ts +2 -2
- package/src/main.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +3 -3
- package/src/panels/agent-logs-panel.ts +3 -3
- package/src/panels/approval-panel.ts +2 -2
- package/src/panels/automation-control-panel.ts +3 -3
- package/src/panels/base-panel.ts +14 -14
- package/src/panels/builtin/operations.ts +1 -1
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/builtin/shared.ts +3 -3
- package/src/panels/cockpit-panel.ts +2 -2
- package/src/panels/communication-panel.ts +3 -3
- package/src/panels/context-visualizer-panel.ts +2 -2
- package/src/panels/control-plane-panel.ts +3 -3
- package/src/panels/cost-tracker-panel.ts +3 -3
- package/src/panels/debug-panel.ts +2 -2
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/docs-panel.ts +1 -1
- package/src/panels/eval-panel.ts +2 -2
- package/src/panels/file-explorer-panel.ts +3 -3
- package/src/panels/file-preview-panel.ts +3 -3
- package/src/panels/forensics-panel.ts +2 -2
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hooks-panel.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/intelligence-panel.ts +2 -2
- package/src/panels/knowledge-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +2 -2
- package/src/panels/marketplace-panel.ts +1 -1
- package/src/panels/mcp-panel.ts +3 -3
- package/src/panels/memory-panel.ts +1 -1
- package/src/panels/ops-control-panel.ts +3 -3
- package/src/panels/ops-strategy-panel.ts +2 -2
- package/src/panels/orchestration-panel.ts +2 -2
- package/src/panels/panel-list-panel.ts +6 -6
- package/src/panels/plan-dashboard-panel.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/policy-panel.ts +2 -2
- package/src/panels/polish.ts +3 -3
- package/src/panels/provider-accounts-panel.ts +2 -2
- package/src/panels/provider-health-panel.ts +2 -2
- package/src/panels/provider-stats-panel.ts +3 -3
- package/src/panels/remote-panel.ts +3 -3
- package/src/panels/routes-panel.ts +3 -3
- package/src/panels/sandbox-panel.ts +2 -2
- package/src/panels/schedule-panel.ts +1 -1
- package/src/panels/security-panel.ts +2 -2
- package/src/panels/services-panel.ts +2 -2
- package/src/panels/session-browser-panel.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/skills-panel.ts +6 -6
- package/src/panels/subscription-panel.ts +2 -2
- package/src/panels/symbol-outline-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +4 -4
- package/src/panels/tasks-panel.ts +2 -2
- package/src/panels/thinking-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +1 -1
- package/src/panels/tool-inspector-panel.ts +3 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/watchers-panel.ts +3 -3
- package/src/panels/welcome-panel.ts +1 -1
- package/src/panels/worktree-panel.ts +2 -2
- package/src/panels/wrfc-panel.ts +3 -3
- package/src/permissions/prompt.ts +3 -22
- package/src/plugins/loader.ts +15 -304
- package/src/renderer/agent-detail-modal.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +2 -2
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/bottom-bar.ts +2 -2
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +2 -2
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +2 -2
- package/src/renderer/file-tree.ts +2 -2
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/live-tail-modal.ts +1 -1
- package/src/renderer/markdown.ts +2 -2
- package/src/renderer/modal-factory.ts +3 -3
- package/src/renderer/model-picker-overlay.ts +2 -2
- package/src/renderer/overlay-box.ts +2 -2
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-picker-overlay.ts +2 -2
- package/src/renderer/panel-tab-bar.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +2 -2
- package/src/renderer/process-modal.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +2 -2
- package/src/renderer/progress.ts +2 -2
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +2 -2
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal.ts +2 -2
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +2 -2
- package/src/renderer/text-layout.ts +1 -1
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/ui-factory.ts +2 -2
- package/src/runtime/bootstrap-command-context.ts +4 -5
- package/src/runtime/bootstrap-command-parts.ts +1 -3
- package/src/runtime/bootstrap-core.ts +3 -2
- package/src/runtime/bootstrap-hook-bridge.ts +15 -174
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +1 -1
- package/src/runtime/context.ts +4 -20
- package/src/runtime/diagnostics/panels/index.ts +1 -1
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
- package/src/runtime/perf/panel-contracts.ts +32 -0
- package/src/runtime/perf/panel-health-monitor.ts +18 -0
- package/src/runtime/services.ts +4 -4
- package/src/runtime/store/domains/conversation.ts +1 -181
- package/src/runtime/store/domains/permissions.ts +1 -143
- package/src/runtime/store/helpers/reducers/conversation.ts +1 -228
- package/src/runtime/store/helpers/reducers/lifecycle.ts +1 -440
- package/src/runtime/store/selectors/index.ts +11 -6
- package/src/runtime/store/state.ts +12 -4
- package/src/runtime/ui-events.ts +46 -0
- package/src/runtime/ui-services.ts +1 -1
- package/src/shell/ui-openers.ts +1 -1
- package/src/tools/index.ts +1 -186
- package/src/types/grid.ts +48 -0
- package/src/utils/clipboard.ts +21 -0
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +185 -0
- package/src/version.ts +1 -1
- package/src/daemon/facade-composition.ts +0 -398
- package/src/daemon/facade.ts +0 -638
- package/src/daemon/surface-policy.ts +0 -60
- package/src/daemon/types.ts +0 -191
- package/src/runtime/ui-read-models-core.ts +0 -95
- package/src/runtime/ui-read-models-operations.ts +0 -203
package/src/core/conversation.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { InfiniteBuffer } from '
|
|
2
|
-
import { createEmptyLine, type Line, type Cell } from '
|
|
3
|
-
import type { SplashOptions } from '
|
|
1
|
+
import { InfiniteBuffer } from './history.ts';
|
|
2
|
+
import { createEmptyLine, type Line, type Cell } from '../types/grid.ts';
|
|
3
|
+
import type { SplashOptions } from '../utils/splash-lines.ts';
|
|
4
4
|
import type { ToolCall, ToolResult } from '@pellux/goodvibes-sdk/platform/types/tools';
|
|
5
5
|
import type { ProviderMessage, ContentPart } from '@pellux/goodvibes-sdk/platform/providers/interface';
|
|
6
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
7
6
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
8
|
-
import type { SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core/session-memory';
|
|
9
|
-
import { SessionLineageTracker } from '@pellux/goodvibes-sdk/platform/core/session-lineage';
|
|
10
|
-
import { buildTranscriptEventIndex } from '@pellux/goodvibes-sdk/platform/core/transcript-events/index';
|
|
11
7
|
import type { TranscriptEventKind } from '@pellux/goodvibes-sdk/platform/core/transcript-events/index';
|
|
12
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ConversationManager as SdkConversationManager,
|
|
10
|
+
type BlockMeta as SdkBlockMeta,
|
|
11
|
+
} from '@pellux/goodvibes-sdk/platform/core/conversation';
|
|
13
12
|
import {
|
|
14
13
|
addConversationSplashScreen,
|
|
15
14
|
appendConversationMessages,
|
|
@@ -21,64 +20,42 @@ import {
|
|
|
21
20
|
renderConversationUserMessage,
|
|
22
21
|
} from './conversation-rendering.ts';
|
|
23
22
|
import { renderMarkdown } from '../renderer/markdown.ts';
|
|
24
|
-
import {
|
|
25
|
-
cloneBranchMap,
|
|
26
|
-
cloneMessages,
|
|
27
|
-
deriveConversationTitle,
|
|
28
|
-
messagesToInternal,
|
|
29
|
-
restoreBranchMap,
|
|
30
|
-
} from '@pellux/goodvibes-sdk/platform/core/conversation-utils';
|
|
31
|
-
import { applyDiffContent, parseDiffForApply } from '@pellux/goodvibes-sdk/platform/core/conversation-diff';
|
|
32
23
|
|
|
33
24
|
/**
|
|
34
|
-
* ConversationManager -
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* the buffer is only actually reconstructed when getDisplayBlocks() is called
|
|
39
|
-
* or when the width changes. This avoids O(n) rebuilds per turn in long sessions.
|
|
25
|
+
* ConversationManager - TUI subclass of the SDK's ConversationManager.
|
|
26
|
+
* Adds InfiniteBuffer history, block registry, collapse state, width tracking,
|
|
27
|
+
* dirty flag, display methods, splash screen, error/event navigation, and
|
|
28
|
+
* Line[]-based rendering atop the SDK's message management.
|
|
40
29
|
*/
|
|
41
|
-
export type TokenUsage = { inputTokens: number; outputTokens: number; cacheReadTokens?: number; cacheWriteTokens?: number };
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
// Re-export SDK types for backward compatibility
|
|
32
|
+
export type {
|
|
33
|
+
TokenUsage,
|
|
34
|
+
ConversationMessageSnapshot,
|
|
35
|
+
ConversationTitleSource,
|
|
36
|
+
} from '@pellux/goodvibes-sdk/platform/core/conversation';
|
|
44
37
|
|
|
45
|
-
export type
|
|
46
|
-
| { role: 'user'; content: string | ContentPart[]; cancelled?: boolean }
|
|
47
|
-
| AssistantMessage
|
|
48
|
-
| { role: 'system'; content: string }
|
|
49
|
-
| { role: 'tool'; callId: string; content: string; toolName?: string };
|
|
50
|
-
|
|
51
|
-
type Message = ConversationMessageSnapshot;
|
|
52
|
-
export type ConversationTitleSource = 'system' | 'user';
|
|
38
|
+
export type { SdkBlockMeta };
|
|
53
39
|
|
|
54
|
-
/**
|
|
55
|
-
export interface BlockMeta {
|
|
40
|
+
/** TUI extends the SDK BlockMeta with rendering position fields. */
|
|
41
|
+
export interface BlockMeta extends SdkBlockMeta {
|
|
56
42
|
/** Index of this block (increments per renderable block). */
|
|
57
43
|
blockIndex: number;
|
|
58
|
-
/** Type of block content. */
|
|
59
|
-
type: 'tool' | 'code' | 'diff' | 'thinking';
|
|
60
44
|
/** First rendered line index in the history buffer. */
|
|
61
45
|
startLine: number;
|
|
62
46
|
/** Number of rendered lines (when not collapsed). */
|
|
63
47
|
lineCount: number;
|
|
64
|
-
/** Raw text content (code source, tool output, diff text). */
|
|
65
|
-
rawContent: string;
|
|
66
48
|
/** Stable key for collapse state persistence across rebuilds (e.g. msg_N). */
|
|
67
49
|
collapseKey: string;
|
|
68
|
-
/** File path for diff blocks. */
|
|
69
|
-
filePath?: string;
|
|
70
|
-
/** Parsed diff for apply: original/updated sections. */
|
|
71
|
-
diffOriginal?: string;
|
|
72
|
-
diffUpdated?: string;
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
|
|
52
|
+
// Import internal types needed for rendering helpers
|
|
53
|
+
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core/conversation';
|
|
54
|
+
type Message = ConversationMessageSnapshot;
|
|
55
|
+
|
|
56
|
+
export class ConversationManager extends SdkConversationManager {
|
|
76
57
|
public history = new InfiniteBuffer();
|
|
77
|
-
|
|
78
|
-
private _title = '';
|
|
79
|
-
private _titleSource: ConversationTitleSource = 'system';
|
|
80
|
-
private messages: Message[] = [];
|
|
81
|
-
private getWidth: () => number;
|
|
58
|
+
private _getWidth: () => number;
|
|
82
59
|
/** Tracks the rendered width; a change invalidates the full history. */
|
|
83
60
|
private lastRenderedWidth = 0;
|
|
84
61
|
/** When true the buffer needs to be rebuilt before the next display. */
|
|
@@ -86,11 +63,7 @@ export class ConversationManager {
|
|
|
86
63
|
/** Index of the first message not yet appended to the buffer. */
|
|
87
64
|
private appendedUpTo = 0;
|
|
88
65
|
/** Optional config manager for display settings. */
|
|
89
|
-
private
|
|
90
|
-
/** Session memory store wired by the runtime composition root. */
|
|
91
|
-
private sessionMemoryStore: Pick<SessionMemoryStore, 'list'> | null = null;
|
|
92
|
-
/** Session lineage tracker wired by the runtime composition root. */
|
|
93
|
-
private sessionLineageTracker: SessionLineageTracker = new SessionLineageTracker();
|
|
66
|
+
private _configManager: ConfigManager | null = null;
|
|
94
67
|
/** Collapse state: stable key (msg_N) -> collapsed (true = collapsed). */
|
|
95
68
|
private collapseState: Map<string, boolean> = new Map();
|
|
96
69
|
/** Block registry: track rendered blocks for copy/apply. */
|
|
@@ -101,196 +74,96 @@ export class ConversationManager {
|
|
|
101
74
|
private errorLineRegistry: number[] = [];
|
|
102
75
|
/** Streaming block start line in history buffer (for incremental streaming update). */
|
|
103
76
|
private streamingStartLine = -1;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
private branches: Map<string, Message[]> = new Map();
|
|
108
|
-
/** Name of the currently active branch. */
|
|
109
|
-
private currentBranch: string = 'main';
|
|
77
|
+
|
|
78
|
+
public suppressSplash: boolean = false;
|
|
79
|
+
public splashOptions: SplashOptions = {};
|
|
110
80
|
|
|
111
81
|
constructor(
|
|
112
82
|
getWidth: () => number = () => process.stdout.columns || 80,
|
|
113
83
|
configManager?: ConfigManager,
|
|
114
84
|
) {
|
|
115
|
-
|
|
116
|
-
this.
|
|
85
|
+
super();
|
|
86
|
+
this._getWidth = getWidth;
|
|
87
|
+
this._configManager = configManager ?? null;
|
|
117
88
|
}
|
|
118
89
|
|
|
119
90
|
/** Wire in a config manager after construction (e.g. from main.ts). */
|
|
120
91
|
public setConfigManager(cm: ConfigManager): void {
|
|
121
|
-
this.
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Wire in the session memory store used for compaction summaries. */
|
|
125
|
-
public setSessionMemoryStore(store: Pick<SessionMemoryStore, 'list'>): void {
|
|
126
|
-
this.sessionMemoryStore = store;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Wire in the session lineage tracker used for compaction output. */
|
|
130
|
-
public setSessionLineageTracker(tracker: SessionLineageTracker): void {
|
|
131
|
-
this.sessionLineageTracker = tracker;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Read the session memory store used for compaction summaries. */
|
|
135
|
-
public getSessionMemoryStore(): Pick<SessionMemoryStore, 'list'> | null {
|
|
136
|
-
return this.sessionMemoryStore;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Read the session lineage tracker used for compaction output. */
|
|
140
|
-
public getSessionLineageTracker(): SessionLineageTracker {
|
|
141
|
-
return this.sessionLineageTracker;
|
|
92
|
+
this._configManager = cm;
|
|
142
93
|
}
|
|
143
94
|
|
|
144
95
|
/** Update the width provider so shell layout can own transcript width. */
|
|
145
96
|
public setWidthProvider(getWidth: () => number): void {
|
|
146
|
-
this.
|
|
97
|
+
this._getWidth = getWidth;
|
|
147
98
|
this.markDirty();
|
|
148
99
|
}
|
|
149
100
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
for (const m of this.messages) {
|
|
154
|
-
if (m.role === 'system') continue; // System messages go via systemPrompt param
|
|
155
|
-
if (m.role === 'user') {
|
|
156
|
-
result.push({ role: 'user', content: m.content as string | ContentPart[] });
|
|
157
|
-
} else if (m.role === 'assistant') {
|
|
158
|
-
result.push({ role: 'assistant', content: m.content, toolCalls: m.toolCalls });
|
|
159
|
-
} else if (m.role === 'tool') {
|
|
160
|
-
result.push({ role: 'tool', callId: m.callId, content: m.content, ...(m.toolName ? { name: m.toolName } : {}) });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private findToolName(callId: string): string | undefined {
|
|
167
|
-
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
168
|
-
const message = this.messages[i];
|
|
169
|
-
if (message.role !== 'assistant' || !message.toolCalls?.length) continue;
|
|
170
|
-
const match = message.toolCalls.find((call) => call.id === callId);
|
|
171
|
-
if (match?.name) return match.name;
|
|
172
|
-
}
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
// Overrides: add markDirty() to message mutations
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
175
104
|
|
|
176
|
-
public addUserMessage(content: string | ContentPart[]): void {
|
|
177
|
-
|
|
178
|
-
if (this._title === '' && typeof content === 'string' && content.trim().length > 0) {
|
|
179
|
-
this.setSystemTitle(deriveConversationTitle(content));
|
|
180
|
-
}
|
|
181
|
-
this.messages.push({ role: 'user', content });
|
|
182
|
-
// Clear undo stack when new user input is added (can't redo past new input)
|
|
183
|
-
this.undoStack = [];
|
|
105
|
+
public override addUserMessage(content: string | ContentPart[]): void {
|
|
106
|
+
super.addUserMessage(content);
|
|
184
107
|
this.markDirty();
|
|
185
108
|
}
|
|
186
109
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const toolName = this.findToolName(r.callId);
|
|
200
|
-
this.messages.push({
|
|
201
|
-
role: 'tool',
|
|
202
|
-
callId: r.callId,
|
|
203
|
-
content,
|
|
204
|
-
...(toolName ? { toolName } : {}),
|
|
205
|
-
});
|
|
206
|
-
}
|
|
110
|
+
public override addAssistantMessage(
|
|
111
|
+
content: string,
|
|
112
|
+
opts?: {
|
|
113
|
+
toolCalls?: ToolCall[];
|
|
114
|
+
reasoningContent?: string;
|
|
115
|
+
reasoningSummary?: string;
|
|
116
|
+
usage?: import('@pellux/goodvibes-sdk/platform/core/conversation').TokenUsage;
|
|
117
|
+
model?: string;
|
|
118
|
+
provider?: string;
|
|
119
|
+
},
|
|
120
|
+
): void {
|
|
121
|
+
super.addAssistantMessage(content, opts);
|
|
207
122
|
this.markDirty();
|
|
208
123
|
}
|
|
209
124
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* non-user messages). Pushes the removed messages onto the undo stack.
|
|
213
|
-
* Returns true if a turn was removed, false if there was nothing to undo.
|
|
214
|
-
*/
|
|
215
|
-
public undo(): boolean {
|
|
216
|
-
// Find the index of the last user message
|
|
217
|
-
let lastUserIdx = -1;
|
|
218
|
-
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
219
|
-
if (this.messages[i].role === 'user') {
|
|
220
|
-
lastUserIdx = i;
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (lastUserIdx === -1) return false;
|
|
225
|
-
|
|
226
|
-
// Collect the turn: user message + everything that follows (assistant, tool)
|
|
227
|
-
const turn = this.messages.splice(lastUserIdx);
|
|
228
|
-
this.undoStack.push(turn);
|
|
125
|
+
public override addToolResults(results: ToolResult[]): void {
|
|
126
|
+
super.addToolResults(results);
|
|
229
127
|
this.markDirty();
|
|
230
|
-
return true;
|
|
231
128
|
}
|
|
232
129
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
* Returns true if a turn was restored, false if the undo stack is empty.
|
|
236
|
-
*/
|
|
237
|
-
public redo(): boolean {
|
|
238
|
-
if (this.undoStack.length === 0) return false;
|
|
239
|
-
const turn = this.undoStack.pop()!;
|
|
240
|
-
this.messages.push(...turn);
|
|
130
|
+
public override addSystemMessage(content: string): void {
|
|
131
|
+
super.addSystemMessage(content);
|
|
241
132
|
this.markDirty();
|
|
242
|
-
return true;
|
|
243
133
|
}
|
|
244
134
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
public getLastUserMessage(): string | null {
|
|
250
|
-
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
251
|
-
if (this.messages[i].role === 'user') {
|
|
252
|
-
const content = this.messages[i].content;
|
|
253
|
-
return typeof content === 'string' ? content : null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Returns the current number of messages (for rollback tracking). */
|
|
260
|
-
public getMessageCount(): number {
|
|
261
|
-
return this.messages.length;
|
|
135
|
+
public override undo(): boolean {
|
|
136
|
+
const result = super.undo();
|
|
137
|
+
if (result) this.markDirty();
|
|
138
|
+
return result;
|
|
262
139
|
}
|
|
263
140
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
this.markDirty();
|
|
269
|
-
}
|
|
141
|
+
public override redo(): boolean {
|
|
142
|
+
const result = super.redo();
|
|
143
|
+
if (result) this.markDirty();
|
|
144
|
+
return result;
|
|
270
145
|
}
|
|
271
146
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (this.messages[i].role === 'user') {
|
|
276
|
-
(this.messages[i] as { cancelled?: boolean }).cancelled = true;
|
|
277
|
-
this.markDirty();
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
147
|
+
public override removeMessagesAfter(count: number): void {
|
|
148
|
+
super.removeMessagesAfter(count);
|
|
149
|
+
this.markDirty();
|
|
281
150
|
}
|
|
282
151
|
|
|
283
|
-
public
|
|
284
|
-
|
|
152
|
+
public override markLastUserMessageCancelled(): void {
|
|
153
|
+
super.markLastUserMessageCancelled();
|
|
285
154
|
this.markDirty();
|
|
286
155
|
}
|
|
287
156
|
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
// Streaming overrides: add rendering tracking
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
|
|
288
161
|
/**
|
|
289
162
|
* startStreamingBlock - Add a placeholder assistant message for incremental display.
|
|
290
163
|
* Called when streaming begins.
|
|
291
164
|
*/
|
|
292
|
-
public startStreamingBlock(): void {
|
|
293
|
-
|
|
165
|
+
public override startStreamingBlock(): void {
|
|
166
|
+
super.startStreamingBlock();
|
|
294
167
|
this.markDirty();
|
|
295
168
|
// Record the line where the streaming block starts so updates can be incremental
|
|
296
169
|
this.flushHistory();
|
|
@@ -302,19 +175,14 @@ export class ConversationManager {
|
|
|
302
175
|
* Called per-delta during streaming. Does NOT trigger a full rebuild — instead it
|
|
303
176
|
* directly updates the history buffer from streamingStartLine onward.
|
|
304
177
|
*/
|
|
305
|
-
public updateStreamingBlock(content: string): void {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const rendered = renderMarkdown(content, width);
|
|
314
|
-
this.history.addLines(rendered);
|
|
315
|
-
}
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
178
|
+
public override updateStreamingBlock(content: string): void {
|
|
179
|
+
super.updateStreamingBlock(content);
|
|
180
|
+
// Incrementally update the history buffer instead of full rebuild
|
|
181
|
+
if (this.streamingStartLine >= 0) {
|
|
182
|
+
const width = this._getWidth();
|
|
183
|
+
this.history.truncateToLine(this.streamingStartLine);
|
|
184
|
+
const rendered = renderMarkdown(content, width);
|
|
185
|
+
this.history.addLines(rendered);
|
|
318
186
|
}
|
|
319
187
|
}
|
|
320
188
|
|
|
@@ -322,17 +190,90 @@ export class ConversationManager {
|
|
|
322
190
|
* finalizeStreamingBlock - Remove the streaming placeholder.
|
|
323
191
|
* The orchestrator calls addAssistantMessage immediately after with the final content.
|
|
324
192
|
*/
|
|
325
|
-
public finalizeStreamingBlock(): void {
|
|
326
|
-
|
|
327
|
-
if (this.messages[i].role === 'assistant') {
|
|
328
|
-
this.messages.splice(i, 1);
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
193
|
+
public override finalizeStreamingBlock(): void {
|
|
194
|
+
super.finalizeStreamingBlock();
|
|
332
195
|
this.streamingStartLine = -1;
|
|
333
196
|
this.markDirty();
|
|
334
197
|
}
|
|
335
198
|
|
|
199
|
+
// -------------------------------------------------------------------------
|
|
200
|
+
// Overrides: reset / replace / branch operations that also affect display
|
|
201
|
+
// -------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* resetAll - Clear both the display buffer and all conversation messages.
|
|
205
|
+
* This is a full reset; the LLM context is wiped.
|
|
206
|
+
*/
|
|
207
|
+
public override resetAll(): void {
|
|
208
|
+
super.resetAll();
|
|
209
|
+
this.history.clear();
|
|
210
|
+
this.appendedUpTo = 0;
|
|
211
|
+
this.lastRenderedWidth = 0;
|
|
212
|
+
this.dirty = true;
|
|
213
|
+
this.collapseState.clear();
|
|
214
|
+
this.blockRegistry = [];
|
|
215
|
+
this.messageLineRegistry = [];
|
|
216
|
+
this.errorLineRegistry = [];
|
|
217
|
+
this.streamingStartLine = -1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* replaceMessagesForLLM - Replace the conversation's LLM-visible messages with a new set.
|
|
222
|
+
* Used by small-window compaction to swap in truncated messages without an LLM call.
|
|
223
|
+
* System messages are always preserved at the front.
|
|
224
|
+
*
|
|
225
|
+
* @param newMessages - Replacement ProviderMessage array (user/assistant/tool roles only)
|
|
226
|
+
*/
|
|
227
|
+
public override replaceMessagesForLLM(newMessages: ProviderMessage[]): void {
|
|
228
|
+
super.replaceMessagesForLLM(newMessages);
|
|
229
|
+
this.history.clear();
|
|
230
|
+
this.appendedUpTo = 0;
|
|
231
|
+
this.lastRenderedWidth = 0;
|
|
232
|
+
this.dirty = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* switchBranch - Replace the active messages with the stored branch snapshot.
|
|
237
|
+
* Returns true on success, false if the branch does not exist.
|
|
238
|
+
*/
|
|
239
|
+
public override switchBranch(name: string): boolean {
|
|
240
|
+
const result = super.switchBranch(name);
|
|
241
|
+
if (result) this.markDirty();
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* mergeBranch - Append all messages from the named branch that come after
|
|
247
|
+
* the fork point.
|
|
248
|
+
* Returns true on success, false if the branch does not exist.
|
|
249
|
+
*/
|
|
250
|
+
public override mergeBranch(name: string): boolean {
|
|
251
|
+
const result = super.mergeBranch(name);
|
|
252
|
+
if (result) this.markDirty();
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* fromJSON - Restore conversation from persisted data.
|
|
258
|
+
*/
|
|
259
|
+
public override fromJSON(data: {
|
|
260
|
+
messages: Message[];
|
|
261
|
+
branches?: Record<string, Message[]>;
|
|
262
|
+
currentBranch?: string;
|
|
263
|
+
title?: string;
|
|
264
|
+
titleSource?: import('@pellux/goodvibes-sdk/platform/core/conversation').ConversationTitleSource;
|
|
265
|
+
}): void {
|
|
266
|
+
super.fromJSON(data);
|
|
267
|
+
this.history.clear();
|
|
268
|
+
this.appendedUpTo = 0;
|
|
269
|
+
this.lastRenderedWidth = 0;
|
|
270
|
+
this.dirty = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// -------------------------------------------------------------------------
|
|
274
|
+
// TUI-only display methods
|
|
275
|
+
// -------------------------------------------------------------------------
|
|
276
|
+
|
|
336
277
|
public getDisplayBlocks(): Line[] {
|
|
337
278
|
this.flushHistory();
|
|
338
279
|
return this.history.getAllLines();
|
|
@@ -348,14 +289,15 @@ export class ConversationManager {
|
|
|
348
289
|
this.blockRegistry = [];
|
|
349
290
|
this.messageLineRegistry = [];
|
|
350
291
|
this.errorLineRegistry = [];
|
|
351
|
-
const width = this.
|
|
292
|
+
const width = this._getWidth();
|
|
352
293
|
this.lastRenderedWidth = width;
|
|
353
294
|
this.dirty = false;
|
|
354
295
|
|
|
355
296
|
// Tool messages ARE rendered (as collapsed blocks); this filter is only
|
|
356
297
|
// for determining whether to show the splash screen (tool-only messages
|
|
357
298
|
// don't count as visible conversation content for splash purposes).
|
|
358
|
-
const
|
|
299
|
+
const snapshot = this.getMessageSnapshot();
|
|
300
|
+
const displayMessages = snapshot.filter(
|
|
359
301
|
(m) => m.role !== 'tool' && m.role !== 'system',
|
|
360
302
|
);
|
|
361
303
|
|
|
@@ -364,8 +306,8 @@ export class ConversationManager {
|
|
|
364
306
|
return;
|
|
365
307
|
}
|
|
366
308
|
|
|
367
|
-
this.appendMessages(
|
|
368
|
-
this.appendedUpTo =
|
|
309
|
+
this.appendMessages(snapshot, width);
|
|
310
|
+
this.appendedUpTo = snapshot.length;
|
|
369
311
|
}
|
|
370
312
|
|
|
371
313
|
/**
|
|
@@ -373,7 +315,7 @@ export class ConversationManager {
|
|
|
373
315
|
* Falls back to a full rebuild when the terminal width has changed.
|
|
374
316
|
*/
|
|
375
317
|
public flushHistory(): void {
|
|
376
|
-
const currentWidth = this.
|
|
318
|
+
const currentWidth = this._getWidth();
|
|
377
319
|
if (!this.dirty && currentWidth === this.lastRenderedWidth) return;
|
|
378
320
|
this.rebuildHistory();
|
|
379
321
|
}
|
|
@@ -388,7 +330,7 @@ export class ConversationManager {
|
|
|
388
330
|
blockRegistry: this.blockRegistry,
|
|
389
331
|
collapseState: this.collapseState,
|
|
390
332
|
errorLineRegistry: this.errorLineRegistry,
|
|
391
|
-
configManager: this.
|
|
333
|
+
configManager: this._configManager,
|
|
392
334
|
splashOptions: this.splashOptions,
|
|
393
335
|
};
|
|
394
336
|
}
|
|
@@ -547,28 +489,6 @@ export class ConversationManager {
|
|
|
547
489
|
return before ?? lines[lines.length - 1]!;
|
|
548
490
|
}
|
|
549
491
|
|
|
550
|
-
public suppressSplash: boolean = false;
|
|
551
|
-
public splashOptions: SplashOptions = {};
|
|
552
|
-
|
|
553
|
-
public get title(): string {
|
|
554
|
-
return this._title;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
public set title(value: string) {
|
|
558
|
-
this._title = String(value ?? '');
|
|
559
|
-
this._titleSource = this._title.trim().length > 0 ? 'user' : 'system';
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
public getTitleSource(): ConversationTitleSource {
|
|
563
|
-
return this._titleSource;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
public setSystemTitle(value: string): void {
|
|
567
|
-
if (this._titleSource === 'user') return;
|
|
568
|
-
this._title = String(value ?? '');
|
|
569
|
-
this._titleSource = 'system';
|
|
570
|
-
}
|
|
571
|
-
|
|
572
492
|
public setSplashSuppressed(suppressed: boolean): void {
|
|
573
493
|
if (this.suppressSplash === suppressed) return;
|
|
574
494
|
this.suppressSplash = suppressed;
|
|
@@ -584,7 +504,7 @@ export class ConversationManager {
|
|
|
584
504
|
}
|
|
585
505
|
|
|
586
506
|
public log(text: string, style: Partial<Cell> = {}, indent = ' '): void {
|
|
587
|
-
logConversationText(this.renderingContext(), this.
|
|
507
|
+
logConversationText(this.renderingContext(), this._getWidth(), text, style, indent);
|
|
588
508
|
}
|
|
589
509
|
|
|
590
510
|
/**
|
|
@@ -596,180 +516,13 @@ export class ConversationManager {
|
|
|
596
516
|
this.appendedUpTo = 0;
|
|
597
517
|
this.dirty = true;
|
|
598
518
|
// Re-render from existing messages to rebuild buffer
|
|
599
|
-
const width = this.
|
|
519
|
+
const width = this._getWidth();
|
|
600
520
|
this.lastRenderedWidth = width;
|
|
601
521
|
this.dirty = false;
|
|
602
|
-
this.
|
|
603
|
-
this.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* resetAll - Clear both the display buffer and all conversation messages.
|
|
608
|
-
* This is a full reset; the LLM context is wiped.
|
|
609
|
-
*/
|
|
610
|
-
public resetAll(): void {
|
|
611
|
-
this.messages = [];
|
|
612
|
-
this._title = '';
|
|
613
|
-
this._titleSource = 'system';
|
|
614
|
-
this.undoStack = [];
|
|
615
|
-
this.branches.clear();
|
|
616
|
-
this.currentBranch = 'main';
|
|
617
|
-
this.history.clear();
|
|
618
|
-
this.appendedUpTo = 0;
|
|
619
|
-
this.lastRenderedWidth = 0;
|
|
620
|
-
this.dirty = true;
|
|
621
|
-
this.collapseState.clear();
|
|
622
|
-
this.blockRegistry = [];
|
|
623
|
-
this.streamingStartLine = -1;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
public getMessageSnapshot(): ConversationMessageSnapshot[] {
|
|
627
|
-
return cloneMessages(this.messages);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
public getTranscriptEventIndex() {
|
|
631
|
-
return buildTranscriptEventIndex(this.getMessageSnapshot());
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* replaceMessagesForLLM - Replace the conversation's LLM-visible messages with a new set.
|
|
636
|
-
* Used by small-window compaction to swap in truncated messages without an LLM call.
|
|
637
|
-
* System messages are always preserved at the front.
|
|
638
|
-
*
|
|
639
|
-
* @param newMessages - Replacement ProviderMessage array (user/assistant/tool roles only)
|
|
640
|
-
*/
|
|
641
|
-
public replaceMessagesForLLM(newMessages: ProviderMessage[]): void {
|
|
642
|
-
const originalSystemMessages = this.messages.filter(m => m.role === 'system');
|
|
643
|
-
const convertedMessages = messagesToInternal(newMessages);
|
|
644
|
-
this.messages = [...originalSystemMessages, ...convertedMessages];
|
|
645
|
-
this.history.clear();
|
|
646
|
-
this.appendedUpTo = 0;
|
|
647
|
-
this.lastRenderedWidth = 0;
|
|
648
|
-
this.dirty = true;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* compact - Reduce conversation state to a structured handoff payload.
|
|
653
|
-
*
|
|
654
|
-
* @param registry - Provider registry
|
|
655
|
-
* @param modelId - Model to use for summarization
|
|
656
|
-
* @param trigger - 'manual' (from /compact command) or 'auto' (from threshold check)
|
|
657
|
-
* @param provider - Provider name for model disambiguation
|
|
658
|
-
* @param context - Structured compaction context
|
|
659
|
-
*/
|
|
660
|
-
public async compact(
|
|
661
|
-
registry: import('@pellux/goodvibes-sdk/platform/providers/registry').ProviderRegistry,
|
|
662
|
-
modelId: string,
|
|
663
|
-
trigger: 'auto' | 'manual' = 'manual',
|
|
664
|
-
provider?: string,
|
|
665
|
-
context?: import('@pellux/goodvibes-sdk/platform/core/context-compaction').CompactionContext,
|
|
666
|
-
): Promise<void> {
|
|
667
|
-
return compactConversation(this, registry, modelId, trigger, provider, context);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* forkBranch - Save a deep-copy of the current messages under a named branch.
|
|
672
|
-
* If no name is provided a timestamp-based name is used.
|
|
673
|
-
* Returns the name used.
|
|
674
|
-
*/
|
|
675
|
-
public forkBranch(name?: string, force = false): string {
|
|
676
|
-
const branchName = name?.trim() || `branch-${Date.now()}`;
|
|
677
|
-
if (!force && this.branches.has(branchName)) {
|
|
678
|
-
logger.warn(`forkBranch: branch '${branchName}' already exists; use force=true to overwrite`);
|
|
679
|
-
}
|
|
680
|
-
this.branches.set(branchName, cloneMessages(this.messages));
|
|
681
|
-
return branchName;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* listBranches - Return the names and message counts of all saved branches.
|
|
686
|
-
*/
|
|
687
|
-
public listBranches(): Array<{ name: string; messageCount: number; isCurrent: boolean }> {
|
|
688
|
-
const result: Array<{ name: string; messageCount: number; isCurrent: boolean }> = [];
|
|
689
|
-
// Always include current branch even if it hasn't been stored in the map yet
|
|
690
|
-
const currentInMap = this.branches.has(this.currentBranch);
|
|
691
|
-
if (!currentInMap) {
|
|
692
|
-
result.push({ name: this.currentBranch, messageCount: this.messages.length, isCurrent: true });
|
|
693
|
-
}
|
|
694
|
-
for (const [name, msgs] of this.branches) {
|
|
695
|
-
result.push({ name, messageCount: msgs.length, isCurrent: name === this.currentBranch });
|
|
696
|
-
}
|
|
697
|
-
return result;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* switchBranch - Replace the active messages with the stored branch snapshot.
|
|
702
|
-
* Returns true on success, false if the branch does not exist.
|
|
703
|
-
*/
|
|
704
|
-
public switchBranch(name: string): boolean {
|
|
705
|
-
const stored = this.branches.get(name);
|
|
706
|
-
if (!stored) return false;
|
|
707
|
-
// Save current branch state before switching to prevent data loss
|
|
708
|
-
this.branches.set(this.currentBranch, cloneMessages(this.messages));
|
|
709
|
-
this.messages = cloneMessages(stored);
|
|
710
|
-
this.currentBranch = name;
|
|
711
|
-
this.undoStack = [];
|
|
712
|
-
this.markDirty();
|
|
713
|
-
return true;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* mergeBranch - Append all messages from the named branch that come after
|
|
718
|
-
* the fork point (messages not already present in the current conversation).
|
|
719
|
-
* Simple strategy: append all branch messages after current messages.
|
|
720
|
-
* Returns true on success, false if the branch does not exist.
|
|
721
|
-
*/
|
|
722
|
-
public mergeBranch(name: string): boolean {
|
|
723
|
-
const stored = this.branches.get(name);
|
|
724
|
-
if (!stored) return false;
|
|
725
|
-
// Use length-based fork point detection: the branch was cloned from a known
|
|
726
|
-
// snapshot so we use the shorter of the two lengths as the common prefix,
|
|
727
|
-
// then append any messages the branch has beyond that point.
|
|
728
|
-
const commonLen = Math.min(this.messages.length, stored.length);
|
|
729
|
-
const toAppend = stored.slice(commonLen);
|
|
730
|
-
if (toAppend.length === 0) return true;
|
|
731
|
-
this.messages.push(...cloneMessages(toAppend));
|
|
732
|
-
this.undoStack = [];
|
|
733
|
-
this.markDirty();
|
|
734
|
-
return true;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/** Returns the name of the currently active branch. */
|
|
738
|
-
public getCurrentBranch(): string {
|
|
739
|
-
return this.currentBranch;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* toJSON - Serialize conversation for persistence.
|
|
744
|
-
*/
|
|
745
|
-
public toJSON(): object {
|
|
746
|
-
// Serialize branches map as a plain object for persistence
|
|
747
|
-
const branchesObj = cloneBranchMap(this.branches);
|
|
748
|
-
return {
|
|
749
|
-
messages: cloneMessages(this.messages),
|
|
750
|
-
timestamp: Date.now(),
|
|
751
|
-
title: this._title,
|
|
752
|
-
titleSource: this._titleSource,
|
|
753
|
-
branches: branchesObj,
|
|
754
|
-
currentBranch: this.currentBranch,
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* fromJSON - Restore conversation from persisted data.
|
|
760
|
-
*/
|
|
761
|
-
public fromJSON(data: { messages: Message[]; branches?: Record<string, Message[]>; currentBranch?: string; title?: string; titleSource?: ConversationTitleSource }): void {
|
|
762
|
-
this.messages = data.messages ?? [];
|
|
763
|
-
this._title = typeof data.title === 'string' ? data.title : '';
|
|
764
|
-
this._titleSource = data.titleSource === 'user' || data.titleSource === 'system'
|
|
765
|
-
? data.titleSource
|
|
766
|
-
: (this._title ? 'user' : 'system');
|
|
767
|
-
this.branches = restoreBranchMap(data.branches);
|
|
768
|
-
this.currentBranch = data.currentBranch ?? 'main';
|
|
769
|
-
this.history.clear();
|
|
770
|
-
this.appendedUpTo = 0;
|
|
771
|
-
this.lastRenderedWidth = 0;
|
|
772
|
-
this.dirty = true;
|
|
522
|
+
const snapshot = this.getMessageSnapshot();
|
|
523
|
+
this.appendMessages(snapshot, width);
|
|
524
|
+
this.appendedUpTo = snapshot.length;
|
|
773
525
|
}
|
|
774
526
|
}
|
|
527
|
+
|
|
775
528
|
export { parseDiffForApply, applyDiffContent } from '@pellux/goodvibes-sdk/platform/core/conversation-diff';
|