@agentuity/coder 2.0.7 → 2.0.9-v3.48d5810

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 (113) hide show
  1. package/AGENTS.md +43 -0
  2. package/README.md +75 -37
  3. package/dist/index.d.ts +2 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +9 -1669
  6. package/dist/index.js.map +1 -1
  7. package/package.json +44 -42
  8. package/src/index.ts +84 -1996
  9. package/dist/chain-preview.d.ts +0 -55
  10. package/dist/chain-preview.d.ts.map +0 -1
  11. package/dist/chain-preview.js +0 -472
  12. package/dist/chain-preview.js.map +0 -1
  13. package/dist/client.d.ts +0 -44
  14. package/dist/client.d.ts.map +0 -1
  15. package/dist/client.js +0 -411
  16. package/dist/client.js.map +0 -1
  17. package/dist/commands.d.ts +0 -22
  18. package/dist/commands.d.ts.map +0 -1
  19. package/dist/commands.js +0 -99
  20. package/dist/commands.js.map +0 -1
  21. package/dist/footer.d.ts +0 -34
  22. package/dist/footer.d.ts.map +0 -1
  23. package/dist/footer.js +0 -249
  24. package/dist/footer.js.map +0 -1
  25. package/dist/handlers.d.ts +0 -24
  26. package/dist/handlers.d.ts.map +0 -1
  27. package/dist/handlers.js +0 -83
  28. package/dist/handlers.js.map +0 -1
  29. package/dist/hub-overlay-state.d.ts +0 -31
  30. package/dist/hub-overlay-state.d.ts.map +0 -1
  31. package/dist/hub-overlay-state.js +0 -78
  32. package/dist/hub-overlay-state.js.map +0 -1
  33. package/dist/hub-overlay.d.ts +0 -146
  34. package/dist/hub-overlay.d.ts.map +0 -1
  35. package/dist/hub-overlay.js +0 -2354
  36. package/dist/hub-overlay.js.map +0 -1
  37. package/dist/native-remote-ui-context.d.ts +0 -5
  38. package/dist/native-remote-ui-context.d.ts.map +0 -1
  39. package/dist/native-remote-ui-context.js +0 -30
  40. package/dist/native-remote-ui-context.js.map +0 -1
  41. package/dist/output-viewer.d.ts +0 -49
  42. package/dist/output-viewer.d.ts.map +0 -1
  43. package/dist/output-viewer.js +0 -389
  44. package/dist/output-viewer.js.map +0 -1
  45. package/dist/overlay.d.ts +0 -40
  46. package/dist/overlay.d.ts.map +0 -1
  47. package/dist/overlay.js +0 -225
  48. package/dist/overlay.js.map +0 -1
  49. package/dist/protocol.d.ts +0 -605
  50. package/dist/protocol.d.ts.map +0 -1
  51. package/dist/protocol.js +0 -4
  52. package/dist/protocol.js.map +0 -1
  53. package/dist/remote-lifecycle.d.ts +0 -61
  54. package/dist/remote-lifecycle.d.ts.map +0 -1
  55. package/dist/remote-lifecycle.js +0 -190
  56. package/dist/remote-lifecycle.js.map +0 -1
  57. package/dist/remote-session.d.ts +0 -128
  58. package/dist/remote-session.d.ts.map +0 -1
  59. package/dist/remote-session.js +0 -876
  60. package/dist/remote-session.js.map +0 -1
  61. package/dist/remote-tui.d.ts +0 -40
  62. package/dist/remote-tui.d.ts.map +0 -1
  63. package/dist/remote-tui.js +0 -867
  64. package/dist/remote-tui.js.map +0 -1
  65. package/dist/remote-ui-handler.d.ts +0 -5
  66. package/dist/remote-ui-handler.d.ts.map +0 -1
  67. package/dist/remote-ui-handler.js +0 -53
  68. package/dist/remote-ui-handler.js.map +0 -1
  69. package/dist/renderers.d.ts +0 -34
  70. package/dist/renderers.d.ts.map +0 -1
  71. package/dist/renderers.js +0 -669
  72. package/dist/renderers.js.map +0 -1
  73. package/dist/review.d.ts +0 -15
  74. package/dist/review.d.ts.map +0 -1
  75. package/dist/review.js +0 -154
  76. package/dist/review.js.map +0 -1
  77. package/dist/titlebar.d.ts +0 -3
  78. package/dist/titlebar.d.ts.map +0 -1
  79. package/dist/titlebar.js +0 -59
  80. package/dist/titlebar.js.map +0 -1
  81. package/dist/todo/index.d.ts +0 -3
  82. package/dist/todo/index.d.ts.map +0 -1
  83. package/dist/todo/index.js +0 -3
  84. package/dist/todo/index.js.map +0 -1
  85. package/dist/todo/store.d.ts +0 -6
  86. package/dist/todo/store.d.ts.map +0 -1
  87. package/dist/todo/store.js +0 -43
  88. package/dist/todo/store.js.map +0 -1
  89. package/dist/todo/types.d.ts +0 -13
  90. package/dist/todo/types.d.ts.map +0 -1
  91. package/dist/todo/types.js +0 -2
  92. package/dist/todo/types.js.map +0 -1
  93. package/src/chain-preview.ts +0 -621
  94. package/src/client.ts +0 -527
  95. package/src/commands.ts +0 -132
  96. package/src/footer.ts +0 -305
  97. package/src/handlers.ts +0 -113
  98. package/src/hub-overlay-state.ts +0 -127
  99. package/src/hub-overlay.ts +0 -3037
  100. package/src/native-remote-ui-context.ts +0 -41
  101. package/src/output-viewer.ts +0 -480
  102. package/src/overlay.ts +0 -294
  103. package/src/protocol.ts +0 -758
  104. package/src/remote-lifecycle.ts +0 -270
  105. package/src/remote-session.ts +0 -1080
  106. package/src/remote-tui.ts +0 -1020
  107. package/src/remote-ui-handler.ts +0 -86
  108. package/src/renderers.ts +0 -740
  109. package/src/review.ts +0 -201
  110. package/src/titlebar.ts +0 -63
  111. package/src/todo/index.ts +0 -2
  112. package/src/todo/store.ts +0 -49
  113. package/src/todo/types.ts +0 -14
