@copado/agentia-cli 0.118.0-alpha.0 → 0.120.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +224 -293
  2. package/dist/commands/cicd/notification/configure.d.ts +1 -1
  3. package/dist/commands/cicd/work/deployment-step/create.d.ts +1 -1
  4. package/dist/commands/testing/build/run.d.ts +2 -2
  5. package/dist/commands/testing/data-table/create.d.ts +1 -1
  6. package/dist/commands/testing/data-table/update.d.ts +1 -1
  7. package/dist/commands/testing/job/create.d.ts +1 -1
  8. package/dist/commands/testing/job/schedule/set.d.ts +1 -1
  9. package/dist/commands/testing/job/update.d.ts +1 -1
  10. package/dist/commands/testing/robot/update.d.ts +1 -1
  11. package/dist/commands/testing/run.d.ts +2 -2
  12. package/dist/commands/testing/test/run.d.ts +2 -2
  13. package/dist/lib/ai-chat-session.d.ts +0 -3
  14. package/dist/lib/ai-chat-session.js +66 -182
  15. package/dist/lib/ai-chat-session.js.map +1 -1
  16. package/dist/lib/ai-gateway.d.ts +1 -8
  17. package/dist/lib/ai-gateway.js +2 -34
  18. package/dist/lib/ai-gateway.js.map +1 -1
  19. package/dist/lib/ai-types.d.ts +0 -18
  20. package/dist/lib/ai-types.js +1 -45
  21. package/dist/lib/ai-types.js.map +1 -1
  22. package/dist/lib/format.d.ts +0 -2
  23. package/dist/lib/format.js +2 -5
  24. package/dist/lib/format.js.map +1 -1
  25. package/dist/lib/loopback-callback-server.d.ts +1 -0
  26. package/dist/lib/loopback-callback-server.js +13 -6
  27. package/dist/lib/loopback-callback-server.js.map +1 -1
  28. package/dist/lib/mcp/tools.d.ts +2 -2
  29. package/dist/lib/testing-data-command.d.ts +1 -1
  30. package/dist/lib/testing-flags.d.ts +2 -2
  31. package/oclif.manifest.json +691 -842
  32. package/package.json +13 -12
  33. package/templates/skills/agentia-ai/SKILL.md +5 -5
  34. package/templates/skills/agentia-ai/references/ai-agents.md +3 -18
  35. package/dist/commands/ai/session/list.d.ts +0 -14
  36. package/dist/commands/ai/session/list.js +0 -46
  37. package/dist/commands/ai/session/list.js.map +0 -1
  38. package/dist/commands/ai/session/start.d.ts +0 -17
  39. package/dist/commands/ai/session/start.js +0 -60
  40. package/dist/commands/ai/session/start.js.map +0 -1
  41. package/dist/lib/ai-session-banner.d.ts +0 -5
  42. package/dist/lib/ai-session-banner.js +0 -20
  43. package/dist/lib/ai-session-banner.js.map +0 -1
  44. package/dist/lib/ai-session-tui-blocks.d.ts +0 -9
  45. package/dist/lib/ai-session-tui-blocks.js +0 -36
  46. package/dist/lib/ai-session-tui-blocks.js.map +0 -1
  47. package/dist/lib/ai-session-tui-clipboard.d.ts +0 -22
  48. package/dist/lib/ai-session-tui-clipboard.js +0 -78
  49. package/dist/lib/ai-session-tui-clipboard.js.map +0 -1
  50. package/dist/lib/ai-session-tui-composer.d.ts +0 -35
  51. package/dist/lib/ai-session-tui-composer.js +0 -96
  52. package/dist/lib/ai-session-tui-composer.js.map +0 -1
  53. package/dist/lib/ai-session-tui-highlight.d.ts +0 -11
  54. package/dist/lib/ai-session-tui-highlight.js +0 -310
  55. package/dist/lib/ai-session-tui-highlight.js.map +0 -1
  56. package/dist/lib/ai-session-tui-keys.d.ts +0 -42
  57. package/dist/lib/ai-session-tui-keys.js +0 -159
  58. package/dist/lib/ai-session-tui-keys.js.map +0 -1
  59. package/dist/lib/ai-session-tui-mermaid-image.d.ts +0 -43
  60. package/dist/lib/ai-session-tui-mermaid-image.js +0 -444
  61. package/dist/lib/ai-session-tui-mermaid-image.js.map +0 -1
  62. package/dist/lib/ai-session-tui-mermaid-paint.d.ts +0 -3
  63. package/dist/lib/ai-session-tui-mermaid-paint.js +0 -182
  64. package/dist/lib/ai-session-tui-mermaid-paint.js.map +0 -1
  65. package/dist/lib/ai-session-tui-mermaid.d.ts +0 -19
  66. package/dist/lib/ai-session-tui-mermaid.js +0 -158
  67. package/dist/lib/ai-session-tui-mermaid.js.map +0 -1
  68. package/dist/lib/ai-session-tui-message.d.ts +0 -9
  69. package/dist/lib/ai-session-tui-message.js +0 -24
  70. package/dist/lib/ai-session-tui-message.js.map +0 -1
  71. package/dist/lib/ai-session-tui-scroll.d.ts +0 -32
  72. package/dist/lib/ai-session-tui-scroll.js +0 -148
  73. package/dist/lib/ai-session-tui-scroll.js.map +0 -1
  74. package/dist/lib/ai-session-tui-slash.d.ts +0 -14
  75. package/dist/lib/ai-session-tui-slash.js +0 -53
  76. package/dist/lib/ai-session-tui-slash.js.map +0 -1
  77. package/dist/lib/ai-session-tui-welcome.d.ts +0 -12
  78. package/dist/lib/ai-session-tui-welcome.js +0 -33
  79. package/dist/lib/ai-session-tui-welcome.js.map +0 -1
  80. package/dist/lib/ai-session-tui-wrap.d.ts +0 -2
  81. package/dist/lib/ai-session-tui-wrap.js +0 -24
  82. package/dist/lib/ai-session-tui-wrap.js.map +0 -1
  83. package/dist/lib/ai-session-tui.d.ts +0 -7
  84. package/dist/lib/ai-session-tui.js +0 -859
  85. package/dist/lib/ai-session-tui.js.map +0 -1
  86. package/dist/lib/ai-sessions.d.ts +0 -17
  87. package/dist/lib/ai-sessions.js +0 -142
  88. package/dist/lib/ai-sessions.js.map +0 -1
