dsh-ssh-tui 0.8.1 → 0.8.2

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 (93) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.en.md +118 -15
  3. package/README.md +78 -13
  4. package/docs/release-notes-0.7.4.md +91 -0
  5. package/docs/release-notes-0.8.0.md +56 -0
  6. package/docs/release-notes-0.8.1.md +79 -0
  7. package/docs/release-notes-0.8.2-rc.1.md +159 -0
  8. package/docs/release-notes-0.8.2.md +62 -0
  9. package/docs/screenshots/reconnect.gif +0 -0
  10. package/docs/terminals.md +23 -0
  11. package/docs/windows.md +13 -0
  12. package/lib/approval-state.js +148 -0
  13. package/lib/approval-state.js.map +1 -0
  14. package/lib/auth-failure.js +38 -0
  15. package/lib/auth-failure.js.map +1 -1
  16. package/lib/color-depth.js +26 -0
  17. package/lib/color-depth.js.map +1 -1
  18. package/lib/copy-text.js +30 -8
  19. package/lib/copy-text.js.map +1 -1
  20. package/lib/dialogs.js +81 -0
  21. package/lib/dialogs.js.map +1 -1
  22. package/lib/display-sock.js +138 -23
  23. package/lib/display-sock.js.map +1 -1
  24. package/lib/footer-accents.js +28 -0
  25. package/lib/footer-accents.js.map +1 -0
  26. package/lib/footer-budget.js +328 -0
  27. package/lib/footer-budget.js.map +1 -0
  28. package/lib/footer-format.js +433 -0
  29. package/lib/footer-format.js.map +1 -0
  30. package/lib/footer-layout.js +407 -0
  31. package/lib/footer-layout.js.map +1 -0
  32. package/lib/footer-meters.js +185 -0
  33. package/lib/footer-meters.js.map +1 -0
  34. package/lib/footer.js +19 -558
  35. package/lib/footer.js.map +1 -1
  36. package/lib/i18n/en.js +65 -5
  37. package/lib/i18n/en.js.map +1 -1
  38. package/lib/i18n/zh.js +67 -5
  39. package/lib/i18n/zh.js.map +1 -1
  40. package/lib/paint.js +53 -8
  41. package/lib/paint.js.map +1 -1
  42. package/lib/plan-projection.js +291 -0
  43. package/lib/plan-projection.js.map +1 -0
  44. package/lib/plan.js +50 -0
  45. package/lib/plan.js.map +1 -1
  46. package/lib/question-state.js +83 -0
  47. package/lib/question-state.js.map +1 -0
  48. package/lib/representation.js +283 -0
  49. package/lib/representation.js.map +1 -0
  50. package/lib/screen.js +105 -0
  51. package/lib/screen.js.map +1 -0
  52. package/lib/selection.js +5 -3
  53. package/lib/selection.js.map +1 -1
  54. package/lib/startup.js +49 -0
  55. package/lib/startup.js.map +1 -1
  56. package/lib/stats.js +101 -3
  57. package/lib/stats.js.map +1 -1
  58. package/lib/term-text.js +154 -55
  59. package/lib/term-text.js.map +1 -1
  60. package/lib/theme.js +20 -3
  61. package/lib/theme.js.map +1 -1
  62. package/lib/throughput.js +305 -0
  63. package/lib/throughput.js.map +1 -0
  64. package/lib/tool-present.js +57 -7
  65. package/lib/tool-present.js.map +1 -1
  66. package/lib/tui.js +3776 -1105
  67. package/lib/tui.js.map +1 -1
  68. package/lib/types/approval-state.d.ts +75 -0
  69. package/lib/types/auth-failure.d.ts +25 -0
  70. package/lib/types/color-depth.d.ts +21 -0
  71. package/lib/types/copy-text.d.ts +18 -1
  72. package/lib/types/dialogs.d.ts +111 -1
  73. package/lib/types/display-sock.d.ts +15 -0
  74. package/lib/types/footer-accents.d.ts +22 -0
  75. package/lib/types/footer-budget.d.ts +204 -0
  76. package/lib/types/footer-format.d.ts +221 -0
  77. package/lib/types/footer-layout.d.ts +292 -0
  78. package/lib/types/footer-meters.d.ts +151 -0
  79. package/lib/types/footer.d.ts +19 -269
  80. package/lib/types/paint.d.ts +48 -0
  81. package/lib/types/plan-projection.d.ts +162 -0
  82. package/lib/types/plan.d.ts +13 -0
  83. package/lib/types/question-state.d.ts +96 -0
  84. package/lib/types/representation.d.ts +498 -0
  85. package/lib/types/screen.d.ts +126 -0
  86. package/lib/types/startup.d.ts +24 -0
  87. package/lib/types/stats.d.ts +64 -1
  88. package/lib/types/term-text.d.ts +45 -1
  89. package/lib/types/throughput.d.ts +163 -0
  90. package/lib/types/tool-present.d.ts +41 -0
  91. package/lib/types/transcript-types.d.ts +63 -2
  92. package/lib/types/tui.d.ts +862 -11
  93. package/package.json +16 -6