package/src/footer.ts DELETED
@@ -1,305 +0,0 @@
1
- /**
2
- * Coder footer for the Pi TUI.
3
- *
4
- * Uses transparent backgrounds with foreground-only ANSI true-color text.
5
- * Includes a braille spinner animation when an agent is actively working.
6
- *
7
- * Layout:
8
- * [brand] [branch] > [model/agent] [hub] | [N] label token-stats
9
- *
10
- * Observer awareness (ASCII only):
11
- * [3] SwiftRaven — 3 observers watching, session label "SwiftRaven"
12
- */
13
-
14
- import type { ExtensionContext, ReadonlyFooterDataProvider } from '@mariozechner/pi-coding-agent';
15
-
16
- const RESET = '\x1b[0m';
17
- const SEP = '>';
18
-
19
- // ──────────────────────────────────────────────
20
- // ANSI true-color helper (foreground only)
21
- // ──────────────────────────────────────────────
22
-
23
- type RGB = [number, number, number];
24
-
25
- function fg(color: RGB, text: string): string {
26
- return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${text}`;
27
- }
28
-
29
- // ──────────────────────────────────────────────
30
- // Color palette (foreground only, no backgrounds)
31
- // ──────────────────────────────────────────────
32
-
33
- const FG_BRAND: RGB = [100, 200, 255];
34
- const FG_MODEL: RGB = [215, 135, 175];
35
- const FG_AGENT: RGB = [130, 200, 130];
36
- const FG_BRANCH: RGB = [150, 180, 150];
37
- const FG_HUB_OK: RGB = [80, 200, 120];
38
- const FG_HUB_WARN: RGB = [245, 179, 66];
39
- const FG_HUB_ERR: RGB = [220, 80, 80];
40
- const FG_DIM: RGB = [100, 110, 120];
41
-
42
- type HubStatus = 'connected' | 'reconnecting' | 'offline';
43
-
44
- /** Observer state provided by the extension's presence tracking. */
45
- export interface ObserverState {
46
- /** Number of observers watching this session (excludes lead + sub-agents). */
47
- count: number;
48
- /** Human-readable session label (e.g. "SwiftRaven"). */
49
- label: string;
50
- }
51
-
52
- const FG_OBSERVER: RGB = [140, 180, 220];
53
-
54
- // ──────────────────────────────────────────────
55
- // Braille spinner
56
- // ──────────────────────────────────────────────
57
-
58
- const SPINNER_FRAMES = [
59
- '\u280B',
60
- '\u2819',
61
- '\u2839',
62
- '\u2838',
63
- '\u283C',
64
- '\u2834',
65
- '\u2826',
66
- '\u2827',
67
- '\u2807',
68
- '\u280F',
69
- ];
70
-
71
- // ──────────────────────────────────────────────
72
- // Footer builder (transparent bg, foreground only)
73
- // ──────────────────────────────────────────────
74
-
75
- /** Strip ANSI escape sequences to get visible character count. */
76
- function visibleLength(str: string): number {
77
- // eslint-disable-next-line no-control-regex
78
- return str.replace(/\x1b\[[0-9;]*m/g, '').length;
79
- }
80
-
81
- /** Truncate an ANSI-colored string to a maximum visible width. */
82
- function truncateAnsi(str: string, maxWidth: number): string {
83
- let visible = 0;
84
- let i = 0;
85
- while (i < str.length && visible < maxWidth) {
86
- if (str[i] === '\x1b') {
87
- // Skip entire ANSI escape sequence
88
- const end = str.indexOf('m', i);
89
- if (end !== -1) {
90
- i = end + 1;
91
- } else {
92
- i++;
93
- }
94
- } else {
95
- visible++;
96
- i++;
97
- }
98
- }
99
- return str.slice(0, i) + RESET;
100
- }
101
-
102
- function buildFooter(left: string, rightText: string, width: number): string {
103
- // Safety margin for Unicode characters that may be double-width
104
- const safeWidth = width - 4;
105
- const leftLen = visibleLength(left);
106
- const rightLen = visibleLength(rightText);
107
- const total = leftLen + 1 + rightLen;
108
-
109
- if (total > safeWidth) {
110
- const maxLeft = safeWidth - rightLen - 1;
111
- if (maxLeft > 0) {
112
- return truncateAnsi(left, maxLeft) + ' ' + rightText;
113
- }
114
- return truncateAnsi(left + ' ' + rightText, safeWidth);
115
- }
116
-
117
- const gap = safeWidth - leftLen - rightLen;
118
- return left + ' '.repeat(gap) + rightText;
119
- }
120
-
121
- // ──────────────────────────────────────────────
122
- // Minimal component
123
- // ──────────────────────────────────────────────
124
-
125
- class FooterComponent {
126
- private getText: (width: number) => string;
127
- private _unsubscribeBranch?: () => void;
128
-
129
- constructor(
130
- getText: (width: number) => string,
131
- footerData: ReadonlyFooterDataProvider,
132
- cleanupSpinner: () => void
133
- ) {
134
- this.getText = getText;
135
- this._cleanupSpinner = cleanupSpinner;
136
- this._unsubscribeBranch = footerData.onBranchChange(() => {
137
- // Triggers TUI refresh
138
- });
139
- }
140
-
141
- private _cleanupSpinner: () => void;
142
-
143
- render(width: number): string[] {
144
- const text = this.getText(width);
145
- // Final safety: ensure line never exceeds terminal width
146
- if (visibleLength(text) > width) {
147
- return [truncateAnsi(text, width)];
148
- }
149
- return [text];
150
- }
151
-
152
- invalidate(): void {
153
- // no-op
154
- }
155
-
156
- dispose(): void {
157
- this._unsubscribeBranch?.();
158
- this._cleanupSpinner();
159
- }
160
- }
161
-
162
- // ──────────────────────────────────────────────
163
- // Token stat formatters
164
- // ──────────────────────────────────────────────
165
-
166
- function formatTokens(n: number): string {
167
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
168
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
169
- return String(n);
170
- }
171
-
172
- function formatCost(n: number): string {
173
- if (n === 0) return '$0.00';
174
- if (n < 0.01) return `$${n.toFixed(4)}`;
175
- return `$${n.toFixed(2)}`;
176
- }
177
-
178
- // ──────────────────────────────────────────────
179
- // Public API
180
- // ──────────────────────────────────────────────
181
-
182
- /**
183
- * Set up the Coder footer (transparent bg, foreground-colored text).
184
- * Call this once with the extension context to replace Pi's default footer.
185
- *
186
- * Includes a braille spinner animation when an agent is actively working.
187
- *
188
- * @param ctx Extension context with UI access
189
- * @param getHubStatus Callback that returns current Hub connection status
190
- * @param getObserverState Optional callback that returns observer count + session label
191
- */
192
- export function setupCoderFooter(
193
- ctx: ExtensionContext,
194
- getHubStatus: () => HubStatus,
195
- getObserverState?: () => ObserverState
196
- ): void {
197
- if (!ctx.hasUI) return;
198
-
199
- ctx.ui.setFooter((tui, _theme, footerData) => {
200
- // Spinner state
201
- let spinnerTimer: ReturnType<typeof setInterval> | null = null;
202
- let spinnerFrame = 0;
203
-
204
- const getText = (width: number): string => {
205
- // Detect active agent
206
- const activeAgent = footerData.getExtensionStatuses().get('active_agent');
207
-
208
- // Start/stop spinner based on agent activity
209
- if (activeAgent && !spinnerTimer) {
210
- spinnerTimer = setInterval(() => {
211
- spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
212
- tui.requestRender();
213
- }, 80);
214
- } else if (!activeAgent && spinnerTimer) {
215
- clearInterval(spinnerTimer);
216
- spinnerTimer = null;
217
- spinnerFrame = 0;
218
- }
219
-
220
- // Token stats from session messages
221
- let inputTokens = 0;
222
- let outputTokens = 0;
223
- let totalCost = 0;
224
- for (const entry of ctx.sessionManager.getBranch()) {
225
- if (entry.type === 'message') {
226
- const msg = entry.message as {
227
- role?: string;
228
- usage?: { input: number; output: number; cost: { total: number } };
229
- };
230
- if (msg.role === 'assistant' && msg.usage) {
231
- inputTokens += msg.usage.input;
232
- outputTokens += msg.usage.output;
233
- totalCost += msg.usage.cost.total;
234
- }
235
- }
236
- }
237
- const tokenStr = `\u2191${formatTokens(inputTokens)} \u2193${formatTokens(outputTokens)} ${formatCost(totalCost)}`;
238
-
239
- // LEFT side: brand > branch > model/agent
240
- const leftParts: string[] = [];
241
-
242
- // Brand (with spinner)
243
- const brandChar = spinnerTimer ? SPINNER_FRAMES[spinnerFrame]! : '\u2A3A';
244
- leftParts.push(fg(FG_BRAND, ` ${brandChar}`));
245
-
246
- // Branch
247
- const branch = footerData.getGitBranch();
248
- if (branch) {
249
- leftParts.push(' ');
250
- leftParts.push(fg(FG_BRANCH, branch));
251
- }
252
-
253
- // Model or active agent
254
- leftParts.push(fg(FG_DIM, ` ${SEP} `));
255
- if (activeAgent) {
256
- leftParts.push(fg(FG_AGENT, activeAgent));
257
- } else {
258
- const modelId = ctx.model ? String((ctx.model as { id?: string }).id ?? '?') : '?';
259
- leftParts.push(fg(FG_MODEL, modelId));
260
- }
261
-
262
- const left = leftParts.join('');
263
-
264
- // RIGHT side: hub status + observer info + token stats
265
- const rightParts: string[] = [];
266
- const hubStatus = getHubStatus();
267
- if (hubStatus === 'connected') {
268
- rightParts.push(fg(FG_HUB_OK, 'Hub'));
269
- } else if (hubStatus === 'reconnecting') {
270
- rightParts.push(fg(FG_HUB_WARN, 'Hub...'));
271
- } else {
272
- rightParts.push(fg(FG_HUB_ERR, 'Hub Off'));
273
- }
274
-
275
- // Observer awareness (ASCII only, no emojis)
276
- if (getObserverState && hubStatus === 'connected') {
277
- const obs = getObserverState();
278
- if (obs.count > 0 || obs.label) {
279
- rightParts.push(fg(FG_DIM, ' | '));
280
- if (obs.count > 0) {
281
- rightParts.push(fg(FG_OBSERVER, `[${obs.count}]`));
282
- if (obs.label) rightParts.push(' ');
283
- }
284
- if (obs.label) {
285
- rightParts.push(fg(FG_OBSERVER, obs.label));
286
- }
287
- }
288
- }
289
-
290
- rightParts.push(fg(FG_DIM, ` ${tokenStr}`) + RESET);
291
- const rightText = rightParts.join('');
292
-
293
- return buildFooter(left, rightText, width);
294
- };
295
-
296
- const cleanupSpinner = (): void => {
297
- if (spinnerTimer) {
298
- clearInterval(spinnerTimer);
299
- spinnerTimer = null;
300
- }
301
- };
302
-
303
- return new FooterComponent(getText, footerData, cleanupSpinner);
304
- });
305
- }
package/src/handlers.ts DELETED
@@ -1,113 +0,0 @@
1
- import type { HubAction } from './protocol.ts';
2
-
3
- export interface ActionResult {
4
- block?: { block: true; reason: string };
5
- returnValue?: unknown;
6
- systemPrompt?: string;
7
- systemPromptMode?: 'replace' | 'prefix' | 'suffix';
8
- // undefined means ACK (proceed normally)
9
- }
10
-
11
- /** Minimal UI surface used by action handlers — avoids a hard dep on pi-coding-agent. */
12
- interface ActionContext {
13
- ui?: {
14
- notify(message: string, level?: 'info' | 'warning' | 'error'): void;
15
- confirm(title: string, message: string): Promise<boolean>;
16
- setStatus(key: string, text?: string): void;
17
- };
18
- sendUserMessage?: (message: string, options?: { deliverAs?: 'followUp' }) => void;
19
- }
20
-
21
- export async function processActions(
22
- actions: HubAction[],
23
- ctx: ActionContext
24
- ): Promise<ActionResult> {
25
- let result: ActionResult = {};
26
-
27
- for (const action of actions) {
28
- switch (action.action) {
29
- case 'ACK':
30
- // Terminal: proceed normally
31
- result = {};
32
- break;
33
-
34
- case 'BLOCK':
35
- // Terminal: block
36
- result = { block: { block: true, reason: action.reason } };
37
- break;
38
-
39
- case 'RETURN':
40
- // Terminal: return a specific result
41
- result = { returnValue: action.result };
42
- break;
43
-
44
- case 'NOTIFY':
45
- // Side effect: show notification, continue
46
- if (ctx?.ui) {
47
- ctx.ui.notify(action.message, action.level ?? 'info');
48
- }
49
- break;
50
-
51
- case 'STATUS':
52
- // Side effect: set status, continue
53
- if (ctx?.ui) {
54
- ctx.ui.setStatus(action.key, action.text);
55
- }
56
- break;
57
-
58
- case 'CONFIRM': {
59
- // Gate: if user denies, stop and block
60
- if (ctx?.ui) {
61
- const confirmed = await ctx.ui.confirm(action.title, action.message);
62
- if (!confirmed) {
63
- return {
64
- block: {
65
- block: true,
66
- reason: action.deny_reason ?? 'Denied by user',
67
- },
68
- };
69
- }
70
- } else {
71
- // No UI available — block by default for safety
72
- result = {
73
- block: {
74
- block: true,
75
- reason: action.deny_reason ?? 'Confirmation required but no UI available',
76
- },
77
- };
78
- }
79
- break;
80
- }
81
-
82
- case 'SYSTEM_PROMPT':
83
- // System prompt injection — store for before_agent_start handler
84
- result = {
85
- ...result,
86
- systemPrompt: action.systemPrompt,
87
- systemPromptMode: action.mode,
88
- };
89
- break;
90
-
91
- case 'INJECT_MESSAGE': {
92
- const content = action.message?.content?.trim();
93
- if (!content) break;
94
-
95
- if (action.message?.role === 'user') {
96
- if (ctx.sendUserMessage) {
97
- ctx.sendUserMessage(content, { deliverAs: 'followUp' });
98
- } else if (ctx.ui) {
99
- ctx.ui.notify(content, 'info');
100
- }
101
- break;
102
- }
103
-
104
- if (ctx.ui) {
105
- ctx.ui.notify(content, 'info');
106
- }
107
- break;
108
- }
109
- }
110
- }
111
-
112
- return result;
113
- }
@@ -1,127 +0,0 @@
1
- export interface StreamBuffer {
2
- output: string;
3
- thinking: string;
4
- }
5
-
6
- export interface StreamProjectionBlock {
7
- output?: string;
8
- thinking?: string;
9
- }
10
-
11
- export interface StreamProjection {
12
- output?: string;
13
- thinking?: string;
14
- tasks?: Record<string, StreamProjectionBlock>;
15
- }
16
-
17
- export interface ConversationEntryLike {
18
- type?: string;
19
- content?: string;
20
- thinking?: string;
21
- taskId?: string | null;
22
- toolName?: string;
23
- toolArgs?: Record<string, unknown>;
24
- }
25
-
26
- export type StreamProjectionSource = 'none' | 'snapshot' | 'replay' | 'hydration' | 'live';
27
-
28
- export interface NormalizedStreamProjection {
29
- output: string;
30
- thinking: string;
31
- tasks: Record<string, StreamBuffer>;
32
- }
33
-
34
- export function normalizeStreamProjection(
35
- projection: StreamProjection | null | undefined
36
- ): NormalizedStreamProjection {
37
- const tasks: Record<string, StreamBuffer> = {};
38
- const rawTasks =
39
- projection?.tasks && typeof projection.tasks === 'object' ? projection.tasks : {};
40
-
41
- for (const [taskId, rawBlock] of Object.entries(rawTasks)) {
42
- if (!rawBlock || typeof rawBlock !== 'object') continue;
43
- tasks[taskId] = {
44
- output: typeof rawBlock.output === 'string' ? rawBlock.output : '',
45
- thinking: typeof rawBlock.thinking === 'string' ? rawBlock.thinking : '',
46
- };
47
- }
48
-
49
- return {
50
- output: typeof projection?.output === 'string' ? projection.output : '',
51
- thinking: typeof projection?.thinking === 'string' ? projection.thinking : '',
52
- tasks,
53
- };
54
- }
55
-
56
- export function buildProjectionFromEntries(
57
- entries: ConversationEntryLike[]
58
- ): NormalizedStreamProjection {
59
- const projection: NormalizedStreamProjection = {
60
- output: '',
61
- thinking: '',
62
- tasks: {},
63
- };
64
-
65
- const append = (kind: 'output' | 'thinking', text: string, taskId?: string | null): void => {
66
- if (!text) return;
67
- if (taskId) {
68
- if (!projection.tasks[taskId]) {
69
- projection.tasks[taskId] = { output: '', thinking: '' };
70
- }
71
- projection.tasks[taskId]![kind] += text;
72
- return;
73
- }
74
- projection[kind] += text;
75
- };
76
-
77
- for (const entry of entries) {
78
- const type = typeof entry.type === 'string' ? entry.type : '';
79
- if (type === 'tool_call') {
80
- const toolName = typeof entry.toolName === 'string' ? entry.toolName : 'tool';
81
- append('output', `[tool_call] ${toolName}\n\n`, entry.taskId);
82
- continue;
83
- }
84
-
85
- const content = typeof entry.content === 'string' ? entry.content : '';
86
- const thinking = typeof entry.thinking === 'string' ? entry.thinking : '';
87
- if (type === 'message') {
88
- if (thinking) {
89
- append('thinking', `${thinking}\n\n`, entry.taskId);
90
- }
91
- if (!content) continue;
92
- append('output', `${content}\n\n`, entry.taskId);
93
- continue;
94
- }
95
- if (!content) continue;
96
-
97
- if (type === 'tool_result' || type === 'task_result') {
98
- append('output', `${content}\n\n`, entry.taskId);
99
- continue;
100
- }
101
-
102
- if (type === 'thinking') {
103
- append('thinking', `${content}\n\n`, entry.taskId);
104
- }
105
- }
106
-
107
- return projection;
108
- }
109
-
110
- export function shouldReplaceStreamProjection(
111
- currentSource: StreamProjectionSource,
112
- nextSource: Exclude<StreamProjectionSource, 'live'>
113
- ): boolean {
114
- if (nextSource === 'snapshot') {
115
- return currentSource === 'none' || currentSource === 'snapshot';
116
- }
117
-
118
- if (nextSource === 'replay') {
119
- return currentSource === 'none' || currentSource === 'snapshot' || currentSource === 'replay';
120
- }
121
-
122
- if (nextSource === 'hydration') {
123
- return currentSource !== 'live';
124
- }
125
-
126
- return false;
127
- }