@@ -1,859 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box, render, Text, useApp, useStdin, useStdout } from 'ink';
3
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
- import { v4 as uuidv4 } from 'uuid';
5
- import { AiGateway, isAiRequestAborted } from './ai-gateway.js';
6
- import { bootstrapAiDialogue } from './ai-operations.js';
7
- import { copyTextToClipboard, lastMarkdownCodeFence } from './ai-session-tui-clipboard.js';
8
- import { COMPOSER_MAX_ROWS, composerBackspace, composerDelete, composerEnd, composerHome, composerRowCount, createComposer, createPromptRecall, insertComposerText, moveComposerCursor, recallNewer, recallOlder, recordPrompt, visibleComposerRows, } from './ai-session-tui-composer.js';
9
- import { SessionInputAssembler } from './ai-session-tui-keys.js';
10
- import { detectMermaidImageProtocol, installStdoutImageBridge } from './ai-session-tui-mermaid-image.js';
11
- import { chatViewportHeight, clampScrollOffset, flattenTranscript, visibleTranscriptRows } from './ai-session-tui-scroll.js';
12
- import { clampSlashIndex, filterSlashCommands, selectedSlashCommand, sessionHelpLines, slashPaletteWindow, } from './ai-session-tui-slash.js';
13
- import { WELCOME_ACTIONS, WelcomeScreen } from './ai-session-tui-welcome.js';
14
- import { filterAiSessions, formatSessionOverlayLine, listAiSessions, pickSessionFromList } from './ai-sessions.js';
15
- import { agentForAssistantId, AI_CHAT_AGENTS, AI_SESSION_LIST_DEFAULT_PAGE_SIZE, assistantIdForAgent, dialogueAssistantId, dialogueMessageText, enrichAiPrompt, } from './ai-types.js';
16
- const LAUNCH_ENTER_GUARD_MS = 400;
17
- function discardPendingStdinBytes(stdin) {
18
- const stream = stdin;
19
- if (typeof stream.read !== 'function')
20
- return;
21
- const pending = stream.readableLength;
22
- if (pending === undefined || pending === 0)
23
- return;
24
- stream.read(pending);
25
- }
26
- const TOKEN_FLUSH_MS = 32;
27
- const MAX_FOLLOWUPS = 5;
28
- const SPINNER = '|/-\\';
29
- function helpText() {
30
- return `Commands: /help, /session, /${AI_CHAT_AGENTS.join(', /')}, /copy, /clear, /new, /exit`;
31
- }
32
- function normalizeAgent(value) {
33
- if (AI_CHAT_AGENTS.includes(value))
34
- return value;
35
- return undefined;
36
- }
37
- function slashAgent(line) {
38
- if (!line.startsWith('/'))
39
- return undefined;
40
- return normalizeAgent(line.slice(1));
41
- }
42
- function errorMessage(error) {
43
- return error instanceof Error ? error.message : String(error);
44
- }
45
- function historyLines(dialogue) {
46
- return (dialogue.messages ?? []).map((message, index) => ({
47
- id: `history-${dialogue.id}-${index}`,
48
- kind: message.role === 'human' ? 'you' : 'agent',
49
- text: dialogueMessageText(message),
50
- }));
51
- }
52
- function followupContent(value) {
53
- if (typeof value === 'string' && value.trim().length > 0)
54
- return value.trim();
55
- if (value === null || typeof value !== 'object' || Array.isArray(value))
56
- return undefined;
57
- const record = value;
58
- for (const candidate of [record.content, record.question, record.text]) {
59
- if (typeof candidate === 'string' && candidate.trim().length > 0)
60
- return candidate.trim();
61
- }
62
- return undefined;
63
- }
64
- function appendLinkLine(append, content) {
65
- if (content === null || typeof content !== 'object' || Array.isArray(content))
66
- return;
67
- const record = content;
68
- const title = typeof record.title === 'string' ? record.title : typeof record.summary === 'string' ? record.summary : undefined;
69
- const url = typeof record.url === 'string' ? record.url : undefined;
70
- const label = [title, url].filter((part) => part !== undefined && part.length > 0).join(' — ');
71
- if (label.length > 0)
72
- append({ kind: 'system', text: `Link: ${label}` });
73
- }
74
- function applyTurnStreamChunk(chunk, state) {
75
- if (chunk.type === 'token' && typeof chunk.content === 'string') {
76
- state.streamText.current += chunk.content;
77
- state.scheduleFlush();
78
- return;
79
- }
80
- if (chunk.type === 'status' && typeof chunk.content === 'string') {
81
- state.setStatus(chunk.content);
82
- return;
83
- }
84
- if (chunk.type === 'warning' && typeof chunk.content === 'string') {
85
- state.append({ kind: 'system', text: chunk.content });
86
- return;
87
- }
88
- if (chunk.type === 'followup') {
89
- const text = followupContent(chunk.content);
90
- if (text !== undefined && state.followups.length < MAX_FOLLOWUPS)
91
- state.followups.push(text);
92
- return;
93
- }
94
- if (chunk.type === 'link') {
95
- appendLinkLine(state.append, chunk.content);
96
- }
97
- }
98
- function lastFollowupItems(lines) {
99
- for (let index = lines.length - 1; index >= 0; index -= 1) {
100
- const line = lines[index];
101
- if (line?.kind === 'followups')
102
- return line.items ?? [];
103
- }
104
- return [];
105
- }
106
- function sessionChatHint(state) {
107
- if (state.slashOpen)
108
- return 'Tab complete · Enter run · Esc dismiss';
109
- if (state.scrollOffset > 0)
110
- return 'Reading history · ↓ to return to latest';
111
- if (state.busy)
112
- return 'Pinned to latest · Esc cancels this turn';
113
- if (state.followupCount > 0 && state.inputLength === 0) {
114
- return `Follow-ups: press 1–${state.followupCount} to send that suggestion`;
115
- }
116
- return '↑/↓ to scroll · ←/→ move the cursor · PgUp/PgDn (Fn+↑/Fn+↓) reuse a sent prompt';
117
- }
118
- function ignoreFailure(work) {
119
- work.catch(() => {
120
- // Errors are reported by the async command handlers themselves.
121
- });
122
- }
123
- function sessionKeyAsInk(key) {
124
- return {
125
- key: {
126
- backspace: key.type === 'backspace',
127
- delete: key.type === 'delete',
128
- downArrow: key.type === 'down',
129
- escape: key.type === 'escape',
130
- return: key.type === 'enter',
131
- tab: key.type === 'tab',
132
- upArrow: key.type === 'up',
133
- },
134
- value: key.type === 'text' ? key.value : '',
135
- };
136
- }
137
- function handleSessionOverlayInput(event, handlers) {
138
- const { key, live, value } = event;
139
- const filtered = filterAiSessions(live.sessions, live.sessionFilter);
140
- if (key.escape || value === '/cancel' || value === 'cancel') {
141
- handlers.setOverlay('none');
142
- return;
143
- }
144
- if (key.upArrow) {
145
- handlers.setSessionIndex((index) => Math.max(0, index - 1));
146
- return;
147
- }
148
- if (key.downArrow) {
149
- handlers.setSessionIndex((index) => Math.min(Math.max(0, filtered.length - 1), index + 1));
150
- return;
151
- }
152
- if (key.backspace || key.delete) {
153
- const next = live.sessionFilter.slice(0, -1);
154
- live.sessionFilter = next;
155
- handlers.setSessionFilter(next);
156
- handlers.setSessionIndex(() => 0);
157
- return;
158
- }
159
- if (value.length > 1 && !/^\d+$/u.test(value)) {
160
- const picked = pickSessionFromList(value, filtered);
161
- if (picked !== undefined) {
162
- ignoreFailure(handlers.resumeDialogue(picked.id));
163
- return;
164
- }
165
- }
166
- if (key.return && filtered[live.sessionIndex] !== undefined) {
167
- ignoreFailure(handlers.resumeDialogue(filtered[live.sessionIndex].id));
168
- return;
169
- }
170
- if (value.length > 0 && !key.return) {
171
- const next = live.sessionFilter + value;
172
- live.sessionFilter = next;
173
- handlers.setSessionFilter(next);
174
- handlers.setSessionIndex(() => 0);
175
- }
176
- }
177
- function handleWelcomeInput(event, handlers) {
178
- const { key, live, value } = event;
179
- if (key.upArrow) {
180
- handlers.setWelcomeIndex((index) => Math.max(0, index - 1));
181
- return;
182
- }
183
- if (key.downArrow) {
184
- const next = Math.min(WELCOME_ACTIONS.length - 1, live.welcomeIndex + 1);
185
- live.welcomeIndex = next;
186
- handlers.setWelcomeIndex(next);
187
- return;
188
- }
189
- if (value === '1') {
190
- ignoreFailure(handlers.startNew());
191
- return;
192
- }
193
- if (value === '2') {
194
- ignoreFailure(handlers.openSessionOverlay());
195
- return;
196
- }
197
- if (key.return) {
198
- ignoreFailure(WELCOME_ACTIONS[live.welcomeIndex]?.id === 'resume' ? handlers.openSessionOverlay() : handlers.startNew());
199
- }
200
- }
201
- function handleSlashPaletteInput(event, matches, handlers) {
202
- if (matches.length === 0)
203
- return false;
204
- const { key, live } = event;
205
- if (key.upArrow) {
206
- handlers.setSlashIndex((index) => Math.max(0, index - 1));
207
- return true;
208
- }
209
- if (key.downArrow) {
210
- handlers.setSlashIndex((index) => Math.min(matches.length - 1, index + 1));
211
- return true;
212
- }
213
- if (key.tab) {
214
- const selected = selectedSlashCommand(matches, live.slashIndex);
215
- if (selected !== undefined) {
216
- handlers.setComposer(createComposer(selected.command));
217
- }
218
- return true;
219
- }
220
- if (key.escape) {
221
- handlers.setSlashDismissed(true);
222
- return true;
223
- }
224
- if (key.return) {
225
- const selected = selectedSlashCommand(matches, live.slashIndex);
226
- if (selected !== undefined) {
227
- handlers.setComposer(createComposer());
228
- handlers.runCommand(selected.command);
229
- }
230
- return true;
231
- }
232
- return false;
233
- }
234
- function handleIdleChatInput(key, live, handlers) {
235
- const matches = live.slashDismissed ? [] : filterSlashCommands(live.input);
236
- const ink = sessionKeyAsInk(key);
237
- if (handleSlashPaletteInput({ key: ink.key, live }, matches, {
238
- runCommand: (command) => ignoreFailure(handlers.handleChatLine(command)),
239
- setComposer: handlers.setComposer,
240
- setSlashDismissed: handlers.setSlashDismissed,
241
- setSlashIndex: handlers.setSlashIndex,
242
- })) {
243
- return;
244
- }
245
- handlePromptInput(key, live, handlers);
246
- }
247
- function handlePromptInput(key, live, handlers) {
248
- const current = { cursor: live.cursor, text: live.input };
249
- if (key.type === 'tab')
250
- return;
251
- if (key.type === 'escape') {
252
- live.input = '';
253
- live.cursor = 0;
254
- handlers.setComposer(createComposer());
255
- return;
256
- }
257
- if (key.type === 'enter') {
258
- const line = live.input.trim();
259
- live.input = '';
260
- live.cursor = 0;
261
- handlers.setComposer(createComposer());
262
- if (line.length > 0)
263
- ignoreFailure(handlers.handleChatLine(line));
264
- return;
265
- }
266
- if (key.type === 'left') {
267
- handlers.setComposer(moveComposerCursor(current, -1));
268
- return;
269
- }
270
- if (key.type === 'right') {
271
- handlers.setComposer(moveComposerCursor(current, 1));
272
- return;
273
- }
274
- if (key.type === 'home') {
275
- handlers.setComposer(composerHome(current));
276
- return;
277
- }
278
- if (key.type === 'end') {
279
- handlers.setComposer(composerEnd(current));
280
- return;
281
- }
282
- if (key.type === 'backspace') {
283
- handlers.setComposer(composerBackspace(current));
284
- return;
285
- }
286
- if (key.type === 'delete') {
287
- handlers.setComposer(composerDelete(current));
288
- return;
289
- }
290
- if (key.type === 'text' && key.value.length > 0) {
291
- handlers.setComposer(insertComposerText(current, key.value));
292
- }
293
- }
294
- // eslint-disable-next-line complexity -- one dispatcher for welcome, overlays, composer, and slash
295
- function handleSessionAppKey(key, live, actions) {
296
- const ink = sessionKeyAsInk(key);
297
- const { value } = ink;
298
- if (live.busy) {
299
- if (key.type === 'escape')
300
- actions.abortTurn();
301
- return;
302
- }
303
- if (live.overlay === 'help') {
304
- if (key.type === 'escape' || key.type === 'enter')
305
- actions.setOverlay('none');
306
- return;
307
- }
308
- if (live.overlay === 'confirm-new') {
309
- if (key.type === 'escape') {
310
- actions.setOverlay('none');
311
- return;
312
- }
313
- if (key.type === 'enter') {
314
- actions.setOverlay('none');
315
- ignoreFailure(actions.startNew());
316
- }
317
- return;
318
- }
319
- if (live.overlay === 'session') {
320
- handleSessionOverlayInput({ key: ink.key, live, value }, {
321
- resumeDialogue: actions.resumeDialogue,
322
- setOverlay: actions.setOverlay,
323
- setSessionFilter: actions.setSessionFilter,
324
- setSessionIndex: actions.setSessionIndex,
325
- });
326
- return;
327
- }
328
- if (live.screen === 'welcome') {
329
- if (key.type === 'enter' && !actions.welcomeArmed)
330
- return;
331
- if (key.type === 'up' || key.type === 'down' || value.length > 0)
332
- actions.armWelcome();
333
- handleWelcomeInput({ key: ink.key, live, value }, { openSessionOverlay: actions.openSessionOverlay, setWelcomeIndex: actions.setWelcomeIndex, startNew: actions.startNew });
334
- return;
335
- }
336
- const composerHandlers = {
337
- handleChatLine: actions.handleChatLine,
338
- setComposer: actions.applyComposer,
339
- setSlashDismissed(dismissed) {
340
- live.slashDismissed = dismissed;
341
- actions.setSlashDismissed(dismissed);
342
- },
343
- setSlashIndex(updater) {
344
- actions.setSlashIndex((index) => {
345
- const next = updater(index);
346
- live.slashIndex = next;
347
- return next;
348
- });
349
- },
350
- };
351
- const slashOpen = !live.slashDismissed && filterSlashCommands(live.input).length > 0;
352
- if (slashOpen) {
353
- handleIdleChatInput(key, live, composerHandlers);
354
- return;
355
- }
356
- if (live.screen === 'chat' && live.overlay === 'none') {
357
- if (key.type === 'up') {
358
- actions.setScrollOffset((offset) => clampScrollOffset(actions.displayRowsLength, actions.viewport, offset + 1));
359
- return;
360
- }
361
- if (key.type === 'down') {
362
- actions.setScrollOffset((offset) => clampScrollOffset(actions.displayRowsLength, actions.viewport, offset - 1));
363
- return;
364
- }
365
- }
366
- if (key.type === 'text' && live.input.length === 0 && /^\d$/u.test(key.value)) {
367
- const picked = actions.followupItems[Number.parseInt(key.value, 10) - 1];
368
- if (picked !== undefined) {
369
- ignoreFailure(actions.handleChatLine(picked));
370
- return;
371
- }
372
- }
373
- if (key.type === 'page-up' || key.type === 'page-down') {
374
- const recalled = key.type === 'page-up' ? recallOlder(actions.recall, live.input) : recallNewer(actions.recall, live.input);
375
- actions.setRecall(recalled.recall);
376
- actions.applyComposer(createComposer(recalled.text));
377
- return;
378
- }
379
- handleIdleChatInput(key, live, composerHandlers);
380
- }
381
- function TranscriptRow(props) {
382
- const { row } = props;
383
- if (row.imageSlot !== undefined) {
384
- return (_jsx(Text, { color: row.imageColor, wrap: "truncate", children: row.text.length === 0 ? ' ' : row.text }));
385
- }
386
- if (row.spans !== undefined && row.spans.length > 0) {
387
- return (_jsx(Text, { wrap: "truncate", children: row.spans.map((span, index) => (_jsx(Text, { backgroundColor: span.backgroundColor, color: span.color, dimColor: span.dim === true, children: span.text.length === 0 ? ' ' : span.text }, `${row.id}-${index}`))) }));
388
- }
389
- return (_jsx(Text, { bold: row.bold, color: row.color, dimColor: row.dim, wrap: "truncate", children: row.text.length === 0 ? ' ' : row.text }));
390
- }
391
- function ChatLivePane(props) {
392
- return (_jsxs(Box, { flexDirection: "column", height: props.height, children: [_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { bold: true, wrap: "truncate", children: [props.agent, " ", props.dialogueId] }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: helpText() }) }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: props.hint }) }), _jsx(Box, { flexDirection: "column", height: props.viewport, overflow: "hidden", children: props.rows.map((row) => (_jsx(TranscriptRow, { row: row }, row.id))) }), props.status.length > 0 || props.busy ? (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: "yellow", dimColor: true, wrap: "truncate", children: props.busy
393
- ? `${SPINNER[props.elapsedSeconds % SPINNER.length]} ${props.status.length > 0 ? props.status : 'Waiting…'} ${props.elapsedSeconds}s`
394
- : props.status }) })) : null, props.suggestions.map((item, index) => (_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { color: index === props.selectedSlash ? 'green' : undefined, wrap: "truncate", children: [index === props.selectedSlash ? '●' : ' ', " ", item.command, " ", item.description] }) }, item.command))), props.composerRows.map((row, index) => (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { children: index === 0 ? '> ' : ' ' }), row.hasCaret && !props.busy ? (_jsxs(Box, { children: [_jsx(Text, { wrap: "truncate", children: row.text.slice(0, Math.max(0, row.caretColumn)) }), _jsx(Text, { color: "cyan", children: "\u2588" }), _jsx(Text, { wrap: "truncate", children: row.text.slice(Math.max(0, row.caretColumn)) })] })) : (_jsx(Text, { wrap: "truncate", children: row.text.length === 0 ? ' ' : row.text }))] }, `prompt-${index}`)))] }));
395
- }
396
- // eslint-disable-next-line complexity -- session screens, overlays, and stdin wiring
397
- export function SessionApp(props) {
398
- const { onFinished, options } = props;
399
- const { exit } = useApp();
400
- const stdinApi = useStdin();
401
- const { stdout } = useStdout();
402
- const frameHeight = stdout.rows === undefined || stdout.rows < 10 ? undefined : stdout.rows;
403
- const chatRows = frameHeight ?? 80;
404
- const columns = Math.max(20, stdout.columns ?? 80);
405
- const promptWidth = Math.max(10, columns - 2);
406
- const skipWelcome = Boolean(options.dialogue);
407
- const confirmArmedRef = useRef(skipWelcome);
408
- const [screen, setScreen] = useState(skipWelcome ? 'chat' : 'welcome');
409
- const [overlay, setOverlay] = useState('none');
410
- const [agent, setAgent] = useState(options.agent);
411
- const [dialogue, setDialogue] = useState();
412
- const [committed, setCommitted] = useState([]);
413
- const [liveAgent, setLiveAgent] = useState();
414
- const [scrollOffset, setScrollOffset] = useState(0);
415
- const [input, setInput] = useState('');
416
- const [cursor, setCursor] = useState(0);
417
- const [welcomeIndex, setWelcomeIndex] = useState(0);
418
- const [sessions, setSessions] = useState([]);
419
- const [sessionIndex, setSessionIndex] = useState(0);
420
- const [sessionFilter, setSessionFilter] = useState('');
421
- const [busy, setBusy] = useState(false);
422
- const [status, setStatus] = useState('');
423
- const [slashIndex, setSlashIndex] = useState(0);
424
- const [slashDismissed, setSlashDismissed] = useState(false);
425
- const [elapsedTick, setElapsedTick] = useState(0);
426
- const turnsRef = useRef(0);
427
- const dialogueRef = useRef(undefined);
428
- const streamTextRef = useRef('');
429
- const rowCountRef = useRef(0);
430
- const abortRef = useRef(undefined);
431
- const turnStartedAtRef = useRef(undefined);
432
- const recallRef = useRef(createPromptRecall());
433
- const liveRef = useRef({
434
- agent,
435
- busy,
436
- cursor,
437
- dialogue,
438
- input,
439
- overlay,
440
- screen,
441
- sessionFilter,
442
- sessionIndex,
443
- sessions,
444
- slashDismissed,
445
- slashIndex,
446
- welcomeIndex,
447
- });
448
- liveRef.current = {
449
- agent,
450
- busy,
451
- cursor,
452
- dialogue,
453
- input,
454
- overlay,
455
- screen,
456
- sessionFilter,
457
- sessionIndex,
458
- sessions,
459
- slashDismissed,
460
- slashIndex,
461
- welcomeIndex,
462
- };
463
- const append = useCallback((line) => {
464
- setCommitted((current) => [...current, { id: uuidv4(), ...line }]);
465
- }, []);
466
- const finish = useCallback((dialogueId) => {
467
- onFinished?.({ dialogueId, turns: turnsRef.current });
468
- exit();
469
- }, [exit, onFinished]);
470
- const applyDialogue = useCallback((next, nextAgent, notice) => {
471
- setDialogue(next);
472
- dialogueRef.current = next;
473
- setAgent(nextAgent);
474
- setScreen('chat');
475
- setOverlay('none');
476
- const lines = options.loadHistory === true ? historyLines(next) : [];
477
- if (notice !== undefined)
478
- lines.push({ id: uuidv4(), kind: 'system', text: notice });
479
- setCommitted(lines);
480
- setLiveAgent(undefined);
481
- setScrollOffset(0);
482
- setSlashDismissed(false);
483
- setSlashIndex(0);
484
- setInput('');
485
- setCursor(0);
486
- recallRef.current = createPromptRecall();
487
- setStatus('');
488
- }, [options.loadHistory]);
489
- const startNew = useCallback(async () => {
490
- setBusy(true);
491
- setStatus('Starting session…');
492
- try {
493
- const bootstrap = await bootstrapAiDialogue({ ...options, agent, dialogue: undefined, kind: 'chat' });
494
- applyDialogue(bootstrap.dialogue, agent);
495
- }
496
- catch (error) {
497
- setStatus(errorMessage(error));
498
- }
499
- finally {
500
- setBusy(false);
501
- }
502
- }, [agent, applyDialogue, options]);
503
- const resumeDialogue = useCallback(async (id) => {
504
- setBusy(true);
505
- setStatus('Loading session…');
506
- try {
507
- const next = await AiGateway.getDialogue(id, options);
508
- const nextAgent = agentForAssistantId(dialogueAssistantId(next)) ?? agent;
509
- applyDialogue(next, nextAgent, `Resumed ${next.id}`);
510
- }
511
- catch (error) {
512
- setStatus(errorMessage(error));
513
- }
514
- finally {
515
- setBusy(false);
516
- }
517
- }, [agent, applyDialogue, options]);
518
- const openSessionOverlay = useCallback(async () => {
519
- setBusy(true);
520
- setStatus('Loading sessions…');
521
- try {
522
- const page = await listAiSessions({ ...options, pageSize: AI_SESSION_LIST_DEFAULT_PAGE_SIZE });
523
- setSessions(page.data);
524
- setSessionIndex(0);
525
- setSessionFilter('');
526
- setOverlay('session');
527
- setStatus(page.data.length === 0 ? 'No AI sessions found.' : '');
528
- }
529
- catch (error) {
530
- setStatus(errorMessage(error));
531
- }
532
- finally {
533
- setBusy(false);
534
- }
535
- }, [options]);
536
- const sendTurn = useCallback(async (line, current, currentAgent) => {
537
- setBusy(true);
538
- streamTextRef.current = '';
539
- append({ kind: 'you', text: line });
540
- setLiveAgent('');
541
- setScrollOffset(0);
542
- const requestId = uuidv4();
543
- const followups = [];
544
- let completed = false;
545
- let flushTimer;
546
- const controller = new AbortController();
547
- abortRef.current = controller;
548
- turnStartedAtRef.current = Date.now();
549
- const prompt = enrichAiPrompt(line, {
550
- component: options.component,
551
- sfOrg: options.sfOrg,
552
- userStory: options.userStory,
553
- workItem: options.workItem,
554
- });
555
- const flushAgentText = () => {
556
- flushTimer = undefined;
557
- setLiveAgent(streamTextRef.current);
558
- };
559
- try {
560
- await AiGateway.sendMessage(current.id, {
561
- assistantId: assistantIdForAgent(currentAgent),
562
- 'dev_context': null,
563
- integrations: null,
564
- prompt,
565
- 'request_id': requestId,
566
- 'system_prompt': null,
567
- }, { ...options, signal: controller.signal }, (chunk) => {
568
- if (chunk.type === 'error' && typeof chunk.content === 'string') {
569
- throw new Error(chunk.content);
570
- }
571
- if (chunk.type === 'done' || chunk.type === 'performance' || chunk.type === 'stream_complete') {
572
- completed = true;
573
- return;
574
- }
575
- applyTurnStreamChunk(chunk, {
576
- append,
577
- followups,
578
- scheduleFlush() {
579
- if (flushTimer === undefined)
580
- flushTimer = setTimeout(flushAgentText, TOKEN_FLUSH_MS);
581
- },
582
- setStatus,
583
- streamText: streamTextRef,
584
- });
585
- });
586
- if (flushTimer !== undefined)
587
- clearTimeout(flushTimer);
588
- flushAgentText();
589
- if (!completed)
590
- throw new Error('AI stream ended before stream_complete. Response may be incomplete.');
591
- append({ kind: 'agent', text: streamTextRef.current });
592
- if (followups.length > 0)
593
- append({ items: followups, kind: 'followups', text: followups.join('\n') });
594
- setLiveAgent(undefined);
595
- turnsRef.current += 1;
596
- setStatus('');
597
- }
598
- catch (error) {
599
- if (flushTimer !== undefined)
600
- clearTimeout(flushTimer);
601
- flushAgentText();
602
- if (streamTextRef.current.length > 0) {
603
- append({ kind: 'agent', text: streamTextRef.current });
604
- setLiveAgent(undefined);
605
- }
606
- if (isAiRequestAborted(error)) {
607
- append({ kind: 'system', text: 'Turn cancelled.' });
608
- }
609
- else {
610
- append({ kind: 'system', text: `AI turn failed: ${errorMessage(error)}` });
611
- }
612
- }
613
- finally {
614
- abortRef.current = undefined;
615
- turnStartedAtRef.current = undefined;
616
- setBusy(false);
617
- }
618
- }, [append, options]);
619
- const handleChatLine = useCallback(
620
- // eslint-disable-next-line complexity -- slash routing mirrors the readline chat session
621
- async (line) => {
622
- if (line === '/exit' || line === 'exit' || line === 'quit') {
623
- finish(dialogueRef.current?.id ?? '');
624
- return;
625
- }
626
- if (line === '/help') {
627
- setOverlay('help');
628
- return;
629
- }
630
- if (line === '/cancel' || line === 'cancel') {
631
- setOverlay('none');
632
- return;
633
- }
634
- if (line === '/session') {
635
- await openSessionOverlay();
636
- return;
637
- }
638
- if (line.startsWith('/session ')) {
639
- const page = sessions.length > 0 ? sessions : (await listAiSessions({ ...options, pageSize: AI_SESSION_LIST_DEFAULT_PAGE_SIZE })).data;
640
- setSessions(page);
641
- const picked = pickSessionFromList(line.slice('/session '.length).trim(), page);
642
- if (picked === undefined) {
643
- append({ kind: 'system', text: 'Unknown session. Use a list number or a dialogue UUID.' });
644
- return;
645
- }
646
- await resumeDialogue(picked.id);
647
- return;
648
- }
649
- if (line === '/clear') {
650
- setCommitted([{ id: uuidv4(), kind: 'system', text: 'Transcript cleared. Same session.' }]);
651
- setLiveAgent(undefined);
652
- setScrollOffset(0);
653
- return;
654
- }
655
- if (line === '/copy') {
656
- const fence = lastMarkdownCodeFence([
657
- ...committed,
658
- ...(liveAgent === undefined ? [] : [{ kind: 'agent', text: liveAgent }]),
659
- ]);
660
- if (fence === undefined) {
661
- append({ kind: 'system', text: 'No code block to copy.' });
662
- return;
663
- }
664
- await copyTextToClipboard(fence.code);
665
- const language = fence.language.trim().length > 0 ? fence.language : 'code';
666
- append({ kind: 'system', text: `Copied ${language} to clipboard` });
667
- return;
668
- }
669
- if (line === '/new') {
670
- setOverlay('confirm-new');
671
- return;
672
- }
673
- const shortcut = slashAgent(line);
674
- if (shortcut !== undefined) {
675
- setAgent(shortcut);
676
- append({ kind: 'system', text: `Using ${shortcut} agent on session ${dialogue?.id ?? ''}` });
677
- return;
678
- }
679
- if (line.startsWith('/agent ')) {
680
- const nextAgent = normalizeAgent(line.slice('/agent '.length).trim());
681
- if (nextAgent === undefined) {
682
- append({ kind: 'system', text: `Unknown agent. Use ${AI_CHAT_AGENTS.join(', ')}.` });
683
- return;
684
- }
685
- setAgent(nextAgent);
686
- append({ kind: 'system', text: `Using ${nextAgent} agent on session ${dialogue?.id ?? ''}` });
687
- return;
688
- }
689
- if (dialogue === undefined) {
690
- append({ kind: 'system', text: 'Session is not ready yet.' });
691
- return;
692
- }
693
- recallRef.current = recordPrompt(recallRef.current, line);
694
- await sendTurn(line, dialogue, agent);
695
- }, [agent, append, committed, dialogue, finish, liveAgent, openSessionOverlay, options, resumeDialogue, sendTurn, sessions, startNew]);
696
- const applyComposer = useCallback((next) => {
697
- liveRef.current.input = next.text;
698
- liveRef.current.cursor = next.cursor;
699
- setInput(next.text);
700
- setCursor(next.cursor);
701
- setSlashDismissed(false);
702
- }, []);
703
- const displayRows = useMemo(() => {
704
- const items = [...committed];
705
- if (liveAgent !== undefined)
706
- items.push({ id: 'live', kind: 'agent', text: liveAgent });
707
- return flattenTranscript(items, columns);
708
- }, [columns, committed, liveAgent]);
709
- const slashMatches = slashDismissed ? [] : filterSlashCommands(input);
710
- const slashWindow = slashPaletteWindow(slashMatches, slashIndex);
711
- const promptRows = composerRowCount(input, promptWidth, COMPOSER_MAX_ROWS);
712
- const extraRows = slashWindow.window.length + Math.max(0, promptRows - 1);
713
- const hasStatus = busy || status.length > 0;
714
- const viewport = chatViewportHeight(chatRows, hasStatus, extraRows);
715
- const visibleRows = visibleTranscriptRows(displayRows, viewport, scrollOffset);
716
- const composerRows = visibleComposerRows(input, cursor, promptWidth, COMPOSER_MAX_ROWS);
717
- const filteredSessions = filterAiSessions(sessions, sessionFilter);
718
- const elapsedSeconds = busy ? elapsedTick : 0;
719
- const followupCount = lastFollowupItems(committed).length;
720
- const hint = sessionChatHint({
721
- busy,
722
- followupCount,
723
- inputLength: input.length,
724
- scrollOffset,
725
- slashOpen: slashWindow.window.length > 0,
726
- });
727
- useEffect(() => {
728
- setSlashIndex((index) => clampSlashIndex(slashMatches.length, index));
729
- }, [input, slashDismissed, slashMatches.length]);
730
- useEffect(() => {
731
- if (!busy) {
732
- setElapsedTick(0);
733
- return;
734
- }
735
- const timer = setInterval(() => {
736
- setElapsedTick((tick) => tick + 1);
737
- }, 1000);
738
- return () => {
739
- clearInterval(timer);
740
- };
741
- }, [busy]);
742
- useEffect(() => {
743
- const previous = rowCountRef.current;
744
- const next = displayRows.length;
745
- const grown = next - previous;
746
- rowCountRef.current = next;
747
- setScrollOffset((offset) => clampScrollOffset(next, viewport, offset > 0 && grown > 0 ? offset + grown : offset));
748
- }, [displayRows.length, viewport]);
749
- useEffect(() => {
750
- if (skipWelcome)
751
- return;
752
- const timer = setTimeout(() => {
753
- confirmArmedRef.current = true;
754
- }, LAUNCH_ENTER_GUARD_MS);
755
- return () => {
756
- clearTimeout(timer);
757
- };
758
- }, [skipWelcome]);
759
- useEffect(() => {
760
- if (!skipWelcome)
761
- return;
762
- let cancelled = false;
763
- setBusy(true);
764
- bootstrapAiDialogue({ ...options, agent: options.agent, kind: 'chat' })
765
- .then((bootstrap) => {
766
- if (!cancelled)
767
- applyDialogue(bootstrap.dialogue, options.agent);
768
- })
769
- .catch((error) => {
770
- if (!cancelled)
771
- setStatus(errorMessage(error));
772
- })
773
- .finally(() => {
774
- if (!cancelled)
775
- setBusy(false);
776
- });
777
- return () => {
778
- cancelled = true;
779
- };
780
- }, [skipWelcome]);
781
- const dispatchRef = useRef(() => { });
782
- dispatchRef.current = (key) => {
783
- handleSessionAppKey(key, liveRef.current, {
784
- abortTurn() {
785
- abortRef.current?.abort();
786
- },
787
- applyComposer,
788
- armWelcome() {
789
- confirmArmedRef.current = true;
790
- },
791
- displayRowsLength: displayRows.length,
792
- followupItems: lastFollowupItems(committed),
793
- handleChatLine,
794
- openSessionOverlay,
795
- recall: recallRef.current,
796
- resumeDialogue,
797
- setOverlay,
798
- setRecall(next) {
799
- recallRef.current = next;
800
- },
801
- setScrollOffset,
802
- setSessionFilter,
803
- setSessionIndex,
804
- setSlashDismissed,
805
- setSlashIndex,
806
- setWelcomeIndex,
807
- startNew,
808
- viewport,
809
- welcomeArmed: confirmArmedRef.current,
810
- });
811
- };
812
- const { setRawMode } = stdinApi;
813
- const stdinEvents = stdinApi.internal_eventEmitter;
814
- useLayoutEffect(() => {
815
- setRawMode(true);
816
- const assembler = new SessionInputAssembler();
817
- let wait;
818
- const onChunk = (chunk) => {
819
- if (wait !== undefined) {
820
- clearTimeout(wait);
821
- wait = undefined;
822
- }
823
- const { keys, pending } = assembler.push(String(chunk));
824
- for (const key of keys)
825
- dispatchRef.current(key);
826
- if (!pending)
827
- return;
828
- wait = setTimeout(() => {
829
- wait = undefined;
830
- for (const key of assembler.flush())
831
- dispatchRef.current(key);
832
- }, 30);
833
- };
834
- stdinEvents.on('input', onChunk);
835
- return () => {
836
- if (wait !== undefined)
837
- clearTimeout(wait);
838
- stdinEvents.off('input', onChunk);
839
- setRawMode(false);
840
- };
841
- }, [setRawMode, stdinEvents]);
842
- return (_jsx(_Fragment, { children: _jsxs(Box, { flexDirection: "column", height: screen === 'welcome' ? frameHeight : undefined, children: [screen === 'welcome' && overlay !== 'session' ? (_jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(WelcomeScreen, { agent: agent, selectedIndex: welcomeIndex }) })) : null, screen === 'chat' ? (_jsx(ChatLivePane, { agent: agent, busy: busy, composerRows: composerRows, dialogueId: dialogue?.id ?? '…', elapsedSeconds: elapsedSeconds, height: chatRows, hint: hint, rows: visibleRows, selectedSlash: slashWindow.selectedInWindow, status: status, suggestions: slashWindow.window, viewport: viewport })) : null, overlay === 'session' ? (_jsxs(Box, { borderStyle: "single", flexDirection: "column", marginTop: 1, paddingX: 1, children: [_jsx(Text, { bold: true, children: "Sessions" }), _jsxs(Text, { dimColor: true, wrap: "truncate", children: ["Filter: ", sessionFilter.length > 0 ? sessionFilter : '(type to filter)'] }), filteredSessions.length === 0 ? _jsx(Text, { dimColor: true, children: "No AI sessions found." }) : null, filteredSessions.map((session, index) => (_jsxs(Text, { color: index === sessionIndex ? 'green' : undefined, wrap: "truncate", children: [index === sessionIndex ? '●' : ' ', " ", formatSessionOverlayLine(session)] }, session.id))), _jsx(Text, { dimColor: true, children: "Enter to open \u00B7 type to filter \u00B7 Esc or /cancel to dismiss" })] })) : null, overlay === 'help' ? (_jsxs(Box, { borderStyle: "single", flexDirection: "column", marginTop: 1, paddingX: 1, children: [_jsx(Text, { bold: true, children: "Help" }), sessionHelpLines().map((line) => (_jsx(Text, { children: line }, line))), _jsx(Text, { dimColor: true, children: "Esc or Enter to close" })] })) : null, overlay === 'confirm-new' ? (_jsxs(Box, { borderStyle: "single", flexDirection: "column", marginTop: 1, paddingX: 1, children: [_jsx(Text, { bold: true, children: "Start a new session?" }), _jsx(Text, { children: "This creates a new Context Hub dialogue. The current one stays in the list." }), _jsx(Text, { dimColor: true, children: "Enter confirm \u00B7 Esc cancel" })] })) : null, screen !== 'chat' && status.length > 0 ? _jsx(Text, { color: "yellow", children: status }) : null] }) }));
843
- }
844
- export async function runAiSessionTui(options) {
845
- discardPendingStdinBytes(process.stdin);
846
- let result = { dialogueId: '', turns: 0 };
847
- const restoreStdout = installStdoutImageBridge(process.stdout, detectMermaidImageProtocol());
848
- try {
849
- const instance = render(_jsx(SessionApp, { onFinished: (value) => {
850
- result = value;
851
- }, options: options }), { exitOnCtrlC: true });
852
- await instance.waitUntilExit();
853
- return result;
854
- }
855
- finally {
856
- restoreStdout();
857
- }
858
- }
859
- //# sourceMappingURL=ai-session-tui.js.map