praxis-agent 0.43.0 → 0.44.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 (59) hide show
  1. package/README.md +48 -47
  2. package/dist/application/native-session-projection.d.ts +4 -0
  3. package/dist/application/native-session-projection.js +135 -0
  4. package/dist/application/session-service.d.ts +0 -1
  5. package/dist/application/session-service.js +9 -170
  6. package/dist/application/top-level-agent-manager.d.ts +7 -0
  7. package/dist/application/top-level-agent-manager.js +21 -3
  8. package/dist/cli/controls.js +4 -0
  9. package/dist/cli/interactive.js +112 -156
  10. package/dist/cli/protocol.d.ts +5 -0
  11. package/dist/cli/protocol.js +45 -0
  12. package/dist/cli/provider-auth-command.d.ts +32 -0
  13. package/dist/cli/provider-auth-command.js +249 -0
  14. package/dist/cli/tui/ansi-frame-renderer.d.ts +2 -0
  15. package/dist/cli/tui/ansi-frame-renderer.js +109 -21
  16. package/dist/cli/tui/ansi-surface.d.ts +6 -22
  17. package/dist/cli/tui/ansi-surface.js +6 -126
  18. package/dist/cli/tui/ansi-theme.d.ts +3 -2
  19. package/dist/cli/tui/ansi-theme.js +22 -1
  20. package/dist/cli/tui/quiet-choice-rows.d.ts +26 -0
  21. package/dist/cli/tui/quiet-choice-rows.js +302 -0
  22. package/dist/cli/tui/quiet-frame-adapter.d.ts +18 -0
  23. package/dist/cli/tui/quiet-frame-adapter.js +90 -0
  24. package/dist/cli/tui/quiet-frame.d.ts +43 -0
  25. package/dist/cli/tui/quiet-frame.js +346 -0
  26. package/dist/cli/tui/quiet-operational-rows.d.ts +17 -0
  27. package/dist/cli/tui/quiet-operational-rows.js +413 -0
  28. package/dist/cli/tui/quiet-screen-projector.d.ts +25 -0
  29. package/dist/cli/tui/quiet-screen-projector.js +110 -0
  30. package/dist/cli/tui/quiet-settings-rows.d.ts +17 -0
  31. package/dist/cli/tui/quiet-settings-rows.js +490 -0
  32. package/dist/cli/tui/status-line.d.ts +15 -9
  33. package/dist/cli/tui/status-line.js +15 -6
  34. package/dist/cli/tui/theme.js +20 -20
  35. package/dist/cli/tui/transcript-viewport.d.ts +5 -0
  36. package/dist/cli/tui/transcript-viewport.js +213 -78
  37. package/dist/cli/tui/tui-row-ir.js +4 -4
  38. package/dist/cli/workspace-trust-prompt.js +2 -2
  39. package/dist/cli-runtime.d.ts +34 -1
  40. package/dist/cli-runtime.js +587 -184
  41. package/dist/maintenance/doctor.js +100 -22
  42. package/dist/persistence/in-memory-transcript-store.js +1 -7
  43. package/dist/persistence/native-transcript-store.d.ts +0 -9
  44. package/dist/persistence/native-transcript-store.js +4 -38
  45. package/dist/persistence/provider-credential-vault.d.ts +72 -0
  46. package/dist/persistence/provider-credential-vault.js +498 -0
  47. package/dist/providers/codex-oauth.d.ts +104 -0
  48. package/dist/providers/codex-oauth.js +770 -0
  49. package/dist/providers/codex-subscription.d.ts +33 -0
  50. package/dist/providers/codex-subscription.js +849 -0
  51. package/dist/providers/provider-auth.d.ts +71 -0
  52. package/dist/providers/provider-auth.js +149 -0
  53. package/dist/providers/provider-registry.d.ts +52 -0
  54. package/dist/providers/provider-registry.js +187 -0
  55. package/dist/providers/provider-settings.d.ts +38 -0
  56. package/dist/providers/provider-settings.js +334 -0
  57. package/dist/security/workspace-trust.d.ts +4 -3
  58. package/dist/security/workspace-trust.js +35 -1
  59. package/package.json +8 -4
@@ -10,7 +10,7 @@ const rgiEmojiPattern = /^\p{RGI_Emoji}$/v;
10
10
  const extendedPictographicPattern = /\p{Extended_Pictographic}/gu;
11
11
  const spacingMarkPattern = /\p{Spacing_Mark}/v;
