pulseed 0.4.12 → 0.4.14

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 (143) hide show
  1. package/dist/base/config/global-config.js +2 -2
  2. package/dist/base/config/global-config.js.map +1 -1
  3. package/dist/base/config/tool-metadata.js +1 -1
  4. package/dist/base/config/tool-metadata.js.map +1 -1
  5. package/dist/base/llm/codex-llm-client.d.ts +1 -1
  6. package/dist/base/llm/codex-llm-client.d.ts.map +1 -1
  7. package/dist/base/llm/codex-llm-client.js +1 -1
  8. package/dist/base/llm/codex-llm-client.js.map +1 -1
  9. package/dist/base/llm/llm-client.d.ts +3 -2
  10. package/dist/base/llm/llm-client.d.ts.map +1 -1
  11. package/dist/base/llm/llm-client.js.map +1 -1
  12. package/dist/base/llm/provider-config.d.ts +1 -1
  13. package/dist/base/llm/provider-config.d.ts.map +1 -1
  14. package/dist/base/llm/provider-config.js +21 -14
  15. package/dist/base/llm/provider-config.js.map +1 -1
  16. package/dist/interface/chat/chat-runner.d.ts.map +1 -1
  17. package/dist/interface/chat/chat-runner.js +29 -8
  18. package/dist/interface/chat/chat-runner.js.map +1 -1
  19. package/dist/interface/cli/commands/setup/import/apply.d.ts.map +1 -1
  20. package/dist/interface/cli/commands/setup/import/apply.js +3 -0
  21. package/dist/interface/cli/commands/setup/import/apply.js.map +1 -1
  22. package/dist/interface/cli/commands/setup/import/discovery.d.ts.map +1 -1
  23. package/dist/interface/cli/commands/setup/import/discovery.js +35 -0
  24. package/dist/interface/cli/commands/setup/import/discovery.js.map +1 -1
  25. package/dist/interface/cli/commands/setup/import/flow.d.ts.map +1 -1
  26. package/dist/interface/cli/commands/setup/import/flow.js +19 -8
  27. package/dist/interface/cli/commands/setup/import/flow.js.map +1 -1
  28. package/dist/interface/cli/commands/setup/import/provider.d.ts.map +1 -1
  29. package/dist/interface/cli/commands/setup/import/provider.js +10 -3
  30. package/dist/interface/cli/commands/setup/import/provider.js.map +1 -1
  31. package/dist/interface/cli/commands/setup/import/types.d.ts +6 -1
  32. package/dist/interface/cli/commands/setup/import/types.d.ts.map +1 -1
  33. package/dist/interface/cli/commands/setup/steps-identity.d.ts.map +1 -1
  34. package/dist/interface/cli/commands/setup/steps-identity.js +1 -2
  35. package/dist/interface/cli/commands/setup/steps-identity.js.map +1 -1
  36. package/dist/interface/cli/commands/setup/steps-provider.d.ts +4 -0
  37. package/dist/interface/cli/commands/setup/steps-provider.d.ts.map +1 -1
  38. package/dist/interface/cli/commands/setup/steps-provider.js +26 -0
  39. package/dist/interface/cli/commands/setup/steps-provider.js.map +1 -1
  40. package/dist/interface/cli/commands/setup/steps-runtime.d.ts +1 -1
  41. package/dist/interface/cli/commands/setup/steps-runtime.d.ts.map +1 -1
  42. package/dist/interface/cli/commands/setup/steps-runtime.js +5 -1
  43. package/dist/interface/cli/commands/setup/steps-runtime.js.map +1 -1
  44. package/dist/interface/cli/commands/setup-wizard.d.ts.map +1 -1
  45. package/dist/interface/cli/commands/setup-wizard.js +73 -20
  46. package/dist/interface/cli/commands/setup-wizard.js.map +1 -1
  47. package/dist/interface/cli/commands/setup.d.ts.map +1 -1
  48. package/dist/interface/cli/commands/setup.js +12 -2
  49. package/dist/interface/cli/commands/setup.js.map +1 -1
  50. package/dist/interface/tui/app.d.ts +2 -0
  51. package/dist/interface/tui/app.d.ts.map +1 -1
  52. package/dist/interface/tui/app.js +19 -15
  53. package/dist/interface/tui/app.js.map +1 -1
  54. package/dist/interface/tui/chat/scroll.d.ts +12 -0
  55. package/dist/interface/tui/chat/scroll.d.ts.map +1 -1
  56. package/dist/interface/tui/chat/scroll.js +51 -15
  57. package/dist/interface/tui/chat/scroll.js.map +1 -1
  58. package/dist/interface/tui/chat/suggestions.d.ts.map +1 -1
  59. package/dist/interface/tui/chat/suggestions.js +0 -6
  60. package/dist/interface/tui/chat/suggestions.js.map +1 -1
  61. package/dist/interface/tui/chat/viewport.d.ts +1 -1
  62. package/dist/interface/tui/chat/viewport.d.ts.map +1 -1
  63. package/dist/interface/tui/chat/viewport.js +2 -3
  64. package/dist/interface/tui/chat/viewport.js.map +1 -1
  65. package/dist/interface/tui/chat.d.ts +14 -2
  66. package/dist/interface/tui/chat.d.ts.map +1 -1
  67. package/dist/interface/tui/chat.js +176 -36
  68. package/dist/interface/tui/chat.js.map +1 -1
  69. package/dist/interface/tui/cursor-tracker.d.ts +18 -3
  70. package/dist/interface/tui/cursor-tracker.d.ts.map +1 -1
  71. package/dist/interface/tui/cursor-tracker.js +109 -20
  72. package/dist/interface/tui/cursor-tracker.js.map +1 -1
  73. package/dist/interface/tui/debug-log.d.ts +4 -0
  74. package/dist/interface/tui/debug-log.d.ts.map +1 -0
  75. package/dist/interface/tui/debug-log.js +39 -0
  76. package/dist/interface/tui/debug-log.js.map +1 -0
  77. package/dist/interface/tui/entry.d.ts +4 -0
  78. package/dist/interface/tui/entry.d.ts.map +1 -1
  79. package/dist/interface/tui/entry.js +72 -25
  80. package/dist/interface/tui/entry.js.map +1 -1
  81. package/dist/interface/tui/flicker/MouseTracking.d.ts +10 -0
  82. package/dist/interface/tui/flicker/MouseTracking.d.ts.map +1 -0
  83. package/dist/interface/tui/flicker/MouseTracking.js +22 -0
  84. package/dist/interface/tui/flicker/MouseTracking.js.map +1 -0
  85. package/dist/interface/tui/flicker/dec.d.ts +12 -0
  86. package/dist/interface/tui/flicker/dec.d.ts.map +1 -1
  87. package/dist/interface/tui/flicker/dec.js +14 -0
  88. package/dist/interface/tui/flicker/dec.js.map +1 -1
  89. package/dist/interface/tui/flicker/frame-writer.d.ts.map +1 -1
  90. package/dist/interface/tui/flicker/frame-writer.js +78 -6
  91. package/dist/interface/tui/flicker/frame-writer.js.map +1 -1
  92. package/dist/interface/tui/flicker/index.d.ts +2 -1
  93. package/dist/interface/tui/flicker/index.d.ts.map +1 -1
  94. package/dist/interface/tui/flicker/index.js +2 -1
  95. package/dist/interface/tui/flicker/index.js.map +1 -1
  96. package/dist/interface/tui/fullscreen-chat.d.ts +13 -0
  97. package/dist/interface/tui/fullscreen-chat.d.ts.map +1 -0
  98. package/dist/interface/tui/fullscreen-chat.js +846 -0
  99. package/dist/interface/tui/fullscreen-chat.js.map +1 -0
  100. package/dist/interface/tui/help-overlay.d.ts.map +1 -1
  101. package/dist/interface/tui/help-overlay.js +1 -1
  102. package/dist/interface/tui/help-overlay.js.map +1 -1
  103. package/dist/interface/tui/settings-overlay.d.ts.map +1 -1
  104. package/dist/interface/tui/settings-overlay.js +0 -6
  105. package/dist/interface/tui/settings-overlay.js.map +1 -1
  106. package/dist/interface/tui/test-app.d.ts +8 -0
  107. package/dist/interface/tui/test-app.d.ts.map +1 -0
  108. package/dist/interface/tui/test-app.js +84 -0
  109. package/dist/interface/tui/test-app.js.map +1 -0
  110. package/dist/interface/tui/test-entry.d.ts +3 -0
  111. package/dist/interface/tui/test-entry.d.ts.map +1 -0
  112. package/dist/interface/tui/test-entry.js +75 -0
  113. package/dist/interface/tui/test-entry.js.map +1 -0
  114. package/dist/orchestrator/execution/agent-loop/agent-loop-budget.d.ts +1 -1
  115. package/dist/orchestrator/execution/agent-loop/agent-loop-budget.d.ts.map +1 -1
  116. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.d.ts.map +1 -1
  117. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.js +3 -0
  118. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.js.map +1 -1
  119. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.d.ts.map +1 -1
  120. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.js +15 -6
  121. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.js.map +1 -1
  122. package/dist/orchestrator/execution/agent-loop/agent-loop-model.d.ts +1 -0
  123. package/dist/orchestrator/execution/agent-loop/agent-loop-model.d.ts.map +1 -1
  124. package/dist/orchestrator/execution/agent-loop/agent-loop-model.js.map +1 -1
  125. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts.map +1 -1
  126. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js +0 -3
  127. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js.map +1 -1
  128. package/dist/orchestrator/execution/agent-loop/index.d.ts +1 -0
  129. package/dist/orchestrator/execution/agent-loop/index.d.ts.map +1 -1
  130. package/dist/orchestrator/execution/agent-loop/index.js +1 -0
  131. package/dist/orchestrator/execution/agent-loop/index.js.map +1 -1
  132. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.d.ts +16 -0
  133. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.d.ts.map +1 -0
  134. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js +74 -0
  135. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js.map +1 -0
  136. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts +1 -1
  137. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts.map +1 -1
  138. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js +3 -2
  139. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js.map +1 -1
  140. package/dist/orchestrator/execution/task/task-lifecycle.js +1 -1
  141. package/dist/orchestrator/execution/task/task-lifecycle.js.map +1 -1
  142. package/dist/tools/network/McpStdioTool/McpStdioTool.d.ts +2 -2
  143. package/package.json +4 -3
