deepclause-sdk 0.0.58 → 0.0.60

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 (90) hide show
  1. package/README.md +71 -19
  2. package/dist/cli/compile.d.ts +9 -0
  3. package/dist/cli/compile.d.ts.map +1 -1
  4. package/dist/cli/compile.js +106 -138
  5. package/dist/cli/compile.js.map +1 -1
  6. package/dist/cli/config.d.ts +73 -23
  7. package/dist/cli/config.d.ts.map +1 -1
  8. package/dist/cli/config.js +151 -58
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +64 -14
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/interactive.d.ts +2 -0
  13. package/dist/cli/interactive.d.ts.map +1 -0
  14. package/dist/cli/interactive.js +20 -0
  15. package/dist/cli/interactive.js.map +1 -0
  16. package/dist/cli/prompt.d.ts +1 -19
  17. package/dist/cli/prompt.d.ts.map +1 -1
  18. package/dist/cli/prompt.js +1 -869
  19. package/dist/cli/prompt.js.map +1 -1
  20. package/dist/cli/run.d.ts +6 -1
  21. package/dist/cli/run.d.ts.map +1 -1
  22. package/dist/cli/run.js +65 -279
  23. package/dist/cli/run.js.map +1 -1
  24. package/dist/cli/search.d.ts +1 -0
  25. package/dist/cli/search.d.ts.map +1 -1
  26. package/dist/cli/search.js +10 -4
  27. package/dist/cli/search.js.map +1 -1
  28. package/dist/cli/tools.d.ts +2 -2
  29. package/dist/cli/tools.d.ts.map +1 -1
  30. package/dist/cli/tools.js +30 -5
  31. package/dist/cli/tools.js.map +1 -1
  32. package/dist/cli/tui.d.ts +58 -0
  33. package/dist/cli/tui.d.ts.map +1 -0
  34. package/dist/cli/tui.js +1742 -0
  35. package/dist/cli/tui.js.map +1 -0
  36. package/dist/compiler_prompt.d.ts +1 -1
  37. package/dist/compiler_prompt.d.ts.map +1 -1
  38. package/dist/compiler_prompt.js +8 -10
  39. package/dist/compiler_prompt.js.map +1 -1
  40. package/dist/prolog-src/deepclause_mi.pl +25 -4
  41. package/dist/prolog-src/deepclause_strings.pl +22 -2
  42. package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +46 -0
  43. package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +654 -0
  44. package/dist/system/assets/index.d.ts +9 -0
  45. package/dist/system/assets/index.d.ts.map +1 -0
  46. package/dist/system/assets/index.js +47 -0
  47. package/dist/system/assets/index.js.map +1 -0
  48. package/dist/system/assets/skills/conductor.dml +70 -0
  49. package/dist/system/assets/skills/skill-creator.dml +182 -0
  50. package/dist/system/config/model-slots.d.ts +34 -0
  51. package/dist/system/config/model-slots.d.ts.map +1 -0
  52. package/dist/system/config/model-slots.js +121 -0
  53. package/dist/system/config/model-slots.js.map +1 -0
  54. package/dist/system/runtime/agentvm-manager.d.ts +18 -0
  55. package/dist/system/runtime/agentvm-manager.d.ts.map +1 -0
  56. package/dist/system/runtime/agentvm-manager.js +98 -0
  57. package/dist/system/runtime/agentvm-manager.js.map +1 -0
  58. package/dist/system/runtime/conductor.d.ts +54 -0
  59. package/dist/system/runtime/conductor.d.ts.map +1 -0
  60. package/dist/system/runtime/conductor.js +457 -0
  61. package/dist/system/runtime/conductor.js.map +1 -0
  62. package/dist/system/runtime/console-capture.d.ts +9 -0
  63. package/dist/system/runtime/console-capture.d.ts.map +1 -0
  64. package/dist/system/runtime/console-capture.js +67 -0
  65. package/dist/system/runtime/console-capture.js.map +1 -0
  66. package/dist/system/runtime/dml-executor.d.ts +41 -0
  67. package/dist/system/runtime/dml-executor.d.ts.map +1 -0
  68. package/dist/system/runtime/dml-executor.js +137 -0
  69. package/dist/system/runtime/dml-executor.js.map +1 -0
  70. package/dist/system/runtime/runtime-tools.d.ts +16 -0
  71. package/dist/system/runtime/runtime-tools.d.ts.map +1 -0
  72. package/dist/system/runtime/runtime-tools.js +146 -0
  73. package/dist/system/runtime/runtime-tools.js.map +1 -0
  74. package/dist/system/runtime/shell-manager.d.ts +20 -0
  75. package/dist/system/runtime/shell-manager.d.ts.map +1 -0
  76. package/dist/system/runtime/shell-manager.js +133 -0
  77. package/dist/system/runtime/shell-manager.js.map +1 -0
  78. package/dist/system/runtime/skill-creator.d.ts +33 -0
  79. package/dist/system/runtime/skill-creator.d.ts.map +1 -0
  80. package/dist/system/runtime/skill-creator.js +583 -0
  81. package/dist/system/runtime/skill-creator.js.map +1 -0
  82. package/dist/system/runtime/token-usage.d.ts +16 -0
  83. package/dist/system/runtime/token-usage.d.ts.map +1 -0
  84. package/dist/system/runtime/token-usage.js +45 -0
  85. package/dist/system/runtime/token-usage.js.map +1 -0
  86. package/dist/tools.js +1 -1
  87. package/dist/tools.js.map +1 -1
  88. package/package.json +4 -3
  89. package/src/prolog-src/deepclause_mi.pl +25 -4
  90. package/src/prolog-src/deepclause_strings.pl +22 -2