package/lib/theme.js CHANGED
@@ -11,7 +11,12 @@ const DEFAULT_THEME = {
11
11
  tokens: {
12
12
  user: '36',
13
13
  assistant: '',
14
- reasoning: '2;3',
14
+ // `2` (faint) is not implemented everywhere — a 256-colour terminal that
15
+ // ignores it draws the reasoning rows in the *default* foreground, i.e. as
16
+ // bright as the reply beside them, which is how the card was reported as
17
+ // white. The colour is what carries "muted"; the attributes only sharpen it
18
+ // where the terminal honours them.
19
+ reasoning: '2;3;90',
15
20
  brand: '1;38;2;77;107;253',
16
21
  tool: '',
17
22
  'tool-result': '',
@@ -24,6 +29,9 @@ const DEFAULT_THEME = {
24
29
  'todo-skipped': '2;33',
25
30
  'todo-pending': '90',
26
31
  'plan-dock': '38;5;180',
32
+ 'setup-step': '1;36',
33
+ 'setup-choice': '33',
34
+ 'setup-field': '1;37',
27
35
  'subagent-header': '38;5;141',
28
36
  diag: '2;36',
29
37
  error: '31',
@@ -61,7 +69,7 @@ const CATPPUCCIN_THEME = {
61
69
  tokens: {
62
70
  user: '38;2;137;180;250',
63
71
  assistant: '',
64
- reasoning: '2;3',
72
+ reasoning: '2;3;38;2;166;173;200',
65
73
  brand: '1;38;2;203;166;247',
66
74
  tool: '',
67
75
  'tool-result': '',
@@ -74,6 +82,9 @@ const CATPPUCCIN_THEME = {
74
82
  'todo-skipped': '2;38;2;249;226;175',
75
83
  'todo-pending': '38;2;108;112;134',
76
84
  'plan-dock': '38;2;250;179;135',
85
+ 'setup-step': '1;36',
86
+ 'setup-choice': '33',
87
+ 'setup-field': '1;37',
77
88
  'subagent-header': '38;2;203;166;247',
78
89
  diag: '2;38;2;148;226;213',
79
90
  error: '38;2;243;139;168',
@@ -111,7 +122,7 @@ const GRUVBOX_THEME = {
111
122
  tokens: {
112
123
  user: '38;2;131;165;152',
113
124
  assistant: '',
114
- reasoning: '2;3',
125
+ reasoning: '2;3;38;2;168;153;132',
115
126
  brand: '1;38;2;211;134;155',
116
127
  tool: '',
117
128
  'tool-result': '',
@@ -124,6 +135,9 @@ const GRUVBOX_THEME = {
124
135
  'todo-skipped': '2;38;2;250;189;47',
125
136
  'todo-pending': '38;2;146;131;116',
126
137
  'plan-dock': '38;2;254;128;25',
138
+ 'setup-step': '1;36',
139
+ 'setup-choice': '33',
140
+ 'setup-field': '1;37',
127
141
  'subagent-header': '38;2;211;134;155',
128
142
  diag: '2;38;2;142;192;124',
129
143
  error: '38;2;251;73;52',
@@ -182,6 +196,9 @@ const MONO_THEME = {
182
196
  'todo-skipped': '2;3',
183
197
  'todo-pending': '2',
184
198
  'plan-dock': '1',
199
+ 'setup-step': '1',
200
+ 'setup-choice': '4',
201
+ 'setup-field': '',
185
202
  'subagent-header': '4',
186
203
  diag: '2;3',
187
204
  error: '1;4',
package/lib/theme.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAmEA;;;;;;GAMG;AACH,MAAM,aAAa,GAAU;IAC3B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,CAAC;IACP,MAAM,EAAE;QACN,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,mBAAmB;QAC1B,IAAI,EAAE,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,gCAAgC;QAC5C,WAAW,EAAE,MAAM;QACnB,WAAW,EAAE,MAAM;QACnB,aAAa,EAAE,MAAM;QACrB,aAAa,EAAE,IAAI;QACnB,cAAc,EAAE,MAAM;QACtB,cAAc,EAAE,IAAI;QACpB,WAAW,EAAE,UAAU;QACvB,iBAAiB,EAAE,UAAU;QAC7B,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;KACb;IACD,QAAQ,EAAE;QACR,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,gCAAgC;KAC7C;IACD,KAAK,EAAE;QACL,eAAe,EAAE,UAAU;QAC3B,kBAAkB,EAAE,SAAS;QAC7B,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI;QACZ,SAAS,EAAE,MAAM;QACjB,WAAW,EAAE,MAAM;QACnB,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,MAAM;QACjB,UAAU,EAAE,MAAM;QAClB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,MAAM;QAClB,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,IAAI;QACpB,SAAS,EAAE,IAAI;KAChB;CACF,CAAA;AAED,gFAAgF;AAChF,MAAM,gBAAgB,GAAU;IAC9B,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,CAAC;IACP,MAAM,EAAE;QACN,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,gCAAgC;QAC5C,WAAW,EAAE,oBAAoB;QACjC,WAAW,EAAE,oBAAoB;QACjC,aAAa,EAAE,oBAAoB;QACnC,aAAa,EAAE,kBAAkB;QACjC,cAAc,EAAE,oBAAoB;QACpC,cAAc,EAAE,kBAAkB;QAClC,WAAW,EAAE,kBAAkB;QAC/B,iBAAiB,EAAE,kBAAkB;QACrC,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,kBAAkB;QACzB,MAAM,EAAE,kBAAkB;KAC3B;IACD,QAAQ,EAAE;QACR,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,iCAAiC;KAC9C;IACD,KAAK,EAAE;QACL,eAAe,EAAE,kBAAkB;QACnC,kBAAkB,EAAE,kBAAkB;QACtC,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,kBAAkB;QAC1B,MAAM,EAAE,kBAAkB;QAC1B,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,oBAAoB;QACjC,SAAS,EAAE,kBAAkB;QAC7B,SAAS,EAAE,oBAAoB;QAC/B,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,oBAAoB;QAC7B,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,kBAAkB;QAC7B,cAAc,EAAE,kBAAkB;QAClC,SAAS,EAAE,kBAAkB;KAC9B;CACF,CAAA;AAED,qEAAqE;AACrE,MAAM,aAAa,GAAU;IAC3B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,CAAC;IACP,MAAM,EAAE;QACN,IAAI,EAAE,kBAAkB;QACxB,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,8BAA8B;QAC1C,WAAW,EAAE,oBAAoB;QACjC,WAAW,EAAE,mBAAmB;QAChC,aAAa,EAAE,mBAAmB;QAClC,aAAa,EAAE,gBAAgB;QAC/B,cAAc,EAAE,mBAAmB;QACnC,cAAc,EAAE,kBAAkB;QAClC,WAAW,EAAE,iBAAiB;QAC9B,iBAAiB,EAAE,kBAAkB;QACrC,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,gBAAgB;QACvB,MAAM,EAAE,kBAAkB;KAC3B;IACD,QAAQ,EAAE;QACR,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,gCAAgC;KAC7C;IACD,KAAK,EAAE;QACL,eAAe,EAAE,kBAAkB;QACnC,kBAAkB,EAAE,kBAAkB;QACtC,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,iBAAiB;QACzB,MAAM,EAAE,kBAAkB;QAC1B,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,oBAAoB;QACjC,SAAS,EAAE,kBAAkB;QAC7B,SAAS,EAAE,oBAAoB;QAC/B,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,oBAAoB;QAC7B,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,kBAAkB;QAC7B,cAAc,EAAE,iBAAiB;QACjC,SAAS,EAAE,iBAAiB;KAC7B;CACF,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAU;IACxB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,CAAC;IACP,MAAM,EAAE;QACN,IAAI,EAAE,GAAG;QACT,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,GAAG;QACV,IAAI,EAAE,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,GAAG;QAChB,WAAW,EAAE,GAAG;QAChB,aAAa,EAAE,GAAG;QAClB,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,GAAG;QACnB,WAAW,EAAE,GAAG;QAChB,iBAAiB,EAAE,GAAG;QACtB,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,GAAG;KACZ;IACD,KAAK,EAAE;QACL,eAAe,EAAE,GAAG;QACpB,kBAAkB,EAAE,KAAK;QACzB,IAAI,EAAE,GAAG;QACT,IAAI,EAAE,GAAG;QACT,MAAM,EAAE,GAAG;QACX,MAAM,EAAE,KAAK;QACb,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;QAChB,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,GAAG;QACd,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,GAAG;QACZ,UAAU,EAAE,GAAG;QACf,SAAS,EAAE,GAAG;QACd,cAAc,EAAE,KAAK;QACrB,SAAS,EAAE,GAAG;KACf;CACF,CAAA;AAED,MAAM,CAAC,MAAM,MAAM,GAAqB,CAAC,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,UAAU,CAAC,CAAA;AAEpG,0DAA0D;AAC1D,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACtD,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;AACpD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,IAAwB;IACnD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,aAAa,CAAA;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,IAAI,YAAY,GAAU,aAAa,CAAA;AAEvC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAA;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,IAAiB;IACxD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAA;IACjC,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;AACzC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAY,EAAE,IAAiB;IAChE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAA;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAY,EAAE,IAAe;IAC3D,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;AACjE,CAAC;AAKD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAY,EAAE,KAAqB;IACrE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACzD,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAA;IACjF,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACnC,CAAC;AAED,6EAA6E;AAC7E,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,+CAA+C,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAY;IACzC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAA;IACpF,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAA;AACvD,CAAC"}
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Live decode throughput for the status row.
3
+ *
4
+ * The harness never publishes tokens per second. What it does publish, per
5
+ * step, is authoritative: `assistant/message` carries the step's output-token
6
+ * count and the packed stream's first-token time, so `decodeTokens / decodeMs`
7
+ * over settled steps is a real measurement. What it does *not* publish is any
8
+ * per-token count while a step is still streaming — adapters emit text deltas
9
+ * and only hand over `usage` as the stream closes (verified against
10
+ * `dsh-llm-deepseek` and `dsh-llm-pi-ai`).
11
+ *
12
+ * So there are exactly two honest numbers, and this module keeps them apart:
13
+ *
14
+ * 1. **Settled** — tokens and milliseconds of a completed step. Exact, and the
15
+ * only number that is ever comparable across steps or sessions.
16
+ * 2. **Live** — characters per second while a step streams, converted with a
17
+ * ratio calibrated from this session's own settled steps. It is an estimate,
18
+ * it is labelled with `~`, and it exists so a long reply shows *something*
19
+ * that moves instead of a number frozen at the previous step.
20
+ *
21
+ * There is deliberately no health grading here. Tokens per second is a property
22
+ * of the model, the provider, the reasoning effort and the prompt, not a signal
23
+ * that something is wrong: 20 tok/s from a reasoning model and 20 tok/s from a
24
+ * small fast one mean different things. The row paints it neutral.
25
+ *
26
+ * @module dsh-ssh-tui/throughput
27
+ */
28
+ /** How much of the recent past the live rate is measured over. */
29
+ export const THROUGHPUT_WINDOW_MS = 4_000;
30
+ /** Minimum span between the two samples a rate is taken from. */
31
+ export const THROUGHPUT_MIN_SPAN_MS = 400;
32
+ /** A live rate older than this describes a stream that stopped, not one running. */
33
+ export const THROUGHPUT_FRESH_MS = 2_500;
34
+ /** Samples kept in the window; the cadence is the paint interval, not per token. */
35
+ const MAX_SAMPLES = 64;
36
+ /**
37
+ * Tokens per character, for the two ends of the script spectrum.
38
+ *
39
+ * Latin text averages roughly a third of a token per character; CJK is close to
40
+ * one, because a Chinese character is usually its own token and most words are one
41
+ * or two characters. Between those ends the estimate is interpolated by the
42
+ * *measured* script mix of the stream being estimated, continuously: a threshold
43
+ * ("30% CJK means Chinese") would put a mostly-English reply with one Chinese
44
+ * paragraph on the wrong side of a cliff, and the number it produces is shown to
45
+ * the reader.
46
+ */
47
+ export const DEFAULT_TOKENS_PER_CHAR_LATIN = 0.32;
48
+ export const DEFAULT_TOKENS_PER_CHAR_CJK = 0.85;
49
+ /** The Latin end under its original name: the fallback for a stream with no text. */
50
+ export const DEFAULT_TOKENS_PER_CHAR = DEFAULT_TOKENS_PER_CHAR_LATIN;
51
+ /** Calibration bounds: below the first a tokenizer is lying, above the second too. */
52
+ const MIN_TOKENS_PER_CHAR = 0.1;
53
+ const MAX_TOKENS_PER_CHAR = 1.5;
54
+ /** Weight of the newest settled step in the calibration average. */
55
+ const CALIBRATION_WEIGHT = 0.5;
56
+ /** Rates kept for the median; three is what the link chip's RTT median uses. */
57
+ const RATE_HISTORY = 3;
58
+ /**
59
+ * Characters to tokens, bounded and finite.
60
+ *
61
+ * The default is an average over mixed prose and code; every settled step
62
+ * replaces half of it with this session's own measurement, which is what makes
63
+ * the live estimate usable on CJK output (where one character is close to one
64
+ * token) without hardcoding a language.
65
+ */
66
+ export function boundTokensPerChar(value, fallback = DEFAULT_TOKENS_PER_CHAR) {
67
+ if (!Number.isFinite(value) || value <= 0)
68
+ return fallback;
69
+ return Math.min(MAX_TOKENS_PER_CHAR, Math.max(MIN_TOKENS_PER_CHAR, value));
70
+ }
71
+ /**
72
+ * The median of up to the last three rates, or the newest one alone.
73
+ *
74
+ * A single frame in which the terminal coalesced a burst of tokens into one
75
+ * delta reads as a spike; a median of three ignores it, while a real change
76
+ * survives because two agreeing samples decide it. Same rule, and the same
77
+ * reasoning, as the link chip's RTT median.
78
+ */
79
+ export function medianRate(values) {
80
+ const recent = values.filter(value => Number.isFinite(value) && value > 0).slice(-RATE_HISTORY);
81
+ if (recent.length === 0)
82
+ return undefined;
83
+ const sorted = [...recent].sort((a, b) => a - b);
84
+ return sorted[Math.floor(sorted.length / 2)];
85
+ }
86
+ /**
87
+ * Tracks one step's live decode rate from character deltas.
88
+ *
89
+ * Sampling is the caller's job (`note` per fragment): the window's own
90
+ * minimum span is what keeps the number stable, so a burst of tiny deltas and
91
+ * one coalesced delta produce the same answer.
92
+ */
93
+ /**
94
+ * Whether one code point is written in a script whose characters are close to one
95
+ * token each: CJK ideographs, kana, Hangul syllables, and the fullwidth or CJK
96
+ * punctuation that travels with them.
97
+ *
98
+ * Deliberately a property of the *text* rather than of the reader's locale: a
99
+ * Chinese reader's English reply is Latin text and costs Latin tokens per
100
+ * character, and `~tok/s` has to describe the tokens, not the person.
101
+ */
102
+ export function isCjkCodePoint(codePoint) {
103
+ return (codePoint >= 0x3040 && codePoint <= 0x30ff) // hiragana + katakana
104
+ || (codePoint >= 0x3400 && codePoint <= 0x4dbf) // CJK extension A
105
+ || (codePoint >= 0x4e00 && codePoint <= 0x9fff) // CJK unified ideographs
106
+ || (codePoint >= 0xac00 && codePoint <= 0xd7af) // hangul syllables
107
+ || (codePoint >= 0xf900 && codePoint <= 0xfaff) // CJK compatibility ideographs
108
+ || (codePoint >= 0xff00 && codePoint <= 0xff60) // fullwidth forms
109
+ || (codePoint >= 0x3000 && codePoint <= 0x303f) // CJK punctuation
110
+ || (codePoint >= 0x20000 && codePoint <= 0x3fffd); // extensions B and up
111
+ }
112
+ /**
113
+ * The starting ratio for a stream of a given script mix: `cjkShare` of the
114
+ * characters read as CJK, the rest as Latin. Continuous by construction, so a
115
+ * reply that is 70% English and 30% Chinese lands 30% of the way up the range
116
+ * instead of being classified as one or the other.
117
+ */
118
+ export function scriptTokensPerChar(cjkShare) {
119
+ const share = Number.isFinite(cjkShare) ? Math.min(1, Math.max(0, cjkShare)) : 0;
120
+ return DEFAULT_TOKENS_PER_CHAR_LATIN
121
+ + (DEFAULT_TOKENS_PER_CHAR_CJK - DEFAULT_TOKENS_PER_CHAR_LATIN) * share;
122
+ }
123
+ export class ThroughputTracker {
124
+ /** The session's own ratio, once a step has settled. */
125
+ calibrated;
126
+ /** Characters and CJK characters seen in the step being estimated. */
127
+ observedChars = 0;
128
+ observedCjk = 0;
129
+ samples = [];
130
+ chars = 0;
131
+ baseAt;
132
+ startedAt;
133
+ lastDeltaAt = 0;
134
+ lastStepChars;
135
+ /** Rates already computed this step, kept for the median. */
136
+ recentRates = [];
137
+ lastRate;
138
+ settledRate;
139
+ /**
140
+ * The ratio the estimate is using right now: this session's own measurement once
141
+ * a step has settled, and the script mix of the stream being estimated before
142
+ * that. `/status` and the tests read it.
143
+ */
144
+ tokensPerChar() {
145
+ return this.calibrated ?? scriptTokensPerChar(this.cjkShare());
146
+ }
147
+ /** Share of this step's characters that were CJK, 0 when none have arrived. */
148
+ cjkShare() {
149
+ return this.observedChars === 0 ? 0 : this.observedCjk / this.observedChars;
150
+ }
151
+ /** Whether the ratio is a settled measurement rather than a script estimate. */
152
+ calibratedRatio() {
153
+ return this.calibrated !== undefined;
154
+ }
155
+ /** Drop a finished step's stream state, keeping calibration and last settle. */
156
+ reset() {
157
+ this.samples = [];
158
+ this.chars = 0;
159
+ this.observedChars = 0;
160
+ this.observedCjk = 0;
161
+ this.baseAt = undefined;
162
+ this.startedAt = undefined;
163
+ this.lastDeltaAt = 0;
164
+ this.lastStepChars = undefined;
165
+ this.recentRates = [];
166
+ this.lastRate = undefined;
167
+ }
168
+ /**
169
+ * One observed fragment of model output.
170
+ *
171
+ * The fragment itself, not a count: the estimate is tokens per character, and
172
+ * how many tokens a character is worth depends on which characters they are, so
173
+ * the script mix has to be measured here rather than assumed once for the
174
+ * session. Code points rather than UTF-16 units, so an emoji or a rare ideograph
175
+ * outside the BMP counts as the one character the reader sees.
176
+ * @param text - the fragment; empty is ignored.
177
+ * @param time - wall clock the fragment arrived at, for tests and replay.
178
+ */
179
+ note(text, time = Date.now()) {
180
+ if (text === '')
181
+ return;
182
+ let chars = 0;
183
+ let cjk = 0;
184
+ for (const char of text) {
185
+ chars += 1;
186
+ if (isCjkCodePoint(char.codePointAt(0) ?? 0))
187
+ cjk += 1;
188
+ }
189
+ if (chars === 0)
190
+ return;
191
+ if (this.startedAt === undefined) {
192
+ this.startedAt = time;
193
+ this.baseAt = time;
194
+ }
195
+ this.chars += chars;
196
+ this.observedChars += chars;
197
+ this.observedCjk += cjk;
198
+ this.lastDeltaAt = time;
199
+ const at = time - (this.baseAt ?? time);
200
+ const last = this.samples[this.samples.length - 1];
201
+ if (last !== undefined && last.at === at) {
202
+ // Two fragments inside one millisecond: one sample, or the window fills
203
+ // with points that share a timestamp and the span stays zero.
204
+ last.chars = this.chars;
205
+ }
206
+ else {
207
+ this.samples.push({ at, chars: this.chars });
208
+ }
209
+ this.trim(at);
210
+ this.sampleRate();
211
+ }
212
+ /**
213
+ * Settle a step: fold its exact decode rate into the calibration and into the
214
+ * number an idle row shows.
215
+ * @param outputTokens - the step's authoritative output tokens.
216
+ * @param decodeMs - milliseconds from first token to the settled message.
217
+ */
218
+ settle(outputTokens, decodeMs) {
219
+ const chars = this.chars;
220
+ if (Number.isFinite(outputTokens) && outputTokens > 0 && Number.isFinite(decodeMs) && decodeMs > 0) {
221
+ this.settledRate = outputTokens / (decodeMs / 1_000);
222
+ if (chars > 0) {
223
+ // The session's own measurement takes over from the script estimate here,
224
+ // and then keeps tracking it: half of the new sample, half of what was
225
+ // already known, so one unusual reply cannot redefine the session.
226
+ const before = this.tokensPerChar();
227
+ const observed = boundTokensPerChar(outputTokens / chars, before);
228
+ this.calibrated = before * (1 - CALIBRATION_WEIGHT) + observed * CALIBRATION_WEIGHT;
229
+ }
230
+ }
231
+ this.reset();
232
+ }
233
+ /** What the status row paints, and what `/status` reports. */
234
+ view(now = Date.now()) {
235
+ const liveRate = this.lastRate;
236
+ const fresh = liveRate !== undefined
237
+ && this.lastDeltaAt > 0
238
+ && now - this.lastDeltaAt <= THROUGHPUT_FRESH_MS;
239
+ return {
240
+ ...(this.settledRate === undefined ? {} : { settledRate: this.settledRate }),
241
+ liveChars: this.chars,
242
+ ...(liveRate === undefined ? {} : { liveRate }),
243
+ fresh,
244
+ };
245
+ }
246
+ /** Drop samples that fell out of the window, and keep the newest few. */
247
+ trim(at) {
248
+ const cutoff = at - THROUGHPUT_WINDOW_MS;
249
+ let drop = 0;
250
+ while (drop + 2 < this.samples.length && (this.samples[drop + 1]?.at ?? 0) <= cutoff)
251
+ drop += 1;
252
+ if (drop > 0)
253
+ this.samples.splice(0, drop);
254
+ if (this.samples.length > MAX_SAMPLES)
255
+ this.samples.splice(0, this.samples.length - MAX_SAMPLES);
256
+ }
257
+ /** Recompute the window's rate and fold it into the median buffer. */
258
+ sampleRate() {
259
+ const newest = this.samples[this.samples.length - 1];
260
+ if (newest === undefined)
261
+ return;
262
+ // The oldest sample still inside the minimum span, so a rate is never taken
263
+ // over a 20 ms sliver where one chunk decides the answer.
264
+ let oldest;
265
+ for (const sample of this.samples) {
266
+ if (newest.at - sample.at >= THROUGHPUT_MIN_SPAN_MS)
267
+ oldest = sample;
268
+ else
269
+ break;
270
+ }
271
+ oldest ??= this.samples[0];
272
+ if (oldest === undefined)
273
+ return;
274
+ const span = newest.at - oldest.at;
275
+ const chars = newest.chars - oldest.chars;
276
+ if (span < THROUGHPUT_MIN_SPAN_MS || chars <= 0)
277
+ return;
278
+ // The ratio is sampled per rate, so a step that starts in English and drifts
279
+ // into Chinese follows the text instead of one classification for the whole
280
+ // reply.
281
+ const rate = chars / (span / 1_000) * this.tokensPerChar();
282
+ if (!Number.isFinite(rate) || rate <= 0)
283
+ return;
284
+ this.lastStepChars = this.chars;
285
+ this.recentRates.push(rate);
286
+ if (this.recentRates.length > RATE_HISTORY * 3)
287
+ this.recentRates.shift();
288
+ this.lastRate = medianRate(this.recentRates) ?? rate;
289
+ }
290
+ }
291
+ /**
292
+ * A rate as the row paints it: whole numbers, `<1` rather than nothing.
293
+ *
294
+ * A slow step that decoded 3 tokens over 30 seconds is still decoding; rounding
295
+ * it to `0` reads as a stalled model. Above 1000 the number is compacted, since
296
+ * four digits is cells the quota and context meters want more.
297
+ */
298
+ export function formatRate(tokensPerSecond) {
299
+ if (!Number.isFinite(tokensPerSecond) || tokensPerSecond < 1)
300
+ return '<1';
301
+ if (tokensPerSecond >= 1_000)
302
+ return `${(Math.round(tokensPerSecond / 10) / 100).toFixed(2)}K`;
303
+ return String(Math.round(tokensPerSecond));
304
+ }
305
+ //# sourceMappingURL=throughput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throughput.js","sourceRoot":"","sources":["../src/throughput.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ import { padToWidth, sliceCodePoints, truncate, wrap } from './term-text.js';
8
8
  import { firstString, parseJsonArgs, scalarText } from './json-args.js';
9
9
  import { parsePlanTodos, planTitleFromMarkdown, todoProgressBar, todoProgressLabel, todoSummary, askSummary, TODO_STATUS_MARK, todoItemKind, planMarkdownFromArgs, } from './plan.js';
10
10
  import { activeTheme, themeExtraToken, themeToken } from './theme.js';
11
+ import { approvalProvenanceKey, approvalStateKey } from './approval-state.js';
11
12
  export const SHELL_TOOL_NAMES = new Set(['bash', 'pwsh']);
12
13
  export const DIFF_TOOL_NAMES = new Set(['edit', 'write', 'str_replace_editor']);
13
14
  export const JOB_TOOL_NAMES = new Set(['job_list', 'job_output', 'job_kill']);
@@ -246,6 +247,26 @@ export const SUBAGENT_TOOL_NAMES = new Set(['subagent', 'subagent_fork', 'task']
246
247
  * just duplicates chrome.
247
248
  */
248
249
  export const HIDDEN_TOOL_NAMES = new Set(['get_goal']);
250
+ /**
251
+ * Tools whose card is the *question* they asked, not a generic tool card (B2.4).
252
+ *
253
+ * `ask_user_question` is the only one: its durable representation is the question
254
+ * card built from the call's arguments, and drawing both was the duplicate this
255
+ * round removes. It is a separate set from {@link HIDDEN_TOOL_NAMES} on purpose —
256
+ * hidden means "no representation at all" (`get_goal`), while this means "the
257
+ * representation is drawn by the question projection". A tool card that never
258
+ * appears is a *different* decision from one whose content is shown elsewhere, and
259
+ * the audit has to be able to tell them apart.
260
+ */
261
+ export const QUESTION_TOOL_NAMES = new Set(['ask_user_question']);
262
+ /**
263
+ * Tools whose card is the *artifact* they act on, not a tool card of their own.
264
+ *
265
+ * `exit_plan_mode` is the plan review: the plan artifact carries the reviewed body,
266
+ * the review's outcome and the lifecycle lines, so a generic card beside all of that
267
+ * was a third representation of one event (B2.5 §15).
268
+ */
269
+ export const PLAN_TOOL_NAMES = new Set(['exit_plan_mode']);
249
270
  const TOOL_TITLE_KEYS = [
250
271
  'edit', 'write', 'str_replace_editor', 'fetch', 'list_files', 'list', 'ls',
251
272
  'find', 'search', 'delete', 'rm', 'rename', 'mv', 'mkdir', 'skills', 'skill',
@@ -470,15 +491,23 @@ export function buildToolHeader(input) {
470
491
  const flipping = input.flipping === true;
471
492
  const marker = flipping ? '◇' : input.expanded ? '▾' : '▸';
472
493
  const lead = `${prefix}${marker} ● ${input.title}`;
473
- const summaryText = input.summary === '' ? '' : ` ${input.summary}`;
494
+ // The summary is a *row*: a `bash` call hands over the command as written, so a
495
+ // heredoc arrives with its newlines intact and would break the frame's absolute
496
+ // row addressing (see `truncateToWidth`). Collapsed here rather than at paint time
497
+ // so the colour offsets below are computed against the text that is actually
498
+ // drawn.
499
+ const summary = input.summary.replace(/\s+/gu, ' ').trim();
500
+ const summaryText = summary === '' ? '' : ` ${summary}`;
474
501
  const statToken = input.diffStat === undefined ? '' : diffStatToken(input.diffStat.add, input.diffStat.del);
475
502
  const statText = statToken === '' ? '' : ` ${statToken}`;
476
503
  const stateGap = stateToken === '' ? '' : ' ';
477
504
  const tail = `${stateGap}${stateToken}${exit}${spinner}`;
478
- const plain = `${lead}${summaryText}${statText}${tail}`;
505
+ const approvalChip = input.approval === undefined ? '' : approvalChipText(input.approval);
506
+ const approvalText = approvalChip === '' ? '' : ` ${approvalChip}`;
507
+ const plain = `${lead}${approvalText}${summaryText}${statText}${tail}`;
479
508
  const stateCode = toolStateColor(input.status);
480
509
  const dotIndex = lead.indexOf('●');
481
- const stateIndex = stateToken === '' ? -1 : lead.length + summaryText.length + statText.length + stateGap.length;
510
+ const stateIndex = stateToken === '' ? -1 : lead.length + approvalText.length + summaryText.length + statText.length + stateGap.length;
482
511
  const segments = [];
483
512
  if (flipping) {
484
513
  const markerIndex = prefix.length;
@@ -486,13 +515,20 @@ export function buildToolHeader(input) {
486
515
  }
487
516
  if (dotIndex >= 0)
488
517
  segments.push({ start: dotIndex, end: dotIndex + '●'.length, sgr: stateCode });
518
+ if (approvalText.length > 0) {
519
+ segments.push({
520
+ start: lead.length,
521
+ end: lead.length + approvalText.length,
522
+ sgr: themeExtraToken(activeTheme(), input.approval?.state === 'rejected' ? 'accent' : 'tool-ok'),
523
+ });
524
+ }
489
525
  if (summaryText.length > 0) {
490
- segments.push({ start: lead.length, end: lead.length + summaryText.length, sgr: themeToken(activeTheme(), 'system') });
526
+ segments.push({ start: lead.length + approvalText.length, end: lead.length + approvalText.length + summaryText.length, sgr: themeToken(activeTheme(), 'system') });
491
527
  }
492
528
  if (statToken !== '') {
493
529
  // Git diffstat colors: deletions red, additions green. The token sits
494
530
  // two cells after the summary, deletions before the joining space.
495
- const statStart = lead.length + summaryText.length + 2;
531
+ const statStart = lead.length + approvalText.length + summaryText.length + 2;
496
532
  const delEnd = statToken.indexOf(' +');
497
533
  if (statToken.startsWith('-')) {
498
534
  segments.push({
@@ -510,11 +546,11 @@ export function buildToolHeader(input) {
510
546
  segments.push({ start: stateIndex, end: stateIndex + stateToken.length + exit.length, sgr: stateCode });
511
547
  }
512
548
  else if (exit !== '') {
513
- const exitIndex = lead.length + summaryText.length + statText.length;
549
+ const exitIndex = lead.length + approvalText.length + summaryText.length + statText.length;
514
550
  segments.push({ start: exitIndex, end: exitIndex + exit.length, sgr: stateCode });
515
551
  }
516
552
  if (spinner !== '') {
517
- const spinnerStart = (stateIndex >= 0 ? stateIndex + stateToken.length + exit.length : lead.length + summaryText.length + statText.length + exit.length);
553
+ const spinnerStart = (stateIndex >= 0 ? stateIndex + stateToken.length + exit.length : lead.length + approvalText.length + summaryText.length + statText.length + exit.length);
518
554
  segments.push({
519
555
  start: spinnerStart,
520
556
  end: plain.length,
@@ -523,6 +559,20 @@ export function buildToolHeader(input) {
523
559
  }
524
560
  return { plain, segments: segments.filter(segment => segment.end > segment.start) };
525
561
  }
562
+ /**
563
+ * The title chip: the state, and — when the reader is being told something
564
+ * weaker — why they can trust it less.
565
+ *
566
+ * `live` and `durable` are the same fact at two moments (a person decided, and
567
+ * the log still says so), so they print the state alone. `policy` adds `auto`,
568
+ * and `inferred` says so: a resume that found only the Harness's refusal sentence
569
+ * in a tool result is a reading, and the card has to admit it.
570
+ */
571
+ export function approvalChipText(approval) {
572
+ const state = t(approvalStateKey(approval.state));
573
+ const note = approvalProvenanceKey(approval);
574
+ return note === undefined ? state : `${state}·${t(note)}`;
575
+ }
526
576
  /** How many terminal rows a tool body occupies after wrapping. */
527
577
  export function wrappedToolBodyLineCount(lines, width) {
528
578
  const inner = Math.max(1, width - 2);