12
12
  const unqualifiedKeycapPattern = /^[\d#*]\u20e3$/u;
13
- function graphemes(text) {
13
+ export function terminalGraphemes(text) {
14
14
  return Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment);
15
15
  }
16
16
  function isPrintableAscii(text) {
@@ -45,7 +45,7 @@ function isDoubleWidthEmoji(cluster) {
45
45
  return false;
46
46
  return (cluster.match(extendedPictographicPattern)?.length ?? 0) >= 2;
47
47
  }
48
- function graphemeWidth(cluster) {
48
+ export function terminalGraphemeWidth(cluster) {
49
49
  if (!cluster || zeroWidthClusterPattern.test(cluster))
50
50
  return 0;
51
51
  if (isDoubleWidthEmoji(cluster))
@@ -69,7 +69,7 @@ function graphemeWidth(cluster) {
69
69
  }
70
70
  return width;
71
71
  }
72
- function terminalStringWidth(text) {
72
+ export function terminalTextWidth(text) {
73
73
  if (isPrintableAscii(text))
74
74
  return text.length;
75
75
  const first = text.codePointAt(0);
@@ -80,14 +80,40 @@ function terminalStringWidth(text) {
80
80
  return 2;
81
81
  return first <= 0xffff ? 1 : isDoubleWidthEmoji(text) ? 2 : 1;
82
82
  }
83
- return graphemes(text).reduce((width, cluster) => width + graphemeWidth(cluster), 0);
83
+ return terminalGraphemes(text).reduce((width, cluster) => width + terminalGraphemeWidth(cluster), 0);
84
+ }
85
+ export function terminalTextHead(text, budget) {
86
+ const limit = Number.isFinite(budget) ? Math.max(0, Math.floor(budget)) : 0;
87
+ let used = 0;
88
+ const result = [];
89
+ for (const { segment: cluster } of graphemeSegmenter.segment(text)) {
90
+ const width = terminalGraphemeWidth(cluster);
91
+ if (used + width > limit)
92
+ break;
93
+ result.push(cluster);
94
+ used += width;
95
+ }
96
+ return result.join('');
97
+ }
98
+ export function terminalTextTail(text, budget) {
99
+ const limit = Number.isFinite(budget) ? Math.max(0, Math.floor(budget)) : 0;
100
+ let used = 0;
101
+ const result = [];
102
+ for (const cluster of [...terminalGraphemes(text)].reverse()) {
103
+ const width = terminalGraphemeWidth(cluster);
104
+ if (used + width > limit)
105
+ break;
106
+ result.unshift(cluster);
107
+ used += width;
108
+ }
109
+ return result.join('');
84
110
  }
85
111
  function expandTabs(line) {
86
112
  if (!line.includes('\t'))
87
113
  return line;
88
114
  let column = 0;
89
115
  let expanded = '';
90
- for (const cluster of graphemes(line)) {
116
+ for (const cluster of terminalGraphemes(line)) {
91
117
  if (cluster === '\t') {
92
118
  const spaces = 8 - (column % 8);
93
119
  expanded += ' '.repeat(spaces);
@@ -95,7 +121,7 @@ function expandTabs(line) {
95
121
  continue;
96
122
  }
97
123
  expanded += cluster;
98
- column += graphemeWidth(cluster);
124
+ column += terminalGraphemeWidth(cluster);
99
125
  }
100
126
  return expanded;
101
127
  }
@@ -109,11 +135,11 @@ function hardWrappedRows(word, width, startColumn) {
109
135
  column: occupied % width || width,
110
136
  };
111
137
  }
112
- const clusters = graphemes(word);
138
+ const clusters = terminalGraphemes(word);
113
139
  let rows = 0;
114
140
  let column = startColumn;
115
141
  for (const [index, cluster] of clusters.entries()) {
116
- const clusterWidth = graphemeWidth(cluster);
142
+ const clusterWidth = terminalGraphemeWidth(cluster);
117
143
  if (column + clusterWidth > width) {
118
144
  rows += 1;
119
145
  column = clusterWidth;
@@ -155,9 +181,9 @@ function wrappedLineLayout(line, width, output) {
155
181
  column = wrapped.column;
156
182
  return;
157
183
  }
158
- const clusters = graphemes(word);
184
+ const clusters = terminalGraphemes(word);
159
185
  for (const [index, cluster] of clusters.entries()) {
160
- const clusterWidth = graphemeWidth(cluster);
186
+ const clusterWidth = terminalGraphemeWidth(cluster);
161
187
  if (column + clusterWidth > width)
162
188
  pushRow();
163
189
  row += cluster;
@@ -170,7 +196,7 @@ function wrappedLineLayout(line, width, output) {
170
196
  const word = words[index] ?? '';
171
197
  const wordWidth = isPrintableAscii(word)
172
198
  ? word.length
173
- : terminalStringWidth(word);
199
+ : terminalTextWidth(word);
174
200
  if (index > 0) {
175
201
  if (column >= width)
176
202
  pushRow();
@@ -271,14 +297,63 @@ function inputString(entry, key) {
271
297
  const value = entry.item.call.input[key];
272
298
  return typeof value === 'string' ? value : '';
273
299
  }
300
+ function oneLine(value, max = 160) {
301
+ const text = stripVTControlCharacters(value)
302
+ .replace(/[\r\n\t]+/gu, ' ')
303
+ .replace(/\s+/gu, ' ')
304
+ .trim();
305
+ return text.length > max ? `${text.slice(0, Math.max(1, max - 1))}…` : text;
306
+ }
274
307
  function toolHeading(entry) {
275
308
  if (entry.item.call.name === 'Edit')
276
- return `Update(${inputString(entry, 'file_path')})`;
309
+ return `Edit ${oneLine(inputString(entry, 'file_path'))}`.trimEnd();
277
310
  if (entry.item.call.name === 'Read')
278
- return `Read(${inputString(entry, 'file_path')})`;
311
+ return `Read ${oneLine(inputString(entry, 'file_path'))}`.trimEnd();
279
312
  if (entry.item.call.name === 'Bash')
280
- return `Bash(${inputString(entry, 'command')})`;
281
- return entry.item.call.name;
313
+ return `Bash ${oneLine(inputString(entry, 'command'))}`.trimEnd();
314
+ return oneLine(entry.item.call.name);
315
+ }
316
+ function toolSummary(entry, mode) {
317
+ const state = !entry.result
318
+ ? 'running'
319
+ : entry.result.isError
320
+ ? 'failed'
321
+ : 'completed';
322
+ const prefix = mode === 'screen-reader'
323
+ ? state === 'running'
324
+ ? 'Running tool: '
325
+ : state === 'failed'
326
+ ? 'Failed tool: '
327
+ : 'Completed tool: '
328
+ : state === 'running'
329
+ ? '… '
330
+ : state === 'failed'
331
+ ? '! '
332
+ : '✓ ';
333
+ const detail = !['Bash', 'Read', 'Edit'].includes(entry.item.call.name) &&
334
+ entry.item.detail
335
+ ? ` · ${oneLine(entry.item.detail, 120)}`
336
+ : '';
337
+ return `${prefix}${toolHeading(entry)}${detail}`;
338
+ }
339
+ function shellSummary(entry, mode) {
340
+ const state = !entry.result
341
+ ? 'running'
342
+ : entry.result.isError
343
+ ? 'failed'
344
+ : 'completed';
345
+ const prefix = mode === 'screen-reader'
346
+ ? state === 'running'
347
+ ? 'Running shell command: '
348
+ : state === 'failed'
349
+ ? 'Failed shell command: '
350
+ : 'Completed shell command: '
351
+ : state === 'running'
352
+ ? '… Bash '
353
+ : state === 'failed'
354
+ ? '! Bash '
355
+ : '✓ Bash ';
356
+ return `${prefix}${oneLine(entry.item.command)}`.trimEnd();
282
357
  }
283
358
  function visibleOutputLineCount(text, width, mode) {
284
359
  const lines = contentLines(text);
@@ -319,7 +394,7 @@ function textTail(text, budget, width) {
319
394
  }
320
395
  const remainingRows = usableBudget - rowsAfter;
321
396
  if (remainingRows > 0) {
322
- const clusters = graphemes(line);
397
+ const clusters = terminalGraphemes(line);
323
398
  let low = 0;
324
399
  let high = clusters.length;
325
400
  let suffix = '';
@@ -336,7 +411,7 @@ function textTail(text, budget, width) {
336
411
  }
337
412
  while (suffix &&
338
413
  wrappedLineCount(`${TRANSCRIPT_TRUNCATION_MARKER}${suffix}`, usableWidth) > remainingRows) {
339
- const suffixClusters = graphemes(suffix);
414
+ const suffixClusters = terminalGraphemes(suffix);
340
415
  suffix = suffixClusters.slice(0, -1).join('');
341
416
  }
342
417
  kept.unshift(suffix);
@@ -364,7 +439,7 @@ function textHead(text, budget, width) {
364
439
  return candidate;
365
440
  const remaining = Math.max(0, usableBudget - wrappedLineCount(marker, usableWidth));
366
441
  const prefix = kept.join('\n');
367
- const clusters = graphemes(prefix);
442
+ const clusters = terminalGraphemes(prefix);
368
443
  let low = 0;
369
444
  let high = clusters.length;
370
445
  let best = '';
@@ -416,19 +491,17 @@ function textRange(text, startRow, budget, width) {
416
491
  }
417
492
  function estimateRenderedTranscriptEntryLines(entry, width, mode) {
418
493
  if (entry.kind === 'read-summary')
419
- return wrappedLineCount(` Read ${entry.count} file${entry.count === 1 ? '' : 's'} (ctrl+o to expand)`, width);
494
+ return wrappedLineCount(`✓ Read ${entry.count} file${entry.count === 1 ? '' : 's'}`, width);
420
495
  if (entry.kind === 'item') {
421
496
  const item = entry.item;
422
497
  if (item.kind === 'user')
423
498
  return (1 +
424
499
  (mode === 'screen-reader' ? 1 : 0) +
425
- wrappedLineCount(`${mode === 'screen-reader' ? 'You: ' : '❯ '}${item.text}`, width));
500
+ wrappedLineCount(`${mode === 'screen-reader' ? 'You: ' : 'you> '}${item.text}`, width));
426
501
  if (item.kind === 'assistant')
427
502
  return (1 +
428
503
  (mode === 'screen-reader' ? 1 : 0) +
429
- wrappedLineCount(item.text, mode === 'screen-reader'
430
- ? Math.max(1, width - 7)
431
- : markdownContentWidth(width)));
504
+ assistantMarkdownProjectionRows(item.text, width, mode).length);
432
505
  if (item.kind === 'thinking') {
433
506
  const summary = item.text.replace(/\s+/gu, ' ').trim();
434
507
  if (mode === 'normal')
@@ -473,15 +546,14 @@ function estimateRenderedTranscriptEntryLines(entry, width, mode) {
473
546
  if (entry.kind === 'orphan-shell-result')
474
547
  return wrappedLineCount(`⎿ ${orphanShellOutput(entry.item.stdout, entry.item.stderr)}`, width);
475
548
  if (entry.kind === 'tool') {
476
- if (mode === 'normal' &&
477
- entry.item.call.name === 'Read' &&
478
- entry.result &&
479
- !entry.result.isError)
480
- return 1;
481
- let rows = 1 + wrappedLineCount(`⏺ ${toolHeading(entry)}`, width);
482
- if (!['Bash', 'Read', 'Edit'].includes(entry.item.call.name) &&
483
- entry.item.detail)
484
- rows += wrappedLineCount(` ${entry.item.detail}`, width);
549
+ const summaryRows = wrappedLineCount(toolSummary(entry, mode), width);
550
+ if (mode === 'normal') {
551
+ if (!entry.result || !entry.result.isError)
552
+ return summaryRows;
553
+ return (summaryRows +
554
+ viewportOutputRows(`Error: ${bounded(entry.result.text, 500)}`, width, mode).length);
555
+ }
556
+ let rows = 1 + summaryRows;
485
557
  if (!entry.result)
486
558
  return rows;
487
559
  if (entry.result.isError)
@@ -491,18 +563,25 @@ function estimateRenderedTranscriptEntryLines(entry, width, mode) {
491
563
  const newLines = contentLines(inputString(entry, 'new_string'));
492
564
  const oldLines = contentLines(inputString(entry, 'old_string'));
493
565
  rows += wrappedLineCount(`⎿ Added ${newLines.length} line${newLines.length === 1 ? '' : 's'}, removed ${oldLines.length} line${oldLines.length === 1 ? '' : 's'}`, Math.max(1, width - 2));
494
- if (mode !== 'normal') {
495
- rows += oldLines.reduce((total, line, index) => total +
496
- wrappedLineCount(` ${index + 1} -${line}`, Math.max(1, width - 2)), 0);
497
- rows += newLines.reduce((total, line, index) => total +
498
- wrappedLineCount(` ${index + 1} +${line}`, Math.max(1, width - 2)), 0);
499
- }
566
+ rows += oldLines.reduce((total, line, index) => total +
567
+ wrappedLineCount(` ${index + 1} -${line}`, Math.max(1, width - 2)), 0);
568
+ rows += newLines.reduce((total, line, index) => total +
569
+ wrappedLineCount(` ${index + 1} +${line}`, Math.max(1, width - 2)), 0);
500
570
  return rows;
501
571
  }
502
572
  return rows + visibleOutputLineCount(entry.result.text, width, mode);
503
573
  }
504
- let rows = 1 +
505
- wrappedLineCount(`${mode === 'screen-reader' ? 'Shell command: ' : '! '}${entry.item.command}`, width);
574
+ const summaryRows = wrappedLineCount(shellSummary(entry, mode), width);
575
+ if (mode === 'normal') {
576
+ if (!entry.result || !entry.result.isError)
577
+ return summaryRows;
578
+ const output = combinedShellOutput(entry.result.stdout, entry.result.stderr);
579
+ return (summaryRows +
580
+ (output
581
+ ? viewportOutputRows(output, width, mode).length
582
+ : wrappedLineCount('⎿ ', Math.max(1, width - 2))));
583
+ }
584
+ let rows = 1 + summaryRows;
506
585
  if (!entry.result)
507
586
  return rows;
508
587
  const output = combinedShellOutput(entry.result.stdout, entry.result.stderr);
@@ -528,18 +607,58 @@ export function transcriptPresentationLineCount(entries, width, mode) {
528
607
  return entries.reduce((n, entry) => n + estimateTranscriptEntryLines(entry, width, mode), 0);
529
608
  }
530
609
  function viewportOutputRows(text, width, mode) {
531
- const lines = contentLines(text);
532
- const visible = mode === 'normal' ? lines.slice(0, 3) : lines;
533
610
  const outputWidth = Math.max(1, width - 2);
534
- const rows = visible.flatMap((line, index) => textVisualRows(`${index === 0 ? '⎿ ' : ' '}${line || ' '}`, outputWidth));
535
- if (mode === 'normal' && lines.length > visible.length) {
536
- rows.push(...textVisualRows(` ${TRANSCRIPT_TRUNCATION_MARKER} +${lines.length - visible.length} lines (ctrl+o to expand)`, outputWidth));
611
+ if (mode !== 'normal') {
612
+ const lines = contentLines(text);
613
+ return lines.flatMap((line, index) => textVisualRows(`${index === 0 ? '⎿ ' : ' '}${line || ' '}`, outputWidth));
537
614
  }
538
- return rows;
615
+ const rows = [];
616
+ const rowBudget = 8;
617
+ const lines = [];
618
+ let lineStart = 0;
619
+ let omittedLines = 0;
620
+ for (let index = 0; index <= text.length && lines.length < 3; index += 1) {
621
+ if (index !== text.length && text[index] !== '\n')
622
+ continue;
623
+ const line = text.slice(lineStart, index).replace(/\r$/u, '');
624
+ if (!(index === text.length && line === '' && text.endsWith('\n')))
625
+ lines.push(line);
626
+ lineStart = index + 1;
627
+ }
628
+ for (let index = lineStart; index < text.length; index += 1)
629
+ if (text[index] === '\n')
630
+ omittedLines += 1;
631
+ if (lineStart < text.length && !text.endsWith('\n'))
632
+ omittedLines += 1;
633
+ let hidden = omittedLines > 0;
634
+ for (const [index, line] of lines.entries()) {
635
+ const prefix = index === 0 ? '⎿ ' : ' ';
636
+ const remaining = rowBudget - rows.length;
637
+ if (remaining <= 1) {
638
+ hidden = true;
639
+ break;
640
+ }
641
+ const candidate = terminalTextHead(line || ' ', outputWidth * (remaining - 1));
642
+ if (candidate.length < (line || ' ').length)
643
+ hidden = true;
644
+ rows.push(...textVisualRows(`${prefix}${candidate}`, outputWidth).slice(0, remaining - 1));
645
+ if (rows.length >= rowBudget - 1)
646
+ break;
647
+ }
648
+ if (hidden) {
649
+ const marker = textVisualRows(omittedLines > 0
650
+ ? ` ${TRANSCRIPT_TRUNCATION_MARKER} +${omittedLines} lines (ctrl+o to expand)`
651
+ : ` ${TRANSCRIPT_TRUNCATION_MARKER}`, outputWidth);
652
+ if (marker.length <= rowBudget - rows.length)
653
+ rows.push(...marker.slice(0, rowBudget - rows.length));
654
+ else
655
+ rows.push(TRANSCRIPT_TRUNCATION_MARKER);
656
+ }
657
+ return rows.slice(0, rowBudget);
539
658
  }
540
659
  function entryViewportRows(entry, width, mode) {
541
660
  if (entry.kind === 'read-summary')
542
- return textVisualRows(` Read ${entry.count} file${entry.count === 1 ? '' : 's'} (ctrl+o to expand)`, width);
661
+ return textVisualRows(`✓ Read ${entry.count} file${entry.count === 1 ? '' : 's'}`, width);
543
662
  if (entry.kind === 'item') {
544
663
  const item = entry.item;
545
664
  if (item.kind === 'context')
@@ -548,15 +667,13 @@ function entryViewportRows(entry, width, mode) {
548
667
  return [
549
668
  '',
550
669
  ...(mode === 'screen-reader' ? [''] : []),
551
- ...textVisualRows(`${mode === 'screen-reader' ? 'You: ' : '❯ '}${item.text}`, width),
670
+ ...textVisualRows(`${mode === 'screen-reader' ? 'You: ' : 'you> '}${item.text}`, width),
552
671
  ];
553
672
  if (item.kind === 'assistant')
554
673
  return [
555
674
  '',
556
675
  ...(mode === 'screen-reader' ? ['Praxis:'] : []),
557
- ...textVisualRows(item.text, mode === 'screen-reader'
558
- ? Math.max(1, width - 7)
559
- : markdownContentWidth(width)),
676
+ ...assistantMarkdownProjectionRows(item.text, width, mode).map((row) => row.text),
560
677
  ];
561
678
  if (item.kind === 'thinking') {
562
679
  const summary = item.text.replace(/\s+/gu, ' ').trim();
@@ -605,10 +722,19 @@ function entryViewportRows(entry, width, mode) {
605
722
  if (entry.kind === 'orphan-shell-result')
606
723
  return textVisualRows(`⎿ ${orphanShellOutput(entry.item.stdout, entry.item.stderr)}`, width);
607
724
  if (entry.kind === 'shell') {
608
- const rows = [
609
- '',
610
- ...textVisualRows(`${mode === 'screen-reader' ? 'Shell command: ' : '! '}${entry.item.command}`, width),
611
- ];
725
+ const summaryRows = textVisualRows(shellSummary(entry, mode), width);
726
+ if (mode === 'normal') {
727
+ if (!entry.result || !entry.result.isError)
728
+ return summaryRows;
729
+ const output = combinedShellOutput(entry.result.stdout, entry.result.stderr);
730
+ return [
731
+ ...summaryRows,
732
+ ...(output
733
+ ? viewportOutputRows(output, width, mode)
734
+ : textVisualRows('⎿ ', Math.max(1, width - 2))),
735
+ ];
736
+ }
737
+ const rows = ['', ...summaryRows];
612
738
  if (!entry.result)
613
739
  return rows;
614
740
  const output = combinedShellOutput(entry.result.stdout, entry.result.stderr);
@@ -619,15 +745,16 @@ function entryViewportRows(entry, width, mode) {
619
745
  : textVisualRows('⎿ ', Math.max(1, width - 2))),
620
746
  ];
621
747
  }
622
- if (mode === 'normal' &&
623
- entry.item.call.name === 'Read' &&
624
- entry.result &&
625
- !entry.result.isError)
626
- return textVisualRows(' Read 1 file (ctrl+o to expand)', width);
627
- const rows = ['', ...textVisualRows(`⏺ ${toolHeading(entry)}`, width)];
628
- if (!['Bash', 'Read', 'Edit'].includes(entry.item.call.name) &&
629
- entry.item.detail)
630
- rows.push(...textVisualRows(` ${entry.item.detail}`, width));
748
+ const summaryRows = textVisualRows(toolSummary(entry, mode), width);
749
+ if (mode === 'normal') {
750
+ if (!entry.result || !entry.result.isError)
751
+ return summaryRows;
752
+ return [
753
+ ...summaryRows,
754
+ ...viewportOutputRows(`Error: ${bounded(entry.result.text, 500)}`, width, mode),
755
+ ];
756
+ }
757
+ const rows = ['', ...summaryRows];
631
758
  if (!entry.result)
632
759
  return rows;
633
760
  if (entry.result.isError) {
@@ -638,12 +765,10 @@ function entryViewportRows(entry, width, mode) {
638
765
  const oldLines = contentLines(inputString(entry, 'old_string'));
639
766
  const newLines = contentLines(inputString(entry, 'new_string'));
640
767
  rows.push(...textVisualRows(`⎿ Added ${newLines.length} line${newLines.length === 1 ? '' : 's'}, removed ${oldLines.length} line${oldLines.length === 1 ? '' : 's'}`, Math.max(1, width - 2)));
641
- if (mode !== 'normal') {
642
- for (const [index, line] of oldLines.entries())
643
- rows.push(...textVisualRows(` ${index + 1} -${line}`, Math.max(1, width - 2)));
644
- for (const [index, line] of newLines.entries())
645
- rows.push(...textVisualRows(` ${index + 1} +${line}`, Math.max(1, width - 2)));
646
- }
768
+ for (const [index, line] of oldLines.entries())
769
+ rows.push(...textVisualRows(` ${index + 1} -${line}`, Math.max(1, width - 2)));
770
+ for (const [index, line] of newLines.entries())
771
+ rows.push(...textVisualRows(` ${index + 1} +${line}`, Math.max(1, width - 2)));
647
772
  return rows;
648
773
  }
649
774
  rows.push(...viewportOutputRows(entry.result.text, width, mode));
@@ -680,12 +805,25 @@ function markdownViewportRows(text, width) {
680
805
  }
681
806
  return rows;
682
807
  }
808
+ function assistantMarkdownProjectionRows(text, width, mode) {
809
+ const contentWidth = mode === 'screen-reader'
810
+ ? Math.max(1, width - 7)
811
+ : markdownContentWidth(width);
812
+ const rows = markdownViewportRows(text, contentWidth);
813
+ if (mode === 'screen-reader')
814
+ return rows;
815
+ const first = rows[0] ?? {
816
+ text: '',
817
+ fenceBefore: false,
818
+ fenceAfter: false,
819
+ fenceLabel: 'code',
820
+ };
821
+ const prefixed = textVisualRows(`praxis> ${first.text.trimEnd()}`, width).map((rowText) => ({ ...first, text: rowText }));
822
+ return [...prefixed, ...rows.slice(1)];
823
+ }
683
824
  export function createTranscriptEntryViewportIndex(entry, width, mode) {
684
825
  if (entry.kind === 'item' && entry.item.kind === 'assistant') {
685
- const contentWidth = mode === 'screen-reader'
686
- ? Math.max(1, width - 7)
687
- : markdownContentWidth(width);
688
- const assistantMarkdownRows = markdownViewportRows(entry.item.text, contentWidth);
826
+ const assistantMarkdownRows = assistantMarkdownProjectionRows(entry.item.text, width, mode);
689
827
  return {
690
828
  entry,
691
829
  width,
@@ -712,12 +850,9 @@ function projectAssistantMarkdownSlice(source, sourceText, startRow, budget, wid
712
850
  const marginTop = startRow <= 0 ? 1 : 0;
713
851
  const projectedChromeRows = marginTop + (mode === 'screen-reader' ? 1 : 0);
714
852
  const contentBudget = Math.max(1, limit - projectedChromeRows);
715
- const contentWidth = mode === 'screen-reader'
716
- ? Math.max(1, width - 7)
717
- : markdownContentWidth(width);
718
853
  const sourceRows = matchingViewportIndex(viewportIndex, source, width, mode)
719
854
  ?.assistantMarkdownRows ??
720
- markdownViewportRows(sourceText ?? '', contentWidth);
855
+ assistantMarkdownProjectionRows(sourceText ?? '', width, mode);
721
856
  const contentStart = Math.min(Math.max(0, startRow - sourceChromeRows + (startRow > 0 ? 1 : 0)), sourceRows.length);
722
857
  const clippedBefore = contentStart > 0 && contentBudget > 1;
723
858
  let remaining = Math.max(1, contentBudget - (clippedBefore ? 1 : 0));
@@ -15,7 +15,7 @@ function entryText(entry) {
15
15
  if (entry.kind === 'read-summary')
16
16
  return {
17
17
  text: `Read ${entry.count} file${entry.count === 1 ? '' : 's'}`,
18
- role: 'muted',
18
+ role: 'success',
19
19
  };
20
20
  if (entry.kind === 'item') {
21
21
  const item = entry.item;
@@ -28,7 +28,7 @@ function entryText(entry) {
28
28
  if (item.kind === 'notice')
29
29
  return { text: `· ${item.text}`, role: 'muted' };
30
30
  if (item.kind === 'warning')
31
- return { text: `⚠ ${item.text}`, role: 'error' };
31
+ return { text: `⚠ ${item.text}`, role: 'warning' };
32
32
  if (item.kind === 'local-result')
33
33
  return { text: `⎿ ${item.text}`, role: 'muted' };
34
34
  if (item.kind === 'compact')
@@ -51,7 +51,7 @@ function entryText(entry) {
51
51
  : '';
52
52
  return {
53
53
  text: `⏺ ${toolHeading(entry.item)}${entry.item.detail ? `\n ${entry.item.detail}` : ''}${output}`,
54
- role: result?.isError ? 'error' : 'tool',
54
+ role: result ? (result.isError ? 'error' : 'success') : 'muted',
55
55
  };
56
56
  }
57
57
  if (entry.kind === 'shell') {
@@ -61,7 +61,7 @@ function entryText(entry) {
61
61
  : '';
62
62
  return {
63
63
  text: `! ${entry.item.command}${output}`,
64
- role: result?.isError ? 'error' : 'tool',
64
+ role: result ? (result.isError ? 'error' : 'success') : 'muted',
65
65
  };
66
66
  }
67
67
  if (entry.kind === 'orphan-tool-result')
@@ -23,10 +23,10 @@ export async function promptWorkspaceTrust(inventory, io = {}) {
23
23
  const output = io.output ?? ((text) => process.stderr.write(text));
24
24
  if (io.signal?.aborted)
25
25
  return false;
26
- output(`Workspace executable resources found for ${safeWorkspaceTrustDisplayField(inventory.canonicalPath)}\n`);
26
+ output(`Workspace-controlled resources/configuration found for ${safeWorkspaceTrustDisplayField(inventory.canonicalPath)}\n`);
27
27
  for (const origin of inventory.origins)
28
28
  output(` ${origin.kind} (${origin.scope}) ${safeWorkspaceTrustDisplayField(origin.path)}: ${safeWorkspaceTrustDisplayField(origin.label)}\n`);
29
- output('Trust these workspace executables? [y/N] ');
29
+ output('Trust these workspace-controlled resources/configuration? [y/N] ');
30
30
  const input = io.input ?? process.stdin;
31
31
  const iterator = input[Symbol.asyncIterator]();
32
32
  const abortedResult = Symbol('workspace-trust-prompt-aborted');
@@ -11,7 +11,7 @@ import type { TuiSlashCommand } from './cli/tui/slash-commands.js';
11
11
  import { type TuiHookConfiguration } from './cli/tui/hook-settings.js';
12
12
  import { type PraxisRuntimeSettings } from './cli/tui/runtime-settings.js';
13
13
  import { type ClaudePermissionMode } from './permissions/claude-permission-resolver.js';
14
- import { type WorkspaceTrustInventory } from './security/workspace-trust.js';
14
+ import { type WorkspaceTrustAssessment, type WorkspaceTrustInventory } from './security/workspace-trust.js';
15
15
  import { type ClaudeMcpServerStatus, type ClaudeMcpToolInspection } from './mcp/claude-mcp-tools.js';
16
16
  import { authenticateMcpServer } from './mcp/claude-mcp-oauth.js';
17
17
  import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
@@ -20,6 +20,7 @@ import { type ClaudeInteractiveToolCallbacks } from './tools/claude-interactive-
20
20
  import { type TopLevelAgentSummary } from './application/top-level-agent-manager.js';
21
21
  import { launchTmuxWorktree } from './platform/tmux-worktree.js';
22
22
  import { type CliControls, type CliRuntimeInfo, type CliElicitationRequest, type CliElicitationResult } from './cli/protocol.js';
23
+ import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
23
24
  import { type PluginEvalDependencies } from './plugins/claude-plugin-eval.js';
24
25
  import { type SelfUpdateResult } from './maintenance/self-update.js';
25
26
  export { parseContextEnvironment, parseProviderEnvironment };
@@ -28,6 +29,7 @@ export interface CliIO {
28
29
  stderr(message: string): void;
29
30
  isTTY?: boolean;
30
31
  readStdinLines?: () => AsyncIterable<string | Uint8Array>;
32
+ readSecret?: (prompt: string, signal?: AbortSignal) => Promise<string>;
31
33
  }
32
34
  interface SessionCommands {
33
35
  teamLeadOperations?: TeamLeadOperations;
@@ -143,6 +145,8 @@ export interface CliDependencies extends InteractiveServiceFactory {
143
145
  }): Promise<SessionCommands>;
144
146
  createAutoModeCritic?(options: {
145
147
  model?: string;
148
+ provider?: string;
149
+ providerProfile?: string;
146
150
  dataPlane?: DataPlane;
147
151
  configRoot?: string;
148
152
  statePath?: string;
@@ -170,6 +174,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
170
174
  launchTmux?: typeof launchTmuxWorktree;
171
175
  mcpAuthenticate?: typeof authenticateMcpServer;
172
176
  mcpServe?: typeof servePraxisMcpStdio;
177
+ executeProviderAuthCommand?: typeof executeProviderAuthCommand;
173
178
  selfUpdate?: (options: {
174
179
  operation: 'install' | 'update';
175
180
  target?: string;
@@ -177,6 +182,20 @@ export interface CliDependencies extends InteractiveServiceFactory {
177
182
  signal?: AbortSignal;
178
183
  }) => Promise<SelfUpdateResult>;
179
184
  }
185
+ interface ConsoleSecretInput extends AsyncIterable<string | Uint8Array> {
186
+ readonly isTTY?: boolean;
187
+ readonly isRaw?: boolean;
188
+ isPaused(): boolean;
189
+ setRawMode?(enabled: boolean): unknown;
190
+ pause(): unknown;
191
+ resume(): unknown;
192
+ on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
193
+ removeListener(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
194
+ }
195
+ interface ConsoleSecretOutput {
196
+ write(message: string): unknown;
197
+ }
198
+ export declare function readConsoleSecret(prompt: string, signal?: AbortSignal, input?: ConsoleSecretInput, output?: ConsoleSecretOutput): Promise<string>;
180
199
  /**
181
200
  * Shared runtime model precedence used by every consumer (provider
182
201
  * construction, status/doctor output, and the interactive display):
@@ -194,9 +213,23 @@ export declare function resolveInteractiveRuntimeSettingsLocation(dataPlane: Dat
194
213
  statePath: string;
195
214
  };
196
215
  export declare function resolveUnknownCostSidecarPath(dataPlane: DataPlane, configRoot: string): string;
216
+ export declare function resolveInteractiveProviderStartup(options: {
217
+ controls: CliControls;
218
+ configRoot: string;
219
+ statePath: string;
220
+ cwd: string;
221
+ environment?: NodeJS.ProcessEnv;
222
+ approveWorkspaceTrust?: (assessment: WorkspaceTrustAssessment) => boolean | Promise<boolean>;
223
+ }): Promise<{
224
+ effectiveModel: string | undefined;
225
+ trustProjectRequestAvailable: boolean;
226
+ }>;
197
227
  export declare function createDefaultDependencies(entrypoint?: string): CliDependencies;
198
228
  export declare function createBackgroundWorkerRuntime(workerSink: RuntimeEventSink, dispatch: {
199
229
  argv: string[];
200
230
  }, createService?: CliDependencies['createService']): Promise<Awaited<ReturnType<CliDependencies['createService']>>>;
231
+ export declare function agentDashboardWorkerArgv(invocation: CliControls & {
232
+ agent: string | undefined;
233
+ }): string[];
201
234
  export declare function run(argv: readonly string[], io?: CliIO, dependencies?: CliDependencies, signal?: AbortSignal): Promise<number>;
202
235
  //# sourceMappingURL=cli-runtime.d.ts.map