@@ -0,0 +1,1742 @@
1
+ import { emitKeypressEvents } from 'readline';
2
+ import { createInterface } from 'readline/promises';
3
+ import { stdin as input, stdout as output } from 'process';
4
+ import { listCommands } from './commands.js';
5
+ import { run } from './run.js';
6
+ import { createConductorSession, getConductorSessionDetail, listConductorSessions, runConductorTurn, } from '../system/runtime/conductor.js';
7
+ const IGNORED_LIVE_LOG_TOOLS = new Set(['set_result', 'update_memory']);
8
+ const BUILTIN_SLASH_COMMANDS = ['new', 'sessions', 'help', 'cancel', 'exit', 'quit'];
9
+ const MAX_ACTIVITY_LINES = 400;
10
+ const SPINNER_FRAMES = ['|', '/', '-', '\\'];
11
+ const IDLE_RENDER_INTERVAL_MS = 16;
12
+ const BUSY_RENDER_INTERVAL_MS = 40;
13
+ const TYPING_RENDER_INTERVAL_MS = 90;
14
+ const ANSI = {
15
+ reset: '\u001b[0m',
16
+ bold: '\u001b[1m',
17
+ dim: '\u001b[2m',
18
+ black: '\u001b[30m',
19
+ red: '\u001b[31m',
20
+ blue: '\u001b[34m',
21
+ white: '\u001b[37m',
22
+ green: '\u001b[32m',
23
+ yellow: '\u001b[33m',
24
+ cyan: '\u001b[36m',
25
+ brightYellow: '\u001b[93m',
26
+ brightCyan: '\u001b[96m',
27
+ brightWhite: '\u001b[97m',
28
+ bgBlue: '\u001b[48;2;0;0;95m',
29
+ bgCyan: '\u001b[48;2;0;96;128m',
30
+ bgWhite: '\u001b[47m',
31
+ };
32
+ export class LiveExecutionPrinter {
33
+ write;
34
+ writeLine;
35
+ activeStreamKey = null;
36
+ streamOpen = false;
37
+ constructor(write = (text) => process.stdout.write(text), writeLine = (text) => console.log(text)) {
38
+ this.write = write;
39
+ this.writeLine = writeLine;
40
+ }
41
+ handle(logEvent) {
42
+ const { event } = logEvent;
43
+ if (event.type === 'usage' || event.type === 'finished' || event.type === 'input_required') {
44
+ return;
45
+ }
46
+ if (event.type === 'stream') {
47
+ this.handleStream(logEvent);
48
+ return;
49
+ }
50
+ this.flushStream();
51
+ switch (event.type) {
52
+ case 'tool_call':
53
+ if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
54
+ return;
55
+ }
56
+ this.writeLine(`${formatEventPrefix(logEvent)}tool ${event.toolName}(${formatToolArgs(event.toolArgs)})`);
57
+ break;
58
+ case 'output':
59
+ if (event.content) {
60
+ this.writeLine(`${formatEventPrefix(logEvent)}output ${event.content}`);
61
+ }
62
+ break;
63
+ case 'answer':
64
+ if (event.content) {
65
+ this.writeLine(`${formatEventPrefix(logEvent)}answer ${event.content}`);
66
+ }
67
+ break;
68
+ case 'error':
69
+ if (event.content) {
70
+ this.writeLine(`${formatEventPrefix(logEvent)}error ${event.content}`);
71
+ }
72
+ break;
73
+ case 'log':
74
+ if (event.content) {
75
+ this.writeLine(`${formatEventPrefix(logEvent)}log ${event.content}`);
76
+ }
77
+ break;
78
+ }
79
+ }
80
+ finish() {
81
+ this.flushStream();
82
+ }
83
+ handleStream(logEvent) {
84
+ const streamKey = logEvent.scope === 'child' ? `child:${logEvent.childSlug ?? '?'}` : 'main';
85
+ if (this.activeStreamKey !== streamKey) {
86
+ this.flushStream();
87
+ this.write(`${streamLabel(logEvent)} `);
88
+ this.activeStreamKey = streamKey;
89
+ this.streamOpen = true;
90
+ }
91
+ if (logEvent.event.content) {
92
+ this.write(logEvent.event.content);
93
+ }
94
+ if (logEvent.event.done) {
95
+ this.flushStream();
96
+ }
97
+ }
98
+ flushStream() {
99
+ if (!this.streamOpen) {
100
+ this.activeStreamKey = null;
101
+ return;
102
+ }
103
+ this.write('\n');
104
+ this.streamOpen = false;
105
+ this.activeStreamKey = null;
106
+ }
107
+ }
108
+ class ActivityBuffer {
109
+ lines = [];
110
+ activeStreamKey = null;
111
+ activeStreamLine = '';
112
+ handle(logEvent) {
113
+ const { event } = logEvent;
114
+ if (event.type === 'usage' || event.type === 'finished' || event.type === 'input_required') {
115
+ return;
116
+ }
117
+ if (event.type === 'stream') {
118
+ this.handleStream(logEvent);
119
+ return;
120
+ }
121
+ this.flushStream();
122
+ switch (event.type) {
123
+ case 'tool_call':
124
+ if (!event.toolName || IGNORED_LIVE_LOG_TOOLS.has(event.toolName)) {
125
+ return;
126
+ }
127
+ this.pushLine(`${formatEventPrefix(logEvent)}tool ${event.toolName}(${formatToolArgs(event.toolArgs)})`);
128
+ break;
129
+ case 'output':
130
+ if (event.content) {
131
+ this.pushLine(`${formatEventPrefix(logEvent)}output ${event.content}`);
132
+ }
133
+ break;
134
+ case 'answer':
135
+ if (event.content) {
136
+ this.pushLine(`${formatEventPrefix(logEvent)}answer ${event.content}`);
137
+ }
138
+ break;
139
+ case 'error':
140
+ if (event.content) {
141
+ this.pushLine(`${formatEventPrefix(logEvent)}error ${event.content}`);
142
+ }
143
+ break;
144
+ case 'log':
145
+ if (event.content) {
146
+ this.pushLine(`${formatEventPrefix(logEvent)}log ${event.content}`);
147
+ }
148
+ break;
149
+ }
150
+ }
151
+ pushLine(line) {
152
+ this.flushStream();
153
+ if (!line) {
154
+ return;
155
+ }
156
+ this.lines.push(line);
157
+ this.trim();
158
+ }
159
+ finish() {
160
+ this.flushStream();
161
+ }
162
+ snapshot() {
163
+ return this.activeStreamLine
164
+ ? [...this.lines, this.activeStreamLine]
165
+ : [...this.lines];
166
+ }
167
+ handleStream(logEvent) {
168
+ const streamKey = logEvent.scope === 'child' ? `child:${logEvent.childSlug ?? '?'}` : 'main';
169
+ if (this.activeStreamKey !== streamKey) {
170
+ this.flushStream();
171
+ this.activeStreamKey = streamKey;
172
+ this.activeStreamLine = `${streamLabel(logEvent)} `;
173
+ }
174
+ if (logEvent.event.content) {
175
+ this.activeStreamLine += logEvent.event.content;
176
+ }
177
+ if (logEvent.event.done) {
178
+ this.flushStream();
179
+ }
180
+ }
181
+ flushStream() {
182
+ if (!this.activeStreamLine) {
183
+ this.activeStreamKey = null;
184
+ return;
185
+ }
186
+ this.lines.push(this.activeStreamLine);
187
+ this.activeStreamLine = '';
188
+ this.activeStreamKey = null;
189
+ this.trim();
190
+ }
191
+ trim() {
192
+ if (this.lines.length > MAX_ACTIVITY_LINES) {
193
+ this.lines.splice(0, this.lines.length - MAX_ACTIVITY_LINES);
194
+ }
195
+ }
196
+ }
197
+ class FullscreenTui {
198
+ workspaceRoot;
199
+ options;
200
+ sessions = [];
201
+ selectedSessionId = '';
202
+ sessionDetail = null;
203
+ commands = [];
204
+ activityBySessionId = new Map();
205
+ ephemeralMessagesBySessionId = new Map();
206
+ focusedPane = 'messages';
207
+ paneScroll = {
208
+ sessions: 0,
209
+ messages: 0,
210
+ process: 0,
211
+ context: 0,
212
+ };
213
+ currentPreview = null;
214
+ currentAbortController = null;
215
+ inputValue = '';
216
+ cursor = 0;
217
+ busy = false;
218
+ exitRequested = false;
219
+ statusLine = 'Loading...';
220
+ pendingQuestion = null;
221
+ renderTimer = null;
222
+ animationTimer = null;
223
+ closeResolver = null;
224
+ lastRenderAt = 0;
225
+ previousFrameLines = null;
226
+ previousFrameSize = null;
227
+ constructor(workspaceRoot, options = {}) {
228
+ this.workspaceRoot = workspaceRoot;
229
+ this.options = options;
230
+ }
231
+ async start() {
232
+ try {
233
+ await this.refreshCommands();
234
+ await this.refreshSessions({ createIfMissing: true });
235
+ this.statusLine = 'Ready. Left/right changes pane. Up/down and PgUp/PgDn scroll the focused pane when input is empty.';
236
+ emitKeypressEvents(input);
237
+ if (input.isTTY) {
238
+ input.setRawMode(true);
239
+ }
240
+ input.on('keypress', this.onKeypress);
241
+ output.write('\u001b[?1049h\u001b[2J\u001b[H');
242
+ this.requestRender();
243
+ await new Promise((resolve) => {
244
+ this.closeResolver = resolve;
245
+ });
246
+ }
247
+ finally {
248
+ if (this.renderTimer) {
249
+ clearTimeout(this.renderTimer);
250
+ this.renderTimer = null;
251
+ }
252
+ if (this.animationTimer) {
253
+ clearInterval(this.animationTimer);
254
+ this.animationTimer = null;
255
+ }
256
+ input.off('keypress', this.onKeypress);
257
+ if (input.isTTY) {
258
+ input.setRawMode(false);
259
+ }
260
+ this.previousFrameLines = null;
261
+ this.previousFrameSize = null;
262
+ output.write('\u001b[?25h\u001b[?1049l');
263
+ }
264
+ }
265
+ onKeypress = (text, key) => {
266
+ void this.handleKeypress(text, key);
267
+ };
268
+ async handleKeypress(text, key) {
269
+ if (key.ctrl && key.name === 'w') {
270
+ this.cyclePaneFocus(1);
271
+ return;
272
+ }
273
+ if (key.ctrl && key.name === 'c') {
274
+ if (this.pendingQuestion) {
275
+ const pending = this.pendingQuestion;
276
+ this.pendingQuestion = null;
277
+ pending.resolve('');
278
+ }
279
+ if (this.busy) {
280
+ this.exitRequested = true;
281
+ this.cancelCurrentExecution('Cancelling current execution, then quitting...');
282
+ return;
283
+ }
284
+ this.close();
285
+ return;
286
+ }
287
+ switch (key.name) {
288
+ case 'return':
289
+ await this.submitInput();
290
+ return;
291
+ case 'backspace':
292
+ this.deleteBackward();
293
+ break;
294
+ case 'delete':
295
+ this.deleteForward();
296
+ break;
297
+ case 'left':
298
+ if (!this.inputValue) {
299
+ this.cyclePaneFocus(-1);
300
+ return;
301
+ }
302
+ if (this.cursor > 0) {
303
+ this.cursor -= 1;
304
+ }
305
+ break;
306
+ case 'right':
307
+ if (!this.inputValue) {
308
+ this.cyclePaneFocus(1);
309
+ return;
310
+ }
311
+ if (this.cursor < this.inputValue.length) {
312
+ this.cursor += 1;
313
+ }
314
+ break;
315
+ case 'home':
316
+ if (!this.inputValue) {
317
+ this.jumpFocusedPane('top');
318
+ return;
319
+ }
320
+ this.cursor = 0;
321
+ break;
322
+ case 'end':
323
+ if (!this.inputValue) {
324
+ this.jumpFocusedPane('bottom');
325
+ return;
326
+ }
327
+ this.cursor = this.inputValue.length;
328
+ break;
329
+ case 'up':
330
+ if (!this.inputValue) {
331
+ await this.handleUpDown(-1);
332
+ return;
333
+ }
334
+ break;
335
+ case 'down':
336
+ if (!this.inputValue) {
337
+ await this.handleUpDown(1);
338
+ return;
339
+ }
340
+ break;
341
+ case 'pageup':
342
+ if (!this.inputValue) {
343
+ this.scrollFocusedPane('up', this.pageScrollAmount());
344
+ return;
345
+ }
346
+ break;
347
+ case 'pagedown':
348
+ if (!this.inputValue) {
349
+ this.scrollFocusedPane('down', this.pageScrollAmount());
350
+ return;
351
+ }
352
+ break;
353
+ case 'tab':
354
+ await this.applyCompletion();
355
+ return;
356
+ case 'escape':
357
+ this.inputValue = '';
358
+ this.cursor = 0;
359
+ break;
360
+ default:
361
+ if (text && !key.ctrl && !key.meta) {
362
+ this.insertText(text);
363
+ }
364
+ break;
365
+ }
366
+ this.requestRender();
367
+ }
368
+ async submitInput() {
369
+ const rawInput = this.inputValue;
370
+ const trimmedInput = rawInput.trim();
371
+ this.inputValue = '';
372
+ this.cursor = 0;
373
+ if (this.pendingQuestion) {
374
+ const pending = this.pendingQuestion;
375
+ this.pendingQuestion = null;
376
+ pending.resolve(trimmedInput);
377
+ this.statusLine = 'Continuing...';
378
+ this.requestRender();
379
+ return;
380
+ }
381
+ if (!trimmedInput) {
382
+ this.requestRender();
383
+ return;
384
+ }
385
+ const parsed = parseSlashInput(trimmedInput);
386
+ if (this.busy && !this.pendingQuestion && !canSubmitParsedInputWhileBusy(parsed)) {
387
+ this.statusLine = 'Execution is still running. Use /cancel or Ctrl+C to stop it.';
388
+ this.requestRender();
389
+ return;
390
+ }
391
+ if (parsed.kind === 'builtin') {
392
+ await this.executeBuiltin(parsed);
393
+ return;
394
+ }
395
+ if (parsed.kind === 'skill') {
396
+ await this.executeSkill(parsed.name, parsed.args);
397
+ return;
398
+ }
399
+ await this.executeTask(parsed.prompt);
400
+ }
401
+ async executeBuiltin(parsed) {
402
+ switch (parsed.name) {
403
+ case 'cancel':
404
+ this.cancelCurrentExecution('Cancellation requested.');
405
+ return;
406
+ case 'exit':
407
+ case 'quit':
408
+ this.close();
409
+ return;
410
+ case 'help': {
411
+ const activity = this.currentProcessActivity();
412
+ for (const line of buildHelpLines()) {
413
+ activity.pushLine(line);
414
+ }
415
+ this.statusLine = 'Help added to the process pane.';
416
+ this.requestRender();
417
+ return;
418
+ }
419
+ case 'sessions':
420
+ await this.refreshSessions({ createIfMissing: true, selectedSessionId: this.selectedSessionId });
421
+ await this.refreshCommands();
422
+ this.statusLine = `Loaded ${this.sessions.length} session${this.sessions.length === 1 ? '' : 's'}.`;
423
+ this.requestRender();
424
+ return;
425
+ case 'new': {
426
+ const created = await createConductorSession(this.workspaceRoot, parsed.rawArgs || undefined);
427
+ await this.refreshSessions({ createIfMissing: true, selectedSessionId: created.id });
428
+ this.statusLine = `Created session ${created.title}.`;
429
+ this.requestRender();
430
+ return;
431
+ }
432
+ }
433
+ }
434
+ async executeTask(promptText) {
435
+ const sessionId = this.selectedSessionId;
436
+ const activity = this.activityFor(sessionId);
437
+ this.focusedPane = 'messages';
438
+ this.paneScroll.messages = 0;
439
+ this.paneScroll.process = 0;
440
+ this.beginExecutionPreview(sessionId, 'task', { role: 'user', content: promptText });
441
+ activity.pushLine(`task ${promptText}`);
442
+ this.busy = true;
443
+ this.currentAbortController = new AbortController();
444
+ this.startAnimation();
445
+ this.statusLine = 'Running conductor...';
446
+ this.requestRender();
447
+ try {
448
+ const result = await runConductorTurn(promptText, {
449
+ workspaceRoot: this.workspaceRoot,
450
+ sessionId,
451
+ stream: true,
452
+ headless: true,
453
+ sandbox: this.options.sandbox,
454
+ signal: this.currentAbortController.signal,
455
+ onUserInput: (question) => this.requestClarification(question),
456
+ onEvent: (event) => {
457
+ activity.handle(event);
458
+ this.updatePreviewFromEvent(event);
459
+ this.requestRender();
460
+ },
461
+ });
462
+ activity.finish();
463
+ await this.refreshSessions({ createIfMissing: true, selectedSessionId: result.sessionId });
464
+ await this.refreshCommands();
465
+ this.finishExecutionPreview({ persist: false });
466
+ this.statusLine = result.error ? 'Task failed.' : 'Task finished.';
467
+ }
468
+ catch (error) {
469
+ activity.finish();
470
+ const message = error.message;
471
+ activity.pushLine(`error ${message}`);
472
+ this.finishExecutionPreview({ persist: true, error: message });
473
+ this.statusLine = 'Task failed.';
474
+ }
475
+ finally {
476
+ this.busy = false;
477
+ this.currentAbortController = null;
478
+ this.stopAnimation();
479
+ if (this.exitRequested) {
480
+ this.close();
481
+ }
482
+ else {
483
+ this.requestRender();
484
+ }
485
+ }
486
+ }
487
+ async executeSkill(skillName, args) {
488
+ const sessionId = this.selectedSessionId || 'global';
489
+ const activity = this.activityFor(sessionId);
490
+ const commandText = `/${skillName}${args.length > 0 ? ` ${args.join(' ')}` : ''}`;
491
+ this.focusedPane = 'messages';
492
+ this.paneScroll.messages = 0;
493
+ this.paneScroll.process = 0;
494
+ this.beginExecutionPreview(sessionId, 'skill', { role: 'system', content: commandText });
495
+ activity.pushLine(`skill /${skillName}${args.length > 0 ? ` ${args.join(' ')}` : ''}`);
496
+ this.busy = true;
497
+ this.currentAbortController = new AbortController();
498
+ this.startAnimation();
499
+ this.statusLine = `Running /${skillName}...`;
500
+ this.requestRender();
501
+ try {
502
+ const result = await runSkillCommand(this.workspaceRoot, skillName, args, {
503
+ sandbox: this.options.sandbox,
504
+ signal: this.currentAbortController.signal,
505
+ onUserInput: (question) => this.requestClarification(`[${skillName}] ${question}`),
506
+ onEvent: (event) => {
507
+ activity.handle(event);
508
+ this.updatePreviewFromEvent(event);
509
+ this.requestRender();
510
+ },
511
+ });
512
+ activity.finish();
513
+ await this.refreshCommands();
514
+ this.finishExecutionPreview({
515
+ persist: true,
516
+ finalText: result.answer || summarizeRunResult(result),
517
+ error: result.error,
518
+ });
519
+ this.statusLine = result.error ? `/${skillName} failed.` : `/${skillName} finished.`;
520
+ }
521
+ catch (error) {
522
+ activity.finish();
523
+ const message = error.message;
524
+ activity.pushLine(`[${skillName}] error ${message}`);
525
+ this.finishExecutionPreview({ persist: true, error: message });
526
+ this.statusLine = `/${skillName} failed.`;
527
+ }
528
+ finally {
529
+ this.busy = false;
530
+ this.currentAbortController = null;
531
+ this.stopAnimation();
532
+ if (this.exitRequested) {
533
+ this.close();
534
+ }
535
+ else {
536
+ this.requestRender();
537
+ }
538
+ }
539
+ }
540
+ async requestClarification(promptText) {
541
+ this.currentProcessActivity().pushLine(`clarify ${condenseWhitespace(promptText)}`);
542
+ this.inputValue = '';
543
+ this.cursor = 0;
544
+ this.statusLine = 'Awaiting clarification...';
545
+ this.requestRender();
546
+ return new Promise((resolve) => {
547
+ this.pendingQuestion = { prompt: promptText, resolve };
548
+ this.requestRender();
549
+ });
550
+ }
551
+ async applyCompletion() {
552
+ if (!this.inputValue.startsWith('/')) {
553
+ return;
554
+ }
555
+ await this.refreshCommands();
556
+ const completion = completeSlashCommand(this.inputValue, [
557
+ ...BUILTIN_SLASH_COMMANDS,
558
+ ...this.commands.map((command) => command.name),
559
+ ]);
560
+ if (completion.applied) {
561
+ this.inputValue = completion.value;
562
+ this.cursor = this.inputValue.length;
563
+ }
564
+ if (completion.matches.length === 0) {
565
+ this.statusLine = 'No matching commands or skills.';
566
+ }
567
+ else if (completion.matches.length === 1) {
568
+ this.statusLine = `Completed /${completion.matches[0]}`;
569
+ }
570
+ else {
571
+ const preview = completion.matches.slice(0, 6).join(', ');
572
+ this.statusLine = `Matches: ${preview}${completion.matches.length > 6 ? ', ...' : ''}`;
573
+ }
574
+ this.requestRender();
575
+ }
576
+ async handleUpDown(offset) {
577
+ if (this.focusedPane === 'sessions') {
578
+ await this.moveSession(offset);
579
+ return;
580
+ }
581
+ this.scrollFocusedPane(offset < 0 ? 'up' : 'down', 1);
582
+ }
583
+ cyclePaneFocus(direction) {
584
+ const order = ['sessions', 'messages', 'process', 'context'];
585
+ const currentIndex = order.indexOf(this.focusedPane);
586
+ const nextIndex = (currentIndex + direction + order.length) % order.length;
587
+ this.focusedPane = order[nextIndex];
588
+ this.statusLine = `Focus: ${paneDisplayName(this.focusedPane)}. Left/right switches panes; PgUp/PgDn scrolls.`;
589
+ this.requestRender();
590
+ }
591
+ beginExecutionPreview(sessionId, kind, lead) {
592
+ this.currentPreview = {
593
+ sessionId,
594
+ kind,
595
+ entries: [lead, { role: 'assistant', content: '', pending: true }],
596
+ };
597
+ }
598
+ updatePreviewFromEvent(logEvent) {
599
+ if (!this.currentPreview) {
600
+ return;
601
+ }
602
+ if (this.currentPreview.kind === 'task' && logEvent.scope !== 'main') {
603
+ return;
604
+ }
605
+ const message = this.ensurePreviewAssistantMessage();
606
+ switch (logEvent.event.type) {
607
+ case 'stream':
608
+ if (logEvent.event.content) {
609
+ message.content += logEvent.event.content;
610
+ }
611
+ break;
612
+ case 'answer':
613
+ if (logEvent.event.content) {
614
+ message.content = logEvent.event.content;
615
+ }
616
+ message.pending = false;
617
+ message.error = false;
618
+ break;
619
+ case 'error':
620
+ message.content = logEvent.event.content ?? (message.content || 'Execution failed');
621
+ message.pending = false;
622
+ message.error = true;
623
+ break;
624
+ default:
625
+ break;
626
+ }
627
+ }
628
+ ensurePreviewAssistantMessage() {
629
+ if (!this.currentPreview) {
630
+ return { role: 'assistant', content: '', pending: false };
631
+ }
632
+ const last = this.currentPreview.entries[this.currentPreview.entries.length - 1];
633
+ if (last && last.role === 'assistant') {
634
+ return last;
635
+ }
636
+ const created = { role: 'assistant', content: '', pending: true };
637
+ this.currentPreview.entries.push(created);
638
+ return created;
639
+ }
640
+ finishExecutionPreview(options) {
641
+ if (!this.currentPreview) {
642
+ return;
643
+ }
644
+ const assistant = this.ensurePreviewAssistantMessage();
645
+ if (typeof options.finalText === 'string' && options.finalText.trim()) {
646
+ assistant.content = options.finalText;
647
+ }
648
+ if (typeof options.error === 'string' && options.error.trim()) {
649
+ assistant.content = options.error;
650
+ assistant.error = true;
651
+ }
652
+ assistant.pending = false;
653
+ if (options.persist) {
654
+ const existing = this.ephemeralMessagesBySessionId.get(this.currentPreview.sessionId) ?? [];
655
+ existing.push(...this.currentPreview.entries.map((entry) => ({ ...entry, pending: false })));
656
+ this.ephemeralMessagesBySessionId.set(this.currentPreview.sessionId, existing);
657
+ }
658
+ this.currentPreview = null;
659
+ }
660
+ cancelCurrentExecution(message) {
661
+ if (!this.busy || !this.currentAbortController) {
662
+ this.statusLine = 'No execution is currently running.';
663
+ this.requestRender();
664
+ return;
665
+ }
666
+ if (this.currentAbortController.signal.aborted) {
667
+ this.statusLine = 'Cancellation already requested.';
668
+ this.requestRender();
669
+ return;
670
+ }
671
+ this.currentAbortController.abort();
672
+ this.statusLine = message;
673
+ this.requestRender();
674
+ }
675
+ startAnimation() {
676
+ if (this.animationTimer) {
677
+ return;
678
+ }
679
+ this.animationTimer = setInterval(() => {
680
+ if ((this.busy || this.currentPreview?.entries.some((entry) => entry.pending)) && !this.isEditingInput()) {
681
+ this.requestRender();
682
+ }
683
+ }, 120);
684
+ }
685
+ stopAnimation() {
686
+ if (!this.animationTimer) {
687
+ return;
688
+ }
689
+ clearInterval(this.animationTimer);
690
+ this.animationTimer = null;
691
+ }
692
+ scrollFocusedPane(direction, amount) {
693
+ const metrics = this.getPaneMetrics(this.focusedPane);
694
+ if (metrics.maxStart === 0) {
695
+ this.statusLine = `${paneDisplayName(this.focusedPane)} fits on screen.`;
696
+ this.requestRender();
697
+ return;
698
+ }
699
+ if (this.focusedPane === 'sessions') {
700
+ const delta = direction === 'down' ? amount : -amount;
701
+ this.paneScroll.sessions = clamp(this.paneScroll.sessions + delta, 0, metrics.maxStart);
702
+ this.statusLine = `Sessions scroll ${this.paneScroll.sessions + 1}/${metrics.maxStart + 1}.`;
703
+ this.requestRender();
704
+ return;
705
+ }
706
+ const pane = this.focusedPane;
707
+ const delta = direction === 'up' ? amount : -amount;
708
+ this.paneScroll[pane] = clamp(this.paneScroll[pane] + delta, 0, metrics.maxStart);
709
+ this.statusLine = this.paneScroll[pane] === 0
710
+ ? `${paneDisplayName(pane)} following newest content.`
711
+ : `${paneDisplayName(pane)} scrolled ${this.paneScroll[pane]} line${this.paneScroll[pane] === 1 ? '' : 's'} above latest.`;
712
+ this.requestRender();
713
+ }
714
+ jumpFocusedPane(target) {
715
+ const metrics = this.getPaneMetrics(this.focusedPane);
716
+ if (this.focusedPane === 'sessions') {
717
+ this.paneScroll.sessions = target === 'top' ? 0 : metrics.maxStart;
718
+ this.statusLine = target === 'top'
719
+ ? 'Sessions at top.'
720
+ : 'Sessions at bottom.';
721
+ this.requestRender();
722
+ return;
723
+ }
724
+ const pane = this.focusedPane;
725
+ this.paneScroll[pane] = target === 'bottom' ? 0 : metrics.maxStart;
726
+ this.statusLine = target === 'bottom'
727
+ ? `${paneDisplayName(pane)} following newest content.`
728
+ : `${paneDisplayName(pane)} at oldest visible content.`;
729
+ this.requestRender();
730
+ }
731
+ pageScrollAmount() {
732
+ const rows = output.rows ?? 40;
733
+ return Math.max(3, Math.floor(Math.max(1, rows - 5) / 2));
734
+ }
735
+ async moveSession(offset) {
736
+ if (this.sessions.length === 0) {
737
+ return;
738
+ }
739
+ const currentIndex = Math.max(0, this.sessions.findIndex((session) => session.id === this.selectedSessionId));
740
+ const nextIndex = (currentIndex + offset + this.sessions.length) % this.sessions.length;
741
+ const nextSession = this.sessions[nextIndex];
742
+ if (!nextSession || nextSession.id === this.selectedSessionId) {
743
+ return;
744
+ }
745
+ this.selectedSessionId = nextSession.id;
746
+ await this.loadSelectedSessionDetail();
747
+ this.statusLine = `Selected session ${nextSession.title}.`;
748
+ this.requestRender();
749
+ }
750
+ currentProcessActivity() {
751
+ return this.activityFor(this.selectedSessionId || 'global');
752
+ }
753
+ insertText(text) {
754
+ this.inputValue = `${this.inputValue.slice(0, this.cursor)}${text}${this.inputValue.slice(this.cursor)}`;
755
+ this.cursor += text.length;
756
+ }
757
+ deleteBackward() {
758
+ if (this.cursor === 0) {
759
+ return;
760
+ }
761
+ this.inputValue = `${this.inputValue.slice(0, this.cursor - 1)}${this.inputValue.slice(this.cursor)}`;
762
+ this.cursor -= 1;
763
+ }
764
+ deleteForward() {
765
+ if (this.cursor >= this.inputValue.length) {
766
+ return;
767
+ }
768
+ this.inputValue = `${this.inputValue.slice(0, this.cursor)}${this.inputValue.slice(this.cursor + 1)}`;
769
+ }
770
+ async refreshCommands() {
771
+ this.commands = await listCommands(this.workspaceRoot);
772
+ }
773
+ async refreshSessions(options = {}) {
774
+ let sessions = await listConductorSessions(this.workspaceRoot);
775
+ if (sessions.length === 0 && options.createIfMissing) {
776
+ const created = await createConductorSession(this.workspaceRoot);
777
+ sessions = [created];
778
+ }
779
+ this.sessions = sessions;
780
+ const desiredSessionId = options.selectedSessionId ?? this.selectedSessionId ?? sessions[0]?.id ?? '';
781
+ this.selectedSessionId = sessions.find((session) => session.id === desiredSessionId)?.id ?? sessions[0]?.id ?? '';
782
+ this.ensureSessionVisible();
783
+ await this.loadSelectedSessionDetail();
784
+ }
785
+ async loadSelectedSessionDetail() {
786
+ if (!this.selectedSessionId) {
787
+ this.sessionDetail = null;
788
+ return;
789
+ }
790
+ this.sessionDetail = await getConductorSessionDetail(this.workspaceRoot, this.selectedSessionId);
791
+ }
792
+ activityFor(key) {
793
+ const existing = this.activityBySessionId.get(key);
794
+ if (existing) {
795
+ return existing;
796
+ }
797
+ const created = new ActivityBuffer();
798
+ this.activityBySessionId.set(key, created);
799
+ return created;
800
+ }
801
+ requestRender() {
802
+ if (this.renderTimer) {
803
+ return;
804
+ }
805
+ const minInterval = this.isEditingInput()
806
+ ? TYPING_RENDER_INTERVAL_MS
807
+ : this.busy
808
+ ? BUSY_RENDER_INTERVAL_MS
809
+ : IDLE_RENDER_INTERVAL_MS;
810
+ const delay = Math.max(0, minInterval - (Date.now() - this.lastRenderAt));
811
+ this.renderTimer = setTimeout(() => {
812
+ this.renderTimer = null;
813
+ this.render();
814
+ }, delay);
815
+ }
816
+ render() {
817
+ this.lastRenderAt = Date.now();
818
+ const columns = output.columns ?? 120;
819
+ const rows = output.rows ?? 40;
820
+ const contentHeight = Math.max(1, rows - 3);
821
+ const widths = computePaneWidths(columns);
822
+ const rightHeights = computeRightPaneHeights(contentHeight);
823
+ const sessionsPane = this.buildPaneView('sessions', 'Sessions', widths.left, contentHeight);
824
+ const messagesPane = this.buildPaneView('messages', 'Messages', widths.center, contentHeight);
825
+ const processPane = this.buildPaneView('process', 'Execution', widths.right, rightHeights.process);
826
+ const contextPane = this.buildPaneView('context', 'Context', widths.right, rightHeights.context);
827
+ const header = renderMenuBar(this.buildHeaderLine(), columns);
828
+ const status = renderStatusBar(this.buildStatusLine(), columns, this.focusedPane);
829
+ const inputPrefix = this.pendingQuestion ? ' Clarify ? ' : ' Command > ';
830
+ const inputLine = renderCommandBar(inputPrefix, this.inputValue, columns);
831
+ const gap = paint(' ', ANSI.bgBlue);
832
+ const lines = [header];
833
+ for (let index = 0; index < contentHeight; index += 1) {
834
+ const rightLine = index < rightHeights.process
835
+ ? processPane[index]
836
+ : contextPane[index - rightHeights.process];
837
+ lines.push(`${sessionsPane[index]}${gap}${messagesPane[index]}${gap}${rightLine}`);
838
+ }
839
+ lines.push(status);
840
+ lines.push(inputLine);
841
+ this.paintFrame(lines, { columns, rows });
842
+ const cursorColumn = Math.min(columns, inputPrefix.length + this.cursor + 1);
843
+ output.write(`\u001b[${rows};${cursorColumn}H`);
844
+ }
845
+ paintFrame(lines, size) {
846
+ const patch = computeFramePatch(this.previousFrameLines, lines, this.previousFrameSize, size);
847
+ let buffer = '\u001b[0m';
848
+ if (patch.fullRender) {
849
+ buffer += `\u001b[H\u001b[2J${lines.join('\n')}\u001b[?25h`;
850
+ }
851
+ else if (patch.changedRows.length > 0) {
852
+ for (const change of patch.changedRows) {
853
+ buffer += `\u001b[${change.row};1H\u001b[2K${change.line}`;
854
+ }
855
+ buffer += '\u001b[?25h';
856
+ }
857
+ else {
858
+ buffer += '\u001b[?25h';
859
+ }
860
+ output.write(buffer);
861
+ this.previousFrameLines = [...lines];
862
+ this.previousFrameSize = size;
863
+ }
864
+ buildHeaderLine() {
865
+ const sessionLabel = this.sessionDetail
866
+ ? `${this.sessionDetail.title} (${this.sessionDetail.id.slice(0, 8)})`
867
+ : 'no session';
868
+ const skillLabel = this.commands.length > 0
869
+ ? `${this.commands.length} skill${this.commands.length === 1 ? '' : 's'}`
870
+ : 'no skills';
871
+ const mode = this.busy ? '[Running]' : '[Idle]';
872
+ return ` DeepClause Session Skills Run Help ${sessionLabel} ${mode} ${skillLabel}`;
873
+ }
874
+ buildStatusLine() {
875
+ const focus = paneDisplayName(this.focusedPane);
876
+ if (!this.pendingQuestion && this.focusedPane !== 'sessions') {
877
+ const follow = this.paneScroll[this.focusedPane] === 0 ? 'follow' : `scroll ${this.paneScroll[this.focusedPane]}`;
878
+ return `${this.statusLine} | Focus ${focus} | ${follow}`;
879
+ }
880
+ if (this.pendingQuestion) {
881
+ return `Clarify: ${condenseWhitespace(this.pendingQuestion.prompt)} | Focus ${focus}`;
882
+ }
883
+ return `${this.statusLine} | Focus ${focus}`;
884
+ }
885
+ buildSessionPaneBody() {
886
+ if (this.sessions.length === 0) {
887
+ return ['No sessions yet.'];
888
+ }
889
+ const lines = [];
890
+ for (const session of this.sessions) {
891
+ const selected = session.id === this.selectedSessionId ? '>' : ' ';
892
+ lines.push(`${selected} ${session.title}`);
893
+ lines.push(` ${formatTimestamp(session.updatedAt)}`);
894
+ }
895
+ return lines;
896
+ }
897
+ buildMessagesPaneBody() {
898
+ const entries = [];
899
+ if (this.sessionDetail) {
900
+ for (const message of this.sessionDetail.messages) {
901
+ entries.push({ role: message.role, content: message.content });
902
+ }
903
+ }
904
+ const persistedEphemeral = this.ephemeralMessagesBySessionId.get(this.selectedSessionId) ?? [];
905
+ entries.push(...persistedEphemeral);
906
+ if (this.currentPreview && this.currentPreview.sessionId === this.selectedSessionId) {
907
+ entries.push(...this.currentPreview.entries);
908
+ }
909
+ if (entries.length === 0) {
910
+ return [
911
+ '[System] No messages yet.',
912
+ ' Enter a prompt to start a conductor turn.',
913
+ ' The center pane shows user and assistant messages.',
914
+ ];
915
+ }
916
+ const lines = [];
917
+ for (const entry of entries) {
918
+ const header = this.formatMessageHeader(entry);
919
+ lines.push(header);
920
+ const bodyLines = entry.content
921
+ ? entry.content.split(/\r?\n/)
922
+ : (entry.pending ? [''] : ['(empty)']);
923
+ if (entry.pending && !entry.content) {
924
+ bodyLines[0] = 'thinking...';
925
+ }
926
+ for (const line of bodyLines) {
927
+ lines.push(` ${line}`);
928
+ }
929
+ lines.push('');
930
+ }
931
+ return lines;
932
+ }
933
+ buildProcessPaneBody() {
934
+ const activity = this.currentProcessActivity().snapshot();
935
+ if (activity.length > 0) {
936
+ return activity;
937
+ }
938
+ return [
939
+ 'No execution activity yet.',
940
+ 'Tool calls, logs, outputs, and exceptions render here.',
941
+ 'Use /cancel to abort the current execution.',
942
+ ];
943
+ }
944
+ buildContextPaneBody() {
945
+ if (!this.sessionDetail) {
946
+ return ['No session selected.'];
947
+ }
948
+ const contextSize = summarizeContextSize(this.sessionDetail);
949
+ const usageEntries = Object.entries(this.sessionDetail.usageByModel ?? {})
950
+ .sort((left, right) => right[1].totalTokens - left[1].totalTokens || left[0].localeCompare(right[0]));
951
+ const body = [
952
+ 'Context Size',
953
+ `Messages ${formatInteger(contextSize.messageCount)}`,
954
+ `Transcript ${formatByteSize(contextSize.messageBytes)} (~${formatInteger(contextSize.messageTokens)} tok)`,
955
+ `Task Memory ${formatByteSize(contextSize.taskMemoryBytes)} (~${formatInteger(contextSize.taskMemoryTokens)} tok)`,
956
+ `Assistant Memory ${formatByteSize(contextSize.assistantMemoryBytes)} (~${formatInteger(contextSize.assistantMemoryTokens)} tok)`,
957
+ `Total ${formatByteSize(contextSize.totalBytes)} (~${formatInteger(contextSize.totalTokens)} tok)`,
958
+ '',
959
+ 'Session Token Usage',
960
+ ];
961
+ if (usageEntries.length === 0) {
962
+ body.push('(no usage recorded yet)');
963
+ return body;
964
+ }
965
+ for (const [modelId, usage] of usageEntries) {
966
+ body.push(modelId);
967
+ body.push(` total ${formatInteger(usage.totalTokens)} in ${formatInteger(usage.inputTokens)} out ${formatInteger(usage.outputTokens)} calls ${formatInteger(usage.calls)}`);
968
+ const extras = [];
969
+ if (usage.cacheReadTokens > 0) {
970
+ extras.push(`cache-read ${formatInteger(usage.cacheReadTokens)}`);
971
+ }
972
+ if (usage.cacheWriteTokens > 0) {
973
+ extras.push(`cache-write ${formatInteger(usage.cacheWriteTokens)}`);
974
+ }
975
+ if (usage.reasoningTokens > 0) {
976
+ extras.push(`reasoning ${formatInteger(usage.reasoningTokens)}`);
977
+ }
978
+ if (extras.length > 0) {
979
+ body.push(` ${extras.join(' ')}`);
980
+ }
981
+ body.push('');
982
+ }
983
+ return body;
984
+ }
985
+ buildPaneView(kind, title, width, height) {
986
+ const metrics = this.getPaneMetrics(kind, width, height);
987
+ const paneTitle = this.buildPaneTitle(title, kind, metrics);
988
+ return buildPane(paneTitle, metrics.lines.slice(metrics.start, metrics.start + metrics.viewportLines), width, height, kind, this.focusedPane === kind);
989
+ }
990
+ buildPaneTitle(title, kind, metrics) {
991
+ if (kind === 'sessions') {
992
+ return this.focusedPane === kind ? `${title} [focus]` : title;
993
+ }
994
+ if (metrics.maxStart === 0) {
995
+ return this.focusedPane === kind ? `${title} [focus]` : title;
996
+ }
997
+ return this.paneScroll[kind] === 0
998
+ ? `${title}${this.focusedPane === kind ? ' [focus]' : ''} [follow]`
999
+ : `${title}${this.focusedPane === kind ? ' [focus]' : ''} [-${this.paneScroll[kind]}]`;
1000
+ }
1001
+ getPaneMetrics(kind, width = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).width, height = paneDimensionsForKind(kind, output.columns ?? 120, output.rows ?? 40).height) {
1002
+ const innerWidth = Math.max(1, width - 2);
1003
+ const rawBody = this.getPaneBody(kind);
1004
+ const lines = wrapBodyLines(rawBody, innerWidth);
1005
+ const viewportLines = Math.max(0, height - 2);
1006
+ const maxStart = Math.max(0, lines.length - viewportLines);
1007
+ if (kind === 'sessions') {
1008
+ this.paneScroll.sessions = clamp(this.paneScroll.sessions, 0, maxStart);
1009
+ return {
1010
+ lines,
1011
+ start: this.paneScroll.sessions,
1012
+ maxStart,
1013
+ viewportLines,
1014
+ };
1015
+ }
1016
+ this.paneScroll[kind] = clamp(this.paneScroll[kind], 0, maxStart);
1017
+ return {
1018
+ lines,
1019
+ start: Math.max(0, maxStart - this.paneScroll[kind]),
1020
+ maxStart,
1021
+ viewportLines,
1022
+ };
1023
+ }
1024
+ getPaneBody(kind) {
1025
+ switch (kind) {
1026
+ case 'sessions':
1027
+ return this.buildSessionPaneBody();
1028
+ case 'messages':
1029
+ return this.buildMessagesPaneBody();
1030
+ case 'process':
1031
+ return this.buildProcessPaneBody();
1032
+ case 'context':
1033
+ return this.buildContextPaneBody();
1034
+ }
1035
+ }
1036
+ ensureSessionVisible() {
1037
+ const selectedIndex = Math.max(0, this.sessions.findIndex((session) => session.id === this.selectedSessionId));
1038
+ const contentRows = Math.max(1, (output.rows ?? 40) - 5);
1039
+ const selectedLine = selectedIndex * 2;
1040
+ if (selectedLine < this.paneScroll.sessions) {
1041
+ this.paneScroll.sessions = selectedLine;
1042
+ return;
1043
+ }
1044
+ if (selectedLine >= this.paneScroll.sessions + contentRows) {
1045
+ this.paneScroll.sessions = Math.max(0, selectedLine - contentRows + 1);
1046
+ }
1047
+ }
1048
+ formatMessageHeader(entry) {
1049
+ const spinner = entry.pending ? ` ${currentSpinnerFrame()}` : '';
1050
+ switch (entry.role) {
1051
+ case 'user':
1052
+ return '[You]';
1053
+ case 'assistant':
1054
+ return entry.error ? '[Assistant Error]' : `[Assistant${spinner}]`;
1055
+ case 'system':
1056
+ return `[System${spinner}]`;
1057
+ }
1058
+ }
1059
+ close() {
1060
+ if (!this.closeResolver) {
1061
+ return;
1062
+ }
1063
+ const resolve = this.closeResolver;
1064
+ this.closeResolver = null;
1065
+ resolve();
1066
+ }
1067
+ isEditingInput() {
1068
+ return !this.pendingQuestion && this.inputValue.length > 0;
1069
+ }
1070
+ }
1071
+ export async function startTui(workspaceRoot = process.cwd(), options = {}) {
1072
+ if (input.isTTY && output.isTTY) {
1073
+ const app = new FullscreenTui(workspaceRoot, options);
1074
+ await app.start();
1075
+ return;
1076
+ }
1077
+ await startLineTui(workspaceRoot, options);
1078
+ }
1079
+ async function startLineTui(workspaceRoot = process.cwd(), options = {}) {
1080
+ const rl = createInterface({ input, output });
1081
+ try {
1082
+ let session = await chooseSession(rl, workspaceRoot);
1083
+ renderHeader(session);
1084
+ while (true) {
1085
+ const prompt = `${paint(session.title || session.id.slice(0, 8), ANSI.bold)} ${paint('>', ANSI.dim)} `;
1086
+ const line = (await rl.question(prompt)).trim();
1087
+ if (!line) {
1088
+ continue;
1089
+ }
1090
+ const parsed = parseSlashInput(line);
1091
+ if (parsed.kind === 'builtin') {
1092
+ switch (parsed.name) {
1093
+ case 'cancel':
1094
+ console.log('No execution is currently running in line mode.');
1095
+ continue;
1096
+ case 'exit':
1097
+ case 'quit':
1098
+ return;
1099
+ case 'new':
1100
+ session = parsed.rawArgs
1101
+ ? await createConductorSession(workspaceRoot, parsed.rawArgs)
1102
+ : await createSessionInteractive(rl, workspaceRoot);
1103
+ console.log(`${paint('switched', ANSI.green)} ${session.title}`);
1104
+ renderHeader(session);
1105
+ continue;
1106
+ case 'sessions':
1107
+ session = await chooseSession(rl, workspaceRoot);
1108
+ renderHeader(session);
1109
+ continue;
1110
+ case 'help':
1111
+ for (const helpLine of buildHelpLines()) {
1112
+ console.log(helpLine);
1113
+ }
1114
+ continue;
1115
+ }
1116
+ }
1117
+ if (parsed.kind === 'skill') {
1118
+ console.log(divider('-'));
1119
+ console.log(`${paint('skill', ANSI.dim)} /${parsed.name}${parsed.args.length > 0 ? ` ${parsed.args.join(' ')}` : ''}`);
1120
+ console.log(divider('-'));
1121
+ const printer = new LiveExecutionPrinter();
1122
+ try {
1123
+ await runSkillCommand(workspaceRoot, parsed.name, parsed.args, {
1124
+ sandbox: options.sandbox,
1125
+ onUserInput: async (question) => {
1126
+ printer.finish();
1127
+ console.log(`${paint('clarify', ANSI.yellow)} ${question}`);
1128
+ return (await rl.question('> ')).trim();
1129
+ },
1130
+ onEvent: (event) => printer.handle(event),
1131
+ });
1132
+ }
1133
+ catch (error) {
1134
+ printer.finish();
1135
+ console.log(`${paint('error', ANSI.red)} ${error.message}`);
1136
+ }
1137
+ printer.finish();
1138
+ console.log('');
1139
+ console.log(divider('-'));
1140
+ console.log('');
1141
+ continue;
1142
+ }
1143
+ console.log(divider('-'));
1144
+ console.log(`${paint('task', ANSI.dim)} ${parsed.prompt}`);
1145
+ console.log(divider('-'));
1146
+ const printer = new LiveExecutionPrinter();
1147
+ const result = await runConductorTurn(parsed.prompt, {
1148
+ workspaceRoot,
1149
+ sessionId: session.id,
1150
+ stream: true,
1151
+ headless: true,
1152
+ sandbox: options.sandbox,
1153
+ onUserInput: async (question) => {
1154
+ printer.finish();
1155
+ console.log(`${paint('clarify', ANSI.yellow)} ${question}`);
1156
+ return (await rl.question('> ')).trim();
1157
+ },
1158
+ onEvent: (event) => printer.handle(event),
1159
+ });
1160
+ printer.finish();
1161
+ console.log('');
1162
+ console.log(divider('-'));
1163
+ console.log('');
1164
+ const sessions = await listConductorSessions(workspaceRoot);
1165
+ session = sessions.find((entry) => entry.id === result.sessionId) ?? session;
1166
+ }
1167
+ }
1168
+ finally {
1169
+ rl.close();
1170
+ }
1171
+ }
1172
+ export async function runPromptHeadless(prompt, workspaceRoot = process.cwd(), options = {}) {
1173
+ const session = await createConductorSession(workspaceRoot);
1174
+ console.log(`Session: ${session.id}`);
1175
+ const result = await runConductorTurn(prompt, {
1176
+ workspaceRoot,
1177
+ sessionId: session.id,
1178
+ stream: false,
1179
+ headless: true,
1180
+ sandbox: options.sandbox,
1181
+ });
1182
+ if (result.output.length > 0 || result.answer) {
1183
+ console.log('');
1184
+ }
1185
+ for (const line of result.output) {
1186
+ console.log(line);
1187
+ }
1188
+ if (result.answer) {
1189
+ if (result.output.length > 0) {
1190
+ console.log('');
1191
+ }
1192
+ console.log(result.answer);
1193
+ }
1194
+ if (result.error) {
1195
+ throw new Error(result.error);
1196
+ }
1197
+ }
1198
+ async function runSkillCommand(workspaceRoot, skillName, args, options = {}) {
1199
+ const commands = await listCommands(workspaceRoot);
1200
+ const command = commands.find((entry) => entry.name === skillName);
1201
+ if (!command) {
1202
+ throw new Error(`Unknown skill: ${skillName}`);
1203
+ }
1204
+ const result = await run(command.path, args, {
1205
+ configRoot: workspaceRoot,
1206
+ headless: true,
1207
+ stream: true,
1208
+ sandbox: options.sandbox,
1209
+ signal: options.signal,
1210
+ onUserInput: options.onUserInput,
1211
+ onEvent: (event) => options.onEvent?.({ scope: 'child', childSlug: skillName, event }),
1212
+ });
1213
+ return result;
1214
+ }
1215
+ export function parseCommandArgs(rawArgs) {
1216
+ const args = [];
1217
+ let current = '';
1218
+ let quote = null;
1219
+ let escaping = false;
1220
+ for (const char of rawArgs.trim()) {
1221
+ if (escaping) {
1222
+ current += char;
1223
+ escaping = false;
1224
+ continue;
1225
+ }
1226
+ if (char === '\\') {
1227
+ escaping = true;
1228
+ continue;
1229
+ }
1230
+ if (quote) {
1231
+ if (char === quote) {
1232
+ quote = null;
1233
+ }
1234
+ else {
1235
+ current += char;
1236
+ }
1237
+ continue;
1238
+ }
1239
+ if (char === '"' || char === '\'') {
1240
+ quote = char;
1241
+ continue;
1242
+ }
1243
+ if (/\s/.test(char)) {
1244
+ if (current) {
1245
+ args.push(current);
1246
+ current = '';
1247
+ }
1248
+ continue;
1249
+ }
1250
+ current += char;
1251
+ }
1252
+ if (escaping) {
1253
+ current += '\\';
1254
+ }
1255
+ if (current) {
1256
+ args.push(current);
1257
+ }
1258
+ return args;
1259
+ }
1260
+ export function parseSlashInput(line) {
1261
+ const trimmed = line.trim();
1262
+ if (!trimmed.startsWith('/')) {
1263
+ return { kind: 'text', prompt: trimmed };
1264
+ }
1265
+ if (trimmed === '/') {
1266
+ return { kind: 'builtin', name: 'help', rawArgs: '', args: [] };
1267
+ }
1268
+ const match = trimmed.slice(1).match(/^(\S+)(?:\s+(.*))?$/);
1269
+ if (!match) {
1270
+ return { kind: 'builtin', name: 'help', rawArgs: '', args: [] };
1271
+ }
1272
+ const name = match[1];
1273
+ const rawArgs = (match[2] ?? '').trim();
1274
+ if (BUILTIN_SLASH_COMMANDS.includes(name)) {
1275
+ return {
1276
+ kind: 'builtin',
1277
+ name: name,
1278
+ rawArgs,
1279
+ args: name === 'new' && rawArgs ? [rawArgs] : parseCommandArgs(rawArgs),
1280
+ };
1281
+ }
1282
+ return {
1283
+ kind: 'skill',
1284
+ name,
1285
+ rawArgs,
1286
+ args: parseCommandArgs(rawArgs),
1287
+ };
1288
+ }
1289
+ export function canSubmitParsedInputWhileBusy(parsed) {
1290
+ return parsed.kind === 'builtin' && (parsed.name === 'cancel' || parsed.name === 'exit' || parsed.name === 'quit');
1291
+ }
1292
+ export function computeFramePatch(previousLines, nextLines, previousSize, nextSize) {
1293
+ if (!previousLines
1294
+ || !previousSize
1295
+ || previousSize.columns !== nextSize.columns
1296
+ || previousSize.rows !== nextSize.rows
1297
+ || previousLines.length !== nextLines.length) {
1298
+ return { fullRender: true, changedRows: [] };
1299
+ }
1300
+ const changedRows = [];
1301
+ for (let index = 0; index < nextLines.length; index += 1) {
1302
+ if (previousLines[index] !== nextLines[index]) {
1303
+ changedRows.push({ row: index + 1, line: nextLines[index] });
1304
+ }
1305
+ }
1306
+ return { fullRender: false, changedRows };
1307
+ }
1308
+ export function completeSlashCommand(inputValue, candidates) {
1309
+ if (!inputValue.startsWith('/')) {
1310
+ return { value: inputValue, matches: [], applied: false };
1311
+ }
1312
+ const match = inputValue.match(/^\/([^\s]*)(.*)$/s);
1313
+ if (!match) {
1314
+ return { value: inputValue, matches: [], applied: false };
1315
+ }
1316
+ const prefix = match[1];
1317
+ const suffix = match[2] ?? '';
1318
+ const uniqueCandidates = Array.from(new Set(candidates)).sort();
1319
+ const matches = uniqueCandidates.filter((candidate) => candidate.startsWith(prefix));
1320
+ if (matches.length === 0) {
1321
+ return { value: inputValue, matches: [], applied: false };
1322
+ }
1323
+ if (matches.length === 1) {
1324
+ return {
1325
+ value: `/${matches[0]}${suffix.length > 0 ? suffix : ' '}`,
1326
+ matches,
1327
+ applied: true,
1328
+ };
1329
+ }
1330
+ const sharedPrefix = longestCommonPrefix(matches);
1331
+ if (sharedPrefix.length > prefix.length) {
1332
+ return {
1333
+ value: `/${sharedPrefix}${suffix}`,
1334
+ matches,
1335
+ applied: true,
1336
+ };
1337
+ }
1338
+ return { value: inputValue, matches, applied: false };
1339
+ }
1340
+ async function chooseSession(rl, workspaceRoot) {
1341
+ const sessions = await listConductorSessions(workspaceRoot);
1342
+ if (sessions.length === 0) {
1343
+ return createSessionInteractive(rl, workspaceRoot);
1344
+ }
1345
+ console.log(divider('='));
1346
+ console.log(paint('Sessions', ANSI.bold));
1347
+ sessions.slice(0, 9).forEach((session, index) => {
1348
+ console.log(`${paint(String(index + 1).padStart(2, ' '), ANSI.cyan)} ${session.title} ${paint(session.updatedAt.replace('T', ' ').slice(0, 16), ANSI.dim)}`);
1349
+ });
1350
+ console.log(`${paint(' 0', ANSI.cyan)} Start a new session`);
1351
+ console.log(divider('='));
1352
+ while (true) {
1353
+ const answer = (await rl.question(paint('Select a session [0]: ', ANSI.dim))).trim();
1354
+ if (!answer || answer === '0') {
1355
+ return createSessionInteractive(rl, workspaceRoot);
1356
+ }
1357
+ const numeric = Number.parseInt(answer, 10);
1358
+ if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= Math.min(sessions.length, 9)) {
1359
+ return sessions[numeric - 1];
1360
+ }
1361
+ console.log('Enter a listed session number or 0 for a new session.');
1362
+ }
1363
+ }
1364
+ async function createSessionInteractive(rl, workspaceRoot) {
1365
+ const title = (await rl.question(paint('New session title (optional): ', ANSI.dim))).trim();
1366
+ return createConductorSession(workspaceRoot, title || undefined);
1367
+ }
1368
+ function renderHeader(session) {
1369
+ console.log(divider('='));
1370
+ console.log(`${paint('DeepClause', ANSI.bold, ANSI.cyan)} ${paint('interactive conductor', ANSI.dim)}`);
1371
+ console.log(`${paint('commands', ANSI.dim)} /new /sessions /help /exit /skill args`);
1372
+ console.log(`${paint('session', ANSI.dim)} ${paint(session.id, ANSI.cyan)}`);
1373
+ console.log(divider('='));
1374
+ console.log('');
1375
+ }
1376
+ function streamLabel(logEvent) {
1377
+ return logEvent.scope === 'child'
1378
+ ? `[${logEvent.childSlug ?? '?'}] llm`
1379
+ : 'llm';
1380
+ }
1381
+ function formatEventPrefix(logEvent) {
1382
+ return logEvent.scope === 'child'
1383
+ ? `[${logEvent.childSlug ?? '?'}] `
1384
+ : '';
1385
+ }
1386
+ function formatToolArgs(args) {
1387
+ if (!args) {
1388
+ return '';
1389
+ }
1390
+ const parts = [];
1391
+ for (const [key, value] of Object.entries(args)) {
1392
+ let rendered = typeof value === 'string' ? value : JSON.stringify(value);
1393
+ if (rendered.length > 80) {
1394
+ rendered = rendered.slice(0, 77) + '...';
1395
+ }
1396
+ parts.push(`${key}=${rendered}`);
1397
+ }
1398
+ return parts.join(', ');
1399
+ }
1400
+ function buildHelpLines() {
1401
+ return [
1402
+ 'commands',
1403
+ '/new [title] create a new conductor session',
1404
+ '/sessions refresh or choose sessions',
1405
+ '/cancel abort the current execution',
1406
+ '/help show this help',
1407
+ '/quit exit the TUI',
1408
+ '/<skill> [args] run a compiled skill directly',
1409
+ 'keys',
1410
+ 'Left/Right change focused pane',
1411
+ 'Up/Down select session or scroll focused pane',
1412
+ 'PgUp/PgDn page-scroll the focused pane',
1413
+ 'End jump to bottom or re-enable follow mode',
1414
+ 'Tab autocomplete /commands and /skills',
1415
+ ];
1416
+ }
1417
+ function computePaneWidths(columns) {
1418
+ const available = Math.max(36, columns - 2);
1419
+ let left = Math.max(18, Math.floor(available * 0.2));
1420
+ let right = Math.max(28, Math.floor(available * 0.3));
1421
+ let center = available - left - right;
1422
+ if (center < 30) {
1423
+ const needed = 30 - center;
1424
+ const reduceRight = Math.min(needed, Math.max(0, right - 18));
1425
+ right -= reduceRight;
1426
+ center += reduceRight;
1427
+ if (center < 30) {
1428
+ const reduceLeft = Math.min(30 - center, Math.max(0, left - 14));
1429
+ left -= reduceLeft;
1430
+ center += reduceLeft;
1431
+ }
1432
+ }
1433
+ center += available - left - center - right;
1434
+ return { left, center, right };
1435
+ }
1436
+ function computeRightPaneHeights(totalHeight) {
1437
+ if (totalHeight <= 8) {
1438
+ return { process: Math.max(1, totalHeight - 1), context: 1 };
1439
+ }
1440
+ let process = Math.max(8, Math.floor(totalHeight * 0.58));
1441
+ let context = totalHeight - process;
1442
+ if (context < 6) {
1443
+ const deficit = 6 - context;
1444
+ process = Math.max(6, process - deficit);
1445
+ context = totalHeight - process;
1446
+ }
1447
+ return { process, context };
1448
+ }
1449
+ function buildPane(title, body, width, height, kind, active = false) {
1450
+ if (height <= 0) {
1451
+ return [];
1452
+ }
1453
+ const innerWidth = Math.max(1, width - 2);
1454
+ const lines = [renderWindowTop(title, innerWidth, active)];
1455
+ bodyLoop: for (const line of body) {
1456
+ for (const wrapped of wrapPlainText(line, innerWidth)) {
1457
+ if (lines.length >= height - 1) {
1458
+ break bodyLoop;
1459
+ }
1460
+ lines.push(renderWindowBody(wrapped, innerWidth, kind));
1461
+ }
1462
+ }
1463
+ while (lines.length < height - 1) {
1464
+ lines.push(renderWindowBody('', innerWidth, kind));
1465
+ }
1466
+ if (height >= 2) {
1467
+ lines.push(renderWindowBottom(innerWidth, active));
1468
+ }
1469
+ return lines.slice(0, height);
1470
+ }
1471
+ function wrapPlainText(text, width) {
1472
+ if (width <= 0) {
1473
+ return [''];
1474
+ }
1475
+ const result = [];
1476
+ for (const rawLine of text.split(/\r?\n/)) {
1477
+ const line = rawLine || '';
1478
+ if (!line) {
1479
+ result.push('');
1480
+ continue;
1481
+ }
1482
+ let remaining = line;
1483
+ while (remaining.length > width) {
1484
+ const slice = remaining.slice(0, width);
1485
+ const breakIndex = slice.lastIndexOf(' ');
1486
+ if (breakIndex > Math.floor(width * 0.4)) {
1487
+ result.push(slice.slice(0, breakIndex));
1488
+ remaining = remaining.slice(breakIndex + 1);
1489
+ }
1490
+ else {
1491
+ result.push(slice);
1492
+ remaining = remaining.slice(width);
1493
+ }
1494
+ }
1495
+ result.push(remaining);
1496
+ }
1497
+ return result;
1498
+ }
1499
+ function padRight(text, width) {
1500
+ return text.length >= width ? text : `${text}${' '.repeat(width - text.length)}`;
1501
+ }
1502
+ function ellipsize(text, width) {
1503
+ if (text.length <= width) {
1504
+ return text;
1505
+ }
1506
+ if (width <= 3) {
1507
+ return text.slice(0, width);
1508
+ }
1509
+ return `${text.slice(0, width - 3)}...`;
1510
+ }
1511
+ function condenseWhitespace(text) {
1512
+ return text.replace(/\s+/g, ' ').trim();
1513
+ }
1514
+ function formatTimestamp(value) {
1515
+ return value.replace('T', ' ').slice(0, 16);
1516
+ }
1517
+ function summarizeContextSize(detail) {
1518
+ const transcript = detail.messages.map((message) => `${message.role}: ${message.content}`).join('\n');
1519
+ const taskMemory = detail.taskMemory ?? '';
1520
+ const assistantMemory = detail.assistantMemory ?? '';
1521
+ const messageBytes = Buffer.byteLength(transcript, 'utf8');
1522
+ const taskMemoryBytes = Buffer.byteLength(taskMemory, 'utf8');
1523
+ const assistantMemoryBytes = Buffer.byteLength(assistantMemory, 'utf8');
1524
+ const messageTokens = estimateTokenCount(transcript);
1525
+ const taskMemoryTokens = estimateTokenCount(taskMemory);
1526
+ const assistantMemoryTokens = estimateTokenCount(assistantMemory);
1527
+ return {
1528
+ messageCount: detail.messages.length,
1529
+ messageBytes,
1530
+ messageTokens,
1531
+ taskMemoryBytes,
1532
+ taskMemoryTokens,
1533
+ assistantMemoryBytes,
1534
+ assistantMemoryTokens,
1535
+ totalBytes: messageBytes + taskMemoryBytes + assistantMemoryBytes,
1536
+ totalTokens: messageTokens + taskMemoryTokens + assistantMemoryTokens,
1537
+ };
1538
+ }
1539
+ function estimateTokenCount(text) {
1540
+ const normalized = text.trim();
1541
+ if (!normalized) {
1542
+ return 0;
1543
+ }
1544
+ return Math.max(1, Math.ceil(normalized.length / 4));
1545
+ }
1546
+ function formatByteSize(bytes) {
1547
+ if (bytes < 1024) {
1548
+ return `${bytes} B`;
1549
+ }
1550
+ if (bytes < 1024 * 1024) {
1551
+ const kib = bytes / 1024;
1552
+ return `${kib >= 10 ? kib.toFixed(0) : kib.toFixed(1)} KB`;
1553
+ }
1554
+ const mib = bytes / (1024 * 1024);
1555
+ return `${mib.toFixed(1)} MB`;
1556
+ }
1557
+ function formatInteger(value) {
1558
+ return new Intl.NumberFormat('en-US').format(value);
1559
+ }
1560
+ function longestCommonPrefix(values) {
1561
+ if (values.length === 0) {
1562
+ return '';
1563
+ }
1564
+ let prefix = values[0];
1565
+ for (const value of values.slice(1)) {
1566
+ while (!value.startsWith(prefix) && prefix) {
1567
+ prefix = prefix.slice(0, -1);
1568
+ }
1569
+ if (!prefix) {
1570
+ break;
1571
+ }
1572
+ }
1573
+ return prefix;
1574
+ }
1575
+ function renderMenuBar(text, columns) {
1576
+ return paint(padRight(ellipsize(text, columns), columns), ANSI.black, ANSI.bgWhite, ANSI.bold);
1577
+ }
1578
+ function renderStatusBar(status, columns, focusedPane) {
1579
+ const hotkeys = [
1580
+ ['<- ->', 'Pane'],
1581
+ [focusedPane === 'sessions' ? 'Up/Down' : 'PgUp/PgDn', focusedPane === 'sessions' ? 'Select' : 'Scroll'],
1582
+ ['End', focusedPane === 'sessions' ? 'Bottom' : 'Follow'],
1583
+ ['/cancel', 'Stop'],
1584
+ ['^C', 'Quit'],
1585
+ ];
1586
+ let hotkeyWidth = 0;
1587
+ const selected = [];
1588
+ for (const hotkey of hotkeys) {
1589
+ const width = hotkey[0].length + hotkey[1].length + 4;
1590
+ if (hotkeyWidth + width > Math.max(0, columns - 12)) {
1591
+ break;
1592
+ }
1593
+ hotkeyWidth += width;
1594
+ selected.push(hotkey);
1595
+ }
1596
+ const leftWidth = Math.max(0, columns - hotkeyWidth);
1597
+ const statusPart = paint(padRight(ellipsize(status, leftWidth), leftWidth), ANSI.black, ANSI.bgCyan);
1598
+ const hotkeyPart = selected.map(([key, label]) => (`${paint(` ${key} `, ANSI.black, ANSI.bgWhite, ANSI.bold)}${paint(` ${label} `, ANSI.black, ANSI.bgCyan)}`)).join('');
1599
+ return `${statusPart}${hotkeyPart}`;
1600
+ }
1601
+ function renderCommandBar(prefix, value, columns) {
1602
+ const prefixPart = paint(prefix, ANSI.black, ANSI.bgWhite, ANSI.bold);
1603
+ const remainingWidth = Math.max(0, columns - prefix.length);
1604
+ const valuePart = paint(padRight(ellipsize(value, remainingWidth), remainingWidth), ANSI.brightWhite, ANSI.bgBlue);
1605
+ return `${prefixPart}${valuePart}`;
1606
+ }
1607
+ function renderWindowTop(title, innerWidth, active) {
1608
+ const safeLabel = ` ${ellipsize(title, Math.max(1, innerWidth - 2))} `;
1609
+ const fillWidth = Math.max(0, innerWidth - safeLabel.length);
1610
+ const leftFill = Math.floor(fillWidth / 2);
1611
+ const rightFill = fillWidth - leftFill;
1612
+ return [
1613
+ paint(`╔${'═'.repeat(leftFill)}`, ANSI.brightCyan, ANSI.bgBlue),
1614
+ paint(safeLabel, ANSI.black, active ? ANSI.bgCyan : ANSI.bgWhite, ANSI.bold),
1615
+ paint(`${'═'.repeat(rightFill)}╗`, ANSI.brightCyan, ANSI.bgBlue),
1616
+ ].join('');
1617
+ }
1618
+ function renderWindowBottom(innerWidth, _active) {
1619
+ return paint(`╚${'═'.repeat(innerWidth)}╝`, ANSI.brightCyan, ANSI.bgBlue);
1620
+ }
1621
+ function renderWindowBody(text, innerWidth, kind) {
1622
+ const normalized = text.trim().toLowerCase();
1623
+ const content = padRight(ellipsize(text, innerWidth), innerWidth);
1624
+ const border = paint('║', ANSI.brightCyan, ANSI.bgBlue);
1625
+ if (kind === 'sessions' && text.startsWith('> ')) {
1626
+ return `${border}${paint(content, ANSI.black, ANSI.bgCyan, ANSI.bold)}${border}`;
1627
+ }
1628
+ if (kind === 'messages') {
1629
+ if (text.startsWith('[You]')) {
1630
+ return `${border}${paint(content, ANSI.brightCyan, ANSI.bgBlue, ANSI.bold)}${border}`;
1631
+ }
1632
+ if (text.startsWith('[Assistant Error]')) {
1633
+ return `${border}${paint(content, ANSI.red, ANSI.bgBlue, ANSI.bold)}${border}`;
1634
+ }
1635
+ if (text.startsWith('[Assistant')) {
1636
+ return `${border}${paint(content, ANSI.brightYellow, ANSI.bgBlue, ANSI.bold)}${border}`;
1637
+ }
1638
+ if (text.startsWith('[System')) {
1639
+ return `${border}${paint(content, ANSI.cyan, ANSI.bgBlue, ANSI.bold)}${border}`;
1640
+ }
1641
+ }
1642
+ if (kind === 'process') {
1643
+ if (normalized.startsWith('error')) {
1644
+ return `${border}${paint(content, ANSI.red, ANSI.bgBlue, ANSI.bold)}${border}`;
1645
+ }
1646
+ if (normalized.startsWith('answer')) {
1647
+ return `${border}${paint(content, ANSI.brightYellow, ANSI.bgBlue, ANSI.bold)}${border}`;
1648
+ }
1649
+ if (normalized.startsWith('tool')) {
1650
+ return `${border}${paint(content, ANSI.brightCyan, ANSI.bgBlue)}${border}`;
1651
+ }
1652
+ if (normalized.startsWith('clarify')) {
1653
+ return `${border}${paint(content, ANSI.yellow, ANSI.bgBlue, ANSI.bold)}${border}`;
1654
+ }
1655
+ if (normalized.startsWith('task') || normalized.startsWith('skill')) {
1656
+ return `${border}${paint(content, ANSI.brightWhite, ANSI.bgBlue, ANSI.bold)}${border}`;
1657
+ }
1658
+ }
1659
+ if (normalized === 'task memory' ||
1660
+ normalized === 'assistant memory' ||
1661
+ normalized === 'recent messages' ||
1662
+ normalized === 'commands' ||
1663
+ normalized === 'keys') {
1664
+ return `${border}${paint(content, ANSI.brightYellow, ANSI.bgBlue, ANSI.bold)}${border}`;
1665
+ }
1666
+ if (kind === 'context' && (normalized.startsWith('created ') || normalized.startsWith('updated ') || normalized.startsWith('session '))) {
1667
+ return `${border}${paint(content, ANSI.cyan, ANSI.bgBlue)}${border}`;
1668
+ }
1669
+ if (kind === 'sessions' && text.startsWith(' ')) {
1670
+ return `${border}${paint(content, ANSI.cyan, ANSI.bgBlue)}${border}`;
1671
+ }
1672
+ return `${border}${paint(content, ANSI.white, ANSI.bgBlue)}${border}`;
1673
+ }
1674
+ function divider(character) {
1675
+ const glyph = character === '=' ? '═' : character === '-' ? '─' : character;
1676
+ return paint(glyph.repeat(72), ANSI.brightCyan, ANSI.bgBlue);
1677
+ }
1678
+ function paneDisplayName(kind) {
1679
+ switch (kind) {
1680
+ case 'sessions':
1681
+ return 'Sessions';
1682
+ case 'messages':
1683
+ return 'Messages';
1684
+ case 'process':
1685
+ return 'Execution';
1686
+ case 'context':
1687
+ return 'Context';
1688
+ }
1689
+ }
1690
+ function paneDimensionsForKind(kind, columns, rows) {
1691
+ const widths = computePaneWidths(columns);
1692
+ const rightHeights = computeRightPaneHeights(Math.max(1, rows - 3));
1693
+ switch (kind) {
1694
+ case 'sessions':
1695
+ return { width: widths.left, height: Math.max(1, rows - 3) };
1696
+ case 'messages':
1697
+ return { width: widths.center, height: Math.max(1, rows - 3) };
1698
+ case 'process':
1699
+ return { width: widths.right, height: rightHeights.process };
1700
+ case 'context':
1701
+ return { width: widths.right, height: rightHeights.context };
1702
+ }
1703
+ }
1704
+ function wrapBodyLines(body, innerWidth) {
1705
+ const lines = [];
1706
+ for (const line of body) {
1707
+ lines.push(...wrapPlainText(line, innerWidth));
1708
+ }
1709
+ return lines;
1710
+ }
1711
+ function clamp(value, min, max) {
1712
+ if (value < min) {
1713
+ return min;
1714
+ }
1715
+ if (value > max) {
1716
+ return max;
1717
+ }
1718
+ return value;
1719
+ }
1720
+ function currentSpinnerFrame() {
1721
+ return SPINNER_FRAMES[Math.floor(Date.now() / 120) % SPINNER_FRAMES.length];
1722
+ }
1723
+ function summarizeRunResult(result) {
1724
+ if (result.answer?.trim()) {
1725
+ return result.answer;
1726
+ }
1727
+ const lastOutput = [...result.output].reverse().find((line) => line.trim());
1728
+ if (lastOutput) {
1729
+ return lastOutput;
1730
+ }
1731
+ if (result.error?.trim()) {
1732
+ return result.error;
1733
+ }
1734
+ return 'Execution completed without a final answer.';
1735
+ }
1736
+ function paint(text, ...codes) {
1737
+ if (!output.isTTY || codes.length === 0) {
1738
+ return text;
1739
+ }
1740
+ return `${codes.join('')}${text}${ANSI.reset}`;
1741
+ }
1742
+ //# sourceMappingURL=tui.js.map