@@ -0,0 +1,846 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useCallback, useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { getClipboardContent } from "./clipboard.js";
5
+ import { logTuiDebug } from "./debug-log.js";
6
+ import { theme } from "./theme.js";
7
+ import { pickSpinnerVerb } from "./spinner-verbs.js";
8
+ import { CARET_MARKER, PROTECTED_ROW_MARKER, setActiveCursorEscape, setActivePromptCursorAnchor, } from "./cursor-tracker.js";
9
+ import { isBashModeInput } from "./bash-mode.js";
10
+ import { buildChatViewport } from "./chat/viewport.js";
11
+ import { getScrollRequest, parseMouseEvent, stripMouseEscapeSequences, } from "./chat/scroll.js";
12
+ import { getMatchingSuggestions } from "./chat/suggestions.js";
13
+ const SCROLL_LINE_STEP = 3;
14
+ const DEFAULT_PROMPT = "◉";
15
+ const BASH_PROMPT = "!";
16
+ const SUGGESTION_HINT = " arrows to navigate, tab/enter to select, esc to dismiss";
17
+ const INPUT_MARGIN = 4;
18
+ const ZERO_WIDTH_CHARS = new Set(["\u200B", "\u2060", "\u2061"]);
19
+ const SELECTION_BACKGROUND = theme.text;
20
+ const SELECTION_FOREGROUND = "#1F2329";
21
+ const FAKE_CURSOR_GLYPH = "█";
22
+ function charWidth(ch) {
23
+ if (ZERO_WIDTH_CHARS.has(ch))
24
+ return 0;
25
+ const cp = ch.codePointAt(0) ?? 0;
26
+ return cp > 0x2e7f ? 2 : 1;
27
+ }
28
+ function stringWidth(text) {
29
+ let width = 0;
30
+ for (const ch of text) {
31
+ width += charWidth(ch);
32
+ }
33
+ return width;
34
+ }
35
+ function trimToWidth(text, width) {
36
+ if (width <= 0)
37
+ return "";
38
+ let out = "";
39
+ let used = 0;
40
+ for (const ch of text) {
41
+ const next = charWidth(ch);
42
+ if (used + next > width)
43
+ break;
44
+ out += ch;
45
+ used += next;
46
+ }
47
+ return out;
48
+ }
49
+ function padToWidth(text, width) {
50
+ const trimmed = trimToWidth(text, width);
51
+ const padding = Math.max(0, width - stringWidth(trimmed));
52
+ return trimmed + " ".repeat(padding);
53
+ }
54
+ function getPreviousOffset(text, offset) {
55
+ if (offset <= 0)
56
+ return 0;
57
+ const previous = offset - 1;
58
+ const previousCode = text.charCodeAt(previous);
59
+ if (previous > 0 &&
60
+ previousCode >= 0xdc00 &&
61
+ previousCode <= 0xdfff) {
62
+ const lead = text.charCodeAt(previous - 1);
63
+ if (lead >= 0xd800 && lead <= 0xdbff) {
64
+ return previous - 1;
65
+ }
66
+ }
67
+ return previous;
68
+ }
69
+ function getNextOffset(text, offset) {
70
+ if (offset >= text.length)
71
+ return text.length;
72
+ const code = text.charCodeAt(offset);
73
+ if (offset + 1 < text.length &&
74
+ code >= 0xd800 &&
75
+ code <= 0xdbff) {
76
+ const trail = text.charCodeAt(offset + 1);
77
+ if (trail >= 0xdc00 && trail <= 0xdfff) {
78
+ return offset + 2;
79
+ }
80
+ }
81
+ return offset + 1;
82
+ }
83
+ function isBackspaceInput(inputChar, key) {
84
+ return (key.backspace === true ||
85
+ inputChar === "\u007f" ||
86
+ inputChar === "\b" ||
87
+ (key.ctrl === true && inputChar === "h") ||
88
+ (key.delete === true && inputChar === ""));
89
+ }
90
+ function isDeleteInput(inputChar, key) {
91
+ return inputChar === "[3~" || inputChar === "\u001b[3~";
92
+ }
93
+ function summarizeKey(key) {
94
+ return Object.fromEntries(Object.entries(key).filter(([, value]) => value === true));
95
+ }
96
+ function getPromptLabel(bashMode) {
97
+ return bashMode ? BASH_PROMPT : DEFAULT_PROMPT;
98
+ }
99
+ function getPlaceholder(bashMode) {
100
+ return bashMode ? "! for bash mode" : "/ for commands";
101
+ }
102
+ function formatSuggestionLabel(suggestion) {
103
+ return suggestion.type === "goal"
104
+ ? ` ${suggestion.name} ${suggestion.description.padEnd(20)} [goal]`
105
+ : ` ${suggestion.name.padEnd(20)}${suggestion.description}`;
106
+ }
107
+ function normalizeSelection(selection) {
108
+ if (!selection || selection.anchor === selection.focus) {
109
+ return null;
110
+ }
111
+ return {
112
+ start: Math.min(selection.anchor, selection.focus),
113
+ end: Math.max(selection.anchor, selection.focus),
114
+ };
115
+ }
116
+ function pushSegment(segments, text, style = {}) {
117
+ if (text.length === 0)
118
+ return;
119
+ const previous = segments[segments.length - 1];
120
+ if (previous &&
121
+ previous.color === style.color &&
122
+ previous.backgroundColor === style.backgroundColor &&
123
+ previous.bold === style.bold &&
124
+ previous.dim === style.dim) {
125
+ previous.text += text;
126
+ return;
127
+ }
128
+ segments.push({ text, ...style });
129
+ }
130
+ function buildInputRows(input, cursorOffset, contentWidth, placeholder, selection) {
131
+ if (contentWidth <= 0) {
132
+ return {
133
+ rows: [{
134
+ cells: [{
135
+ text: CARET_MARKER,
136
+ width: 0,
137
+ offsetBefore: cursorOffset,
138
+ offsetAfter: cursorOffset,
139
+ }],
140
+ startOffset: cursorOffset,
141
+ endOffset: cursorOffset,
142
+ }],
143
+ };
144
+ }
145
+ if (input.length === 0) {
146
+ const cells = [{
147
+ text: CARET_MARKER,
148
+ width: 0,
149
+ offsetBefore: 0,
150
+ offsetAfter: 0,
151
+ }];
152
+ for (const ch of trimToWidth(placeholder, Math.max(0, contentWidth - 1))) {
153
+ cells.push({
154
+ text: ch,
155
+ width: charWidth(ch),
156
+ offsetBefore: 0,
157
+ offsetAfter: 0,
158
+ placeholder: true,
159
+ });
160
+ }
161
+ return {
162
+ rows: [{
163
+ cells,
164
+ startOffset: 0,
165
+ endOffset: 0,
166
+ }],
167
+ };
168
+ }
169
+ const rows = [];
170
+ let currentCells = [];
171
+ let currentWidth = 0;
172
+ let rowStartOffset = 0;
173
+ let rowEndOffset = 0;
174
+ const pushRow = () => {
175
+ rows.push({
176
+ cells: currentCells,
177
+ startOffset: rowStartOffset,
178
+ endOffset: rowEndOffset,
179
+ });
180
+ currentCells = [];
181
+ currentWidth = 0;
182
+ };
183
+ let offset = 0;
184
+ while (offset <= input.length) {
185
+ if (offset === cursorOffset) {
186
+ if (currentWidth >= contentWidth && currentCells.length > 0) {
187
+ pushRow();
188
+ rowStartOffset = offset;
189
+ rowEndOffset = offset;
190
+ }
191
+ currentCells.push({
192
+ text: CARET_MARKER,
193
+ width: 0,
194
+ offsetBefore: offset,
195
+ offsetAfter: offset,
196
+ });
197
+ }
198
+ if (offset === input.length) {
199
+ break;
200
+ }
201
+ const codePoint = input.codePointAt(offset) ?? 0;
202
+ const ch = String.fromCodePoint(codePoint);
203
+ const nextOffset = offset + ch.length;
204
+ if (ch === "\n") {
205
+ pushRow();
206
+ rowStartOffset = nextOffset;
207
+ rowEndOffset = nextOffset;
208
+ offset = nextOffset;
209
+ continue;
210
+ }
211
+ const width = charWidth(ch);
212
+ if (currentWidth + width > contentWidth && currentCells.length > 0) {
213
+ pushRow();
214
+ rowStartOffset = offset;
215
+ rowEndOffset = offset;
216
+ }
217
+ currentCells.push({
218
+ text: ch,
219
+ width,
220
+ offsetBefore: offset,
221
+ offsetAfter: nextOffset,
222
+ selected: selection !== null &&
223
+ offset < selection.end &&
224
+ nextOffset > selection.start,
225
+ });
226
+ currentWidth += width;
227
+ rowEndOffset = nextOffset;
228
+ offset = nextOffset;
229
+ }
230
+ rows.push({
231
+ cells: currentCells,
232
+ startOffset: rowStartOffset,
233
+ endOffset: rowEndOffset,
234
+ });
235
+ return { rows };
236
+ }
237
+ function buildInputContentSegments(row, contentWidth, bashMode) {
238
+ const segments = [];
239
+ const defaultColor = bashMode ? theme.command : undefined;
240
+ let usedWidth = 0;
241
+ for (const cell of row.cells) {
242
+ if (cell.text === CARET_MARKER) {
243
+ pushSegment(segments, FAKE_CURSOR_GLYPH, {
244
+ color: theme.text,
245
+ bold: true,
246
+ });
247
+ usedWidth += 1;
248
+ continue;
249
+ }
250
+ usedWidth += cell.width;
251
+ if (cell.selected) {
252
+ pushSegment(segments, cell.text, {
253
+ color: SELECTION_FOREGROUND,
254
+ backgroundColor: SELECTION_BACKGROUND,
255
+ });
256
+ continue;
257
+ }
258
+ pushSegment(segments, cell.text, {
259
+ color: defaultColor,
260
+ dim: cell.placeholder,
261
+ });
262
+ }
263
+ if (usedWidth < contentWidth) {
264
+ pushSegment(segments, " ".repeat(contentWidth - usedWidth), {
265
+ color: defaultColor,
266
+ });
267
+ }
268
+ return segments;
269
+ }
270
+ function buildCursorEscapeFromComposerLayout(layout) {
271
+ for (let rowIndex = 0; rowIndex < layout.rows.length; rowIndex += 1) {
272
+ const row = layout.rows[rowIndex];
273
+ if (!row)
274
+ continue;
275
+ let colOffset = 0;
276
+ for (const cell of row.cells) {
277
+ if (cell.text === CARET_MARKER) {
278
+ return `\x1b[${layout.startLine + rowIndex};${layout.contentStartCol + colOffset}H\x1b[?25l`;
279
+ }
280
+ colOffset += cell.width;
281
+ }
282
+ }
283
+ return null;
284
+ }
285
+ function getCursorAnchorFromComposerLayout(layout) {
286
+ for (let rowIndex = 0; rowIndex < layout.rows.length; rowIndex += 1) {
287
+ const row = layout.rows[rowIndex];
288
+ if (!row)
289
+ continue;
290
+ let colOffset = 0;
291
+ for (const cell of row.cells) {
292
+ if (cell.text === CARET_MARKER) {
293
+ return {
294
+ caretRowOffset: rowIndex,
295
+ caretColumnOffset: colOffset,
296
+ };
297
+ }
298
+ colOffset += cell.width;
299
+ }
300
+ }
301
+ return null;
302
+ }
303
+ function buildComposerLines(args) {
304
+ const { cols, input, cursorOffset, bashMode, emptyHint, matches, selectedIdx, copyToast, selection, } = args;
305
+ const lines = [];
306
+ lines.push({
307
+ key: "copy-toast",
308
+ text: padToWidth(copyToast ?? "", cols),
309
+ color: copyToast ? "cyan" : undefined,
310
+ });
311
+ const innerWidth = Math.max(1, cols - 2);
312
+ const promptLabel = getPromptLabel(bashMode);
313
+ const prompt = `${promptLabel} `;
314
+ const promptWidth = stringWidth(prompt);
315
+ const contentWidth = Math.max(1, innerWidth - INPUT_MARGIN - promptWidth);
316
+ const inputRender = buildInputRows(input, cursorOffset, contentWidth, getPlaceholder(bashMode), selection);
317
+ const inputRows = inputRender.rows;
318
+ lines.push({
319
+ key: "composer-top",
320
+ text: padToWidth(`┌${"─".repeat(Math.max(0, cols - 2))}┐`, cols),
321
+ color: bashMode ? theme.command : theme.border,
322
+ });
323
+ inputRows.forEach((row, index) => {
324
+ const segments = [];
325
+ const borderColor = bashMode ? theme.command : undefined;
326
+ const promptColor = bashMode ? theme.command : theme.userPrompt;
327
+ pushSegment(segments, "│ ", { color: borderColor });
328
+ if (index === 0) {
329
+ pushSegment(segments, promptLabel, { color: promptColor, bold: true });
330
+ pushSegment(segments, " ", { color: promptColor, bold: true });
331
+ }
332
+ else {
333
+ pushSegment(segments, " ".repeat(promptWidth), { color: borderColor });
334
+ }
335
+ segments.push(...buildInputContentSegments(row, contentWidth, bashMode));
336
+ pushSegment(segments, " │", { color: borderColor });
337
+ lines.push({
338
+ key: `composer-row-${index}`,
339
+ segments,
340
+ protected: true,
341
+ });
342
+ });
343
+ lines.push({
344
+ key: "composer-bottom",
345
+ text: padToWidth(`└${"─".repeat(Math.max(0, cols - 2))}┘`, cols),
346
+ color: bashMode ? theme.command : theme.border,
347
+ });
348
+ if (bashMode) {
349
+ lines.push({
350
+ key: "bash-hint",
351
+ text: padToWidth("! for bash mode", cols),
352
+ color: theme.command,
353
+ });
354
+ }
355
+ if (emptyHint) {
356
+ lines.push({
357
+ key: "empty-hint",
358
+ text: padToWidth(" Type a message or /help for commands", cols),
359
+ dim: true,
360
+ });
361
+ }
362
+ if (matches.length > 0) {
363
+ matches.forEach((suggestion, index) => {
364
+ lines.push({
365
+ key: `suggestion-${index}`,
366
+ text: padToWidth(formatSuggestionLabel(suggestion), cols),
367
+ color: index === selectedIdx ? theme.selected : undefined,
368
+ bold: index === selectedIdx,
369
+ dim: index !== selectedIdx,
370
+ });
371
+ });
372
+ lines.push({
373
+ key: "suggestion-hint",
374
+ text: padToWidth(SUGGESTION_HINT, cols),
375
+ dim: true,
376
+ });
377
+ }
378
+ return {
379
+ lines,
380
+ inputRows,
381
+ inputRowStartIndex: 2,
382
+ contentStartCol: 3 + promptWidth,
383
+ };
384
+ }
385
+ function renderMessageRow(row, cols) {
386
+ if (row.kind === "spacer") {
387
+ return { key: row.key, text: " ".repeat(cols) };
388
+ }
389
+ return {
390
+ key: row.key,
391
+ text: padToWidth(row.text, cols),
392
+ color: row.color,
393
+ backgroundColor: row.backgroundColor,
394
+ bold: row.bold,
395
+ dim: row.dim,
396
+ };
397
+ }
398
+ function getMouseOffsetFromComposer(layout, x, y, clampOutside) {
399
+ if (layout.rows.length === 0) {
400
+ return null;
401
+ }
402
+ let rowIndex = y - layout.startLine;
403
+ if (rowIndex < 0) {
404
+ if (!clampOutside)
405
+ return null;
406
+ rowIndex = 0;
407
+ }
408
+ if (rowIndex >= layout.rows.length) {
409
+ if (!clampOutside)
410
+ return null;
411
+ rowIndex = layout.rows.length - 1;
412
+ }
413
+ const row = layout.rows[rowIndex];
414
+ if (!row) {
415
+ return null;
416
+ }
417
+ if (row.startOffset === row.endOffset) {
418
+ return row.startOffset;
419
+ }
420
+ const localCol = x - layout.contentStartCol;
421
+ if (localCol <= 0) {
422
+ return row.startOffset;
423
+ }
424
+ let usedWidth = 0;
425
+ for (const cell of row.cells) {
426
+ if (cell.placeholder || cell.width <= 0) {
427
+ continue;
428
+ }
429
+ const midpoint = usedWidth + cell.width / 2;
430
+ if (localCol <= midpoint) {
431
+ return cell.offsetBefore;
432
+ }
433
+ usedWidth += cell.width;
434
+ if (localCol <= usedWidth) {
435
+ return cell.offsetAfter;
436
+ }
437
+ }
438
+ return row.endOffset;
439
+ }
440
+ export function FullscreenChat({ messages, onSubmit, onClear, isProcessing, goalNames = [], availableRows, availableCols, }) {
441
+ const [input, setInput] = useState("");
442
+ const [cursorOffset, setCursorOffset] = useState(0);
443
+ const [selection, setSelection] = useState(null);
444
+ const selectionAnchor = React.useRef(null);
445
+ const [selectedIdx, setSelectedIdx] = useState(0);
446
+ const justSelected = React.useRef(false);
447
+ const [history, setHistory] = React.useState([]);
448
+ const [historyIdx, setHistoryIdx] = React.useState(-1);
449
+ const [draft, setDraft] = React.useState("");
450
+ const [emptyHint, setEmptyHint] = React.useState(false);
451
+ const [copyToast, setCopyToast] = useState(null);
452
+ const emptyHintTimer = React.useRef(null);
453
+ const [scrollOffset, setScrollOffset] = React.useState(0);
454
+ const [spinnerVerb, setSpinnerVerb] = React.useState(() => pickSpinnerVerb());
455
+ React.useEffect(() => {
456
+ let lastClipboard = "";
457
+ let mounted = true;
458
+ getClipboardContent().then((content) => {
459
+ if (mounted)
460
+ lastClipboard = content;
461
+ });
462
+ const interval = setInterval(async () => {
463
+ if (!mounted)
464
+ return;
465
+ const current = await getClipboardContent();
466
+ if (current !== lastClipboard && current.length > 0) {
467
+ lastClipboard = current;
468
+ setCopyToast(`copied ${current.length} chars to clipboard`);
469
+ setTimeout(() => {
470
+ if (mounted)
471
+ setCopyToast(null);
472
+ }, 2000);
473
+ }
474
+ }, 500);
475
+ return () => {
476
+ mounted = false;
477
+ clearInterval(interval);
478
+ };
479
+ }, []);
480
+ React.useEffect(() => {
481
+ if (!isProcessing)
482
+ return;
483
+ const interval = setInterval(() => {
484
+ setSpinnerVerb(pickSpinnerVerb());
485
+ }, 5000);
486
+ return () => clearInterval(interval);
487
+ }, [isProcessing]);
488
+ const clearSelection = useCallback(() => {
489
+ selectionAnchor.current = null;
490
+ setSelection(null);
491
+ }, []);
492
+ const replaceInputRange = useCallback((start, end, replacement) => {
493
+ const next = input.slice(0, start) + replacement + input.slice(end);
494
+ setInput(next);
495
+ setCursorOffset(start + replacement.length);
496
+ clearSelection();
497
+ }, [clearSelection, input]);
498
+ const insertText = useCallback((text) => {
499
+ justSelected.current = false;
500
+ const selectedRange = normalizeSelection(selection);
501
+ if (selectedRange) {
502
+ replaceInputRange(selectedRange.start, selectedRange.end, text);
503
+ return;
504
+ }
505
+ const next = input.slice(0, cursorOffset) + text + input.slice(cursorOffset);
506
+ setInput(next);
507
+ setCursorOffset(cursorOffset + text.length);
508
+ clearSelection();
509
+ }, [clearSelection, cursorOffset, input, replaceInputRange, selection]);
510
+ const deleteSelection = useCallback(() => {
511
+ const selectedRange = normalizeSelection(selection);
512
+ if (!selectedRange) {
513
+ return false;
514
+ }
515
+ replaceInputRange(selectedRange.start, selectedRange.end, "");
516
+ return true;
517
+ }, [replaceInputRange, selection]);
518
+ const matches = justSelected.current ? [] : getMatchingSuggestions(input, goalNames);
519
+ const hasMatches = matches.length > 0;
520
+ const bashMode = isBashModeInput(input);
521
+ const normalizedSelection = normalizeSelection(selection);
522
+ const composer = buildComposerLines({
523
+ cols: availableCols,
524
+ input,
525
+ cursorOffset,
526
+ bashMode,
527
+ emptyHint,
528
+ matches,
529
+ selectedIdx,
530
+ copyToast,
531
+ selection: normalizedSelection,
532
+ });
533
+ const messageRows = Math.max(1, availableRows - composer.lines.length - 3);
534
+ const viewport = buildChatViewport(messages, availableCols, messageRows, scrollOffset);
535
+ const composerLayout = {
536
+ startLine: viewport.maxVisibleRows + 3 + composer.inputRowStartIndex + 1,
537
+ contentStartCol: composer.contentStartCol,
538
+ rows: composer.inputRows,
539
+ };
540
+ setActiveCursorEscape(buildCursorEscapeFromComposerLayout(composerLayout));
541
+ const promptCursorAnchor = getCursorAnchorFromComposerLayout(composerLayout);
542
+ setActivePromptCursorAnchor(promptCursorAnchor
543
+ ? {
544
+ promptLabel: getPromptLabel(bashMode),
545
+ ...promptCursorAnchor,
546
+ }
547
+ : null);
548
+ React.useEffect(() => {
549
+ return () => {
550
+ setActiveCursorEscape(null);
551
+ setActivePromptCursorAnchor(null);
552
+ };
553
+ }, []);
554
+ const applyScroll = useCallback((direction, kind) => {
555
+ setScrollOffset((prev) => {
556
+ const maxOffset = Math.max(0, viewport.totalRows - 1);
557
+ const amount = kind === "page" ? viewport.maxVisibleRows : SCROLL_LINE_STEP;
558
+ const delta = direction === "up" ? amount : -amount;
559
+ return Math.max(0, Math.min(maxOffset, prev + delta));
560
+ });
561
+ }, [viewport.maxVisibleRows, viewport.totalRows]);
562
+ const handleSubmit = useCallback((value) => {
563
+ logTuiDebug("fullscreen-chat", "submit-attempt", {
564
+ value,
565
+ hasMatches,
566
+ isProcessing,
567
+ });
568
+ if (hasMatches || isProcessing)
569
+ return;
570
+ if (!value.trim()) {
571
+ setEmptyHint(true);
572
+ if (emptyHintTimer.current)
573
+ clearTimeout(emptyHintTimer.current);
574
+ emptyHintTimer.current = setTimeout(() => setEmptyHint(false), 1500);
575
+ return;
576
+ }
577
+ const trimmed = value.trim();
578
+ if (trimmed === "/clear") {
579
+ onClear?.();
580
+ setInput("");
581
+ setCursorOffset(0);
582
+ clearSelection();
583
+ setHistory((prev) => [...prev, trimmed]);
584
+ setHistoryIdx(-1);
585
+ setScrollOffset(0);
586
+ return;
587
+ }
588
+ onSubmit(trimmed);
589
+ setInput("");
590
+ setCursorOffset(0);
591
+ clearSelection();
592
+ setHistory((prev) => [...prev, trimmed]);
593
+ setHistoryIdx(-1);
594
+ setScrollOffset(0);
595
+ }, [clearSelection, hasMatches, isProcessing, onClear, onSubmit]);
596
+ useInput((inputChar, key) => {
597
+ logTuiDebug("fullscreen-chat", "input-event", {
598
+ inputChar,
599
+ key: summarizeKey(key),
600
+ input,
601
+ cursorOffset,
602
+ selection: normalizedSelection,
603
+ historyIdx,
604
+ });
605
+ const scrollRequest = getScrollRequest(inputChar, key);
606
+ if (scrollRequest) {
607
+ logTuiDebug("fullscreen-chat", "scroll-request", {
608
+ direction: scrollRequest.direction,
609
+ kind: scrollRequest.kind,
610
+ });
611
+ applyScroll(scrollRequest.direction, scrollRequest.kind);
612
+ return;
613
+ }
614
+ const mouseEvent = parseMouseEvent(inputChar);
615
+ if (mouseEvent && mouseEvent.kind !== "wheel" && mouseEvent.button === "left") {
616
+ const offset = getMouseOffsetFromComposer(composerLayout, mouseEvent.x, mouseEvent.y, mouseEvent.kind !== "press" && selectionAnchor.current !== null);
617
+ if (mouseEvent.kind === "release" && offset === null) {
618
+ selectionAnchor.current = null;
619
+ return;
620
+ }
621
+ if (offset !== null) {
622
+ justSelected.current = false;
623
+ setCursorOffset(offset);
624
+ if (mouseEvent.kind === "press") {
625
+ selectionAnchor.current = offset;
626
+ setSelection({ anchor: offset, focus: offset });
627
+ }
628
+ else if (mouseEvent.kind === "drag" && selectionAnchor.current !== null) {
629
+ setSelection({ anchor: selectionAnchor.current, focus: offset });
630
+ }
631
+ else if (mouseEvent.kind === "release" && selectionAnchor.current !== null) {
632
+ const nextSelection = { anchor: selectionAnchor.current, focus: offset };
633
+ selectionAnchor.current = null;
634
+ setSelection(nextSelection.anchor === nextSelection.focus ? null : nextSelection);
635
+ }
636
+ return;
637
+ }
638
+ }
639
+ if (key.return && key.shift) {
640
+ logTuiDebug("fullscreen-chat", "insert-newline", { cursorOffset });
641
+ insertText("\n");
642
+ return;
643
+ }
644
+ if (hasMatches) {
645
+ if (key.upArrow) {
646
+ setSelectedIdx((prev) => (prev <= 0 ? matches.length - 1 : prev - 1));
647
+ return;
648
+ }
649
+ if (key.downArrow) {
650
+ setSelectedIdx((prev) => (prev >= matches.length - 1 ? 0 : prev + 1));
651
+ return;
652
+ }
653
+ if (key.tab || key.return) {
654
+ const selected = matches[selectedIdx];
655
+ if (selected) {
656
+ const value = selected.type === "goal"
657
+ ? `${selected.name} ${selected.description}`
658
+ : selected.name;
659
+ setInput(value);
660
+ setCursorOffset(value.length);
661
+ clearSelection();
662
+ setSelectedIdx(0);
663
+ justSelected.current = true;
664
+ }
665
+ return;
666
+ }
667
+ if (key.escape) {
668
+ setSelectedIdx(0);
669
+ setInput("");
670
+ setCursorOffset(0);
671
+ clearSelection();
672
+ return;
673
+ }
674
+ }
675
+ if (key.return) {
676
+ handleSubmit(input);
677
+ return;
678
+ }
679
+ if (key.leftArrow) {
680
+ if (normalizedSelection) {
681
+ setCursorOffset(normalizedSelection.start);
682
+ clearSelection();
683
+ return;
684
+ }
685
+ setCursorOffset((prev) => Math.max(0, prev - 1));
686
+ return;
687
+ }
688
+ if (key.rightArrow) {
689
+ if (normalizedSelection) {
690
+ setCursorOffset(normalizedSelection.end);
691
+ clearSelection();
692
+ return;
693
+ }
694
+ setCursorOffset((prev) => Math.min(input.length, prev + 1));
695
+ return;
696
+ }
697
+ if ((key.ctrl && inputChar === "a") || key.home) {
698
+ setCursorOffset(0);
699
+ clearSelection();
700
+ return;
701
+ }
702
+ if ((key.ctrl && inputChar === "e") || key.end) {
703
+ setCursorOffset(input.length);
704
+ clearSelection();
705
+ return;
706
+ }
707
+ if (isBackspaceInput(inputChar, key)) {
708
+ logTuiDebug("fullscreen-chat", "backspace-detected", {
709
+ inputChar,
710
+ key: summarizeKey(key),
711
+ cursorOffset,
712
+ input,
713
+ selection: normalizedSelection,
714
+ });
715
+ if (deleteSelection()) {
716
+ logTuiDebug("fullscreen-chat", "backspace-delete-selection", {});
717
+ return;
718
+ }
719
+ if (cursorOffset > 0) {
720
+ const previousOffset = getPreviousOffset(input, cursorOffset);
721
+ const next = input.slice(0, previousOffset) + input.slice(cursorOffset);
722
+ setInput(next);
723
+ setCursorOffset(previousOffset);
724
+ logTuiDebug("fullscreen-chat", "backspace-applied", {
725
+ previousOffset,
726
+ next,
727
+ });
728
+ }
729
+ else {
730
+ logTuiDebug("fullscreen-chat", "backspace-at-start", {});
731
+ }
732
+ return;
733
+ }
734
+ if (isDeleteInput(inputChar, key)) {
735
+ logTuiDebug("fullscreen-chat", "delete-detected", {
736
+ inputChar,
737
+ key: summarizeKey(key),
738
+ cursorOffset,
739
+ input,
740
+ selection: normalizedSelection,
741
+ });
742
+ if (deleteSelection()) {
743
+ logTuiDebug("fullscreen-chat", "delete-selection", {});
744
+ return;
745
+ }
746
+ if (cursorOffset < input.length) {
747
+ const nextOffset = getNextOffset(input, cursorOffset);
748
+ const next = input.slice(0, cursorOffset) + input.slice(nextOffset);
749
+ setInput(next);
750
+ logTuiDebug("fullscreen-chat", "delete-applied", {
751
+ nextOffset,
752
+ next,
753
+ });
754
+ }
755
+ else {
756
+ logTuiDebug("fullscreen-chat", "delete-at-end", {});
757
+ }
758
+ return;
759
+ }
760
+ if (key.upArrow) {
761
+ if (history.length > 0) {
762
+ clearSelection();
763
+ if (historyIdx === -1) {
764
+ setDraft(input);
765
+ const idx = history.length - 1;
766
+ setHistoryIdx(idx);
767
+ setInput(history[idx]);
768
+ setCursorOffset(history[idx].length);
769
+ }
770
+ else if (historyIdx > 0) {
771
+ const idx = historyIdx - 1;
772
+ setHistoryIdx(idx);
773
+ setInput(history[idx]);
774
+ setCursorOffset(history[idx].length);
775
+ }
776
+ }
777
+ return;
778
+ }
779
+ if (key.downArrow && historyIdx !== -1) {
780
+ clearSelection();
781
+ if (historyIdx < history.length - 1) {
782
+ const idx = historyIdx + 1;
783
+ setHistoryIdx(idx);
784
+ setInput(history[idx]);
785
+ setCursorOffset(history[idx].length);
786
+ }
787
+ else {
788
+ setHistoryIdx(-1);
789
+ setInput(draft);
790
+ setCursorOffset(draft.length);
791
+ }
792
+ return;
793
+ }
794
+ if (inputChar && !key.ctrl && !key.meta) {
795
+ const clean = stripMouseEscapeSequences(inputChar);
796
+ if (clean.length === 0)
797
+ return;
798
+ insertText(clean);
799
+ }
800
+ }, { isActive: !isProcessing });
801
+ React.useEffect(() => {
802
+ setSelectedIdx(0);
803
+ }, [matches.map((match) => match.name).join(",")]);
804
+ React.useEffect(() => {
805
+ return () => {
806
+ if (emptyHintTimer.current)
807
+ clearTimeout(emptyHintTimer.current);
808
+ };
809
+ }, []);
810
+ const lines = [];
811
+ lines.push({
812
+ key: "indicator-top",
813
+ text: padToWidth(viewport.hiddenAboveRows > 0 ? `↑ ${viewport.hiddenAboveRows} earlier lines` : "", availableCols),
814
+ dim: true,
815
+ });
816
+ const renderedRows = viewport.rows.map((row) => renderMessageRow(row, availableCols));
817
+ const fillerCount = Math.max(0, viewport.maxVisibleRows - renderedRows.length);
818
+ for (let index = 0; index < fillerCount; index += 1) {
819
+ lines.push({ key: `filler-${index}`, text: " ".repeat(availableCols) });
820
+ }
821
+ lines.push(...renderedRows);
822
+ lines.push({
823
+ key: "processing",
824
+ text: padToWidth(isProcessing ? `⠋ ${spinnerVerb}...` : "", availableCols),
825
+ dim: !isProcessing,
826
+ });
827
+ lines.push({
828
+ key: "indicator-bottom",
829
+ text: padToWidth(viewport.hiddenBelowRows > 0 ? `↓ ${viewport.hiddenBelowRows} newer lines` : "", availableCols),
830
+ dim: true,
831
+ });
832
+ lines.push(...composer.lines);
833
+ while (lines.length < availableRows) {
834
+ lines.push({
835
+ key: `tail-filler-${lines.length}`,
836
+ text: " ".repeat(availableCols),
837
+ });
838
+ }
839
+ const visibleLines = lines.slice(0, availableRows);
840
+ return (_jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleLines.map((line) => (_jsx(Box, { height: 1, overflow: "hidden", children: line.segments ? (line.segments.map((segment, index) => (_jsx(Text, { color: segment.color ?? line.color, backgroundColor: segment.backgroundColor ?? line.backgroundColor, bold: segment.bold ?? line.bold, dimColor: segment.dim ?? line.dim, children: index === 0 && line.protected
841
+ ? `${PROTECTED_ROW_MARKER}${segment.text}`
842
+ : segment.text }, `${line.key}-${index}`)))) : (_jsx(Text, { color: line.color, backgroundColor: line.backgroundColor, bold: line.bold, dimColor: line.dim, children: line.protected
843
+ ? `${PROTECTED_ROW_MARKER}${line.text ?? ""}`
844
+ : (line.text ?? "") })) }, line.key))) }));
845
+ }
846
+ //# sourceMappingURL=fullscreen-chat.js.map