@aragon-agent/cli 0.6.14 → 0.6.16

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 (131) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +295 -238
  3. package/dist/agent/controller.d.ts +37 -1
  4. package/dist/agent/controller.js +195 -45
  5. package/dist/agent/plan-prompt.d.ts +1 -1
  6. package/dist/agent/plan-prompt.js +4 -3
  7. package/dist/agent/project-guidance-prompt.d.ts +4 -0
  8. package/dist/agent/project-guidance-prompt.js +104 -0
  9. package/dist/agent/reducer.d.ts +2 -0
  10. package/dist/agent/reducer.js +4 -0
  11. package/dist/agent/system-prompt.js +4 -0
  12. package/dist/cli.js +49 -2
  13. package/dist/commands/builtins.js +26 -28
  14. package/dist/compaction/child.d.ts +9 -1
  15. package/dist/compaction/child.js +39 -13
  16. package/dist/compaction/command.js +20 -10
  17. package/dist/compaction/compactor.d.ts +33 -204
  18. package/dist/compaction/compactor.js +311 -726
  19. package/dist/compaction/context-command.js +14 -3
  20. package/dist/compaction/digest.d.ts +15 -51
  21. package/dist/compaction/digest.js +113 -148
  22. package/dist/compaction/limits.d.ts +19 -142
  23. package/dist/compaction/limits.js +20 -147
  24. package/dist/compaction/memory-identity.d.ts +16 -0
  25. package/dist/compaction/memory-identity.js +63 -0
  26. package/dist/compaction/memory-input.d.ts +25 -0
  27. package/dist/compaction/memory-input.js +106 -0
  28. package/dist/compaction/memory-render.d.ts +4 -0
  29. package/dist/compaction/memory-render.js +47 -0
  30. package/dist/compaction/memory-validation.d.ts +21 -0
  31. package/dist/compaction/memory-validation.js +268 -0
  32. package/dist/compaction/memory.d.ts +79 -0
  33. package/dist/compaction/memory.js +118 -0
  34. package/dist/compaction/meter.d.ts +14 -10
  35. package/dist/compaction/meter.js +53 -19
  36. package/dist/compaction/operation.d.ts +18 -0
  37. package/dist/compaction/operation.js +68 -0
  38. package/dist/compaction/pressure.d.ts +5 -20
  39. package/dist/compaction/pressure.js +13 -23
  40. package/dist/compaction/summarize-call.d.ts +3 -16
  41. package/dist/compaction/summarize-call.js +20 -20
  42. package/dist/compaction/summary-prompt.d.ts +29 -66
  43. package/dist/compaction/summary-prompt.js +132 -178
  44. package/dist/compaction/types.d.ts +27 -4
  45. package/dist/compaction/wiring.d.ts +10 -25
  46. package/dist/compaction/wiring.js +56 -66
  47. package/dist/config/app-paths.d.ts +10 -0
  48. package/dist/config/app-paths.js +12 -0
  49. package/dist/config/env.d.ts +2 -1
  50. package/dist/config/env.js +13 -1
  51. package/dist/config/load.d.ts +9 -0
  52. package/dist/config/load.js +14 -0
  53. package/dist/config/model-windows.d.ts +91 -0
  54. package/dist/config/model-windows.js +181 -0
  55. package/dist/config/schema.d.ts +18 -1
  56. package/dist/config/schema.js +2 -1
  57. package/dist/exec/events.d.ts +8 -0
  58. package/dist/exec/index.js +13 -6
  59. package/dist/exec/runner.d.ts +8 -0
  60. package/dist/exec/runner.js +26 -2
  61. package/dist/input/csiu-keys.d.ts +66 -0
  62. package/dist/input/csiu-keys.js +170 -0
  63. package/dist/input/keyboard-enhancement.d.ts +42 -0
  64. package/dist/input/keyboard-enhancement.js +65 -0
  65. package/dist/input/stdin-filter.d.ts +9 -0
  66. package/dist/input/stdin-filter.js +45 -9
  67. package/dist/input/win32-input-mode.d.ts +61 -0
  68. package/dist/input/win32-input-mode.js +251 -0
  69. package/dist/session/persist.d.ts +3 -0
  70. package/dist/session/persist.js +9 -0
  71. package/dist/session/store.js +2 -0
  72. package/dist/session/validate-session.js +34 -0
  73. package/dist/team/subagent.js +8 -0
  74. package/dist/ui/App.js +218 -116
  75. package/dist/ui/BottomStatusRow.d.ts +5 -10
  76. package/dist/ui/BottomStatusRow.js +7 -11
  77. package/dist/ui/Composer.d.ts +4 -2
  78. package/dist/ui/Composer.js +2 -2
  79. package/dist/ui/Header.d.ts +6 -2
  80. package/dist/ui/Header.js +7 -6
  81. package/dist/ui/PromptInput.d.ts +3 -1
  82. package/dist/ui/PromptInput.js +32 -41
  83. package/dist/ui/StatusBar.d.ts +6 -0
  84. package/dist/ui/StatusBar.js +8 -7
  85. package/dist/ui/Transcript.js +1 -1
  86. package/dist/ui/entries/CompactionCard.d.ts +3 -1
  87. package/dist/ui/entries/CompactionCard.js +7 -5
  88. package/dist/ui/frame-differ.js +27 -0
  89. package/dist/ui/frame-observer.js +18 -5
  90. package/dist/ui/interaction-copy.d.ts +73 -73
  91. package/dist/ui/interaction-copy.js +30 -38
  92. package/dist/ui/interaction-hints.d.ts +8 -1
  93. package/dist/ui/interaction-hints.js +56 -69
  94. package/dist/ui/layout/AppShell.d.ts +6 -2
  95. package/dist/ui/layout/AppShell.js +2 -2
  96. package/dist/ui/layout/OverlayFrame.d.ts +2 -0
  97. package/dist/ui/layout/OverlayFrame.js +23 -14
  98. package/dist/ui/layout/ScrollViewport.d.ts +3 -1
  99. package/dist/ui/layout/ScrollViewport.js +10 -2
  100. package/dist/ui/layout/budget.d.ts +25 -26
  101. package/dist/ui/layout/budget.js +29 -22
  102. package/dist/ui/layout/document-layout.d.ts +2 -2
  103. package/dist/ui/layout/document-layout.js +2 -3
  104. package/dist/ui/layout/queue-layout.js +4 -4
  105. package/dist/ui/layout/status-detail-layout.d.ts +9 -0
  106. package/dist/ui/layout/status-detail-layout.js +56 -0
  107. package/dist/ui/layout/status-layout.d.ts +19 -2
  108. package/dist/ui/layout/status-layout.js +95 -84
  109. package/dist/ui/layout/wrap-rows.d.ts +2 -0
  110. package/dist/ui/layout/wrap-rows.js +21 -7
  111. package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
  112. package/dist/ui/overlays/ConfirmDialog.js +22 -3
  113. package/dist/ui/overlays/HelpOverlay.js +9 -2
  114. package/dist/ui/overlays/ModelPicker.d.ts +5 -8
  115. package/dist/ui/overlays/ModelPicker.js +44 -10
  116. package/dist/ui/overlays/ModelProfileEditor.js +2 -0
  117. package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
  118. package/dist/ui/overlays/ModelProfilePicker.js +4 -2
  119. package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
  120. package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
  121. package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
  122. package/dist/ui/overlays/QuestionOverlay.js +45 -32
  123. package/dist/ui/overlays/QueueOverlay.js +2 -2
  124. package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
  125. package/dist/ui/overlays/SettingsScreen.js +10 -3
  126. package/dist/ui/run-metrics.d.ts +23 -0
  127. package/dist/ui/run-metrics.js +27 -0
  128. package/dist/ui/status-feedback.d.ts +33 -0
  129. package/dist/ui/status-feedback.js +36 -0
  130. package/dist/ui/stdout-frame-writer.js +28 -0
  131. package/package.json +2 -2
@@ -1,77 +1,77 @@
1
- /** Product copy is centralized here; Unicode escapes keep UI source ASCII-only. */
1
+ /** Built-in product text; user content retains its original language. */
2
2
  export declare const interactionCopy: {
3
- readonly copied: "已复制";
4
- readonly lines: "行";
5
- readonly copySent: "复制请求已发送,无法确认剪贴板";
3
+ readonly copied: "Copied";
4
+ readonly lines: "lines";
5
+ readonly copySent: "Copy sent; clipboard confirmation unavailable";
6
6
  readonly copyReasons: {
7
- readonly empty: "没有可复制的内容";
8
- readonly unavailable: "剪贴板工具不可用";
9
- readonly write: "写入剪贴板失败";
10
- readonly timeout: "剪贴板工具超时";
11
- readonly 'too-large': "内容超过剪贴板限制";
12
- readonly cancelled: "复制已取消";
7
+ readonly empty: "Nothing to copy";
8
+ readonly unavailable: "Clipboard tool unavailable";
9
+ readonly write: "Clipboard write failed";
10
+ readonly timeout: "Clipboard tool timed out";
11
+ readonly 'too-large': "Content exceeds clipboard limit";
12
+ readonly cancelled: "Copy cancelled";
13
13
  };
14
- readonly stopServicesShort: "^C 停服务";
15
- readonly approveTool: "y 批准";
16
- readonly rejectTool: "n/Enter 拒绝";
17
- readonly saveSettings: "Enter 保存";
18
- readonly modeLabel: "切换模式";
19
- readonly idle: readonly ["空闲", "空闲"];
20
- readonly starting: readonly ["启动", "启动中"];
21
- readonly waiting: readonly ["等待", "等待模型"];
22
- readonly thinking: readonly ["思考", "正在思考"];
23
- readonly generating: readonly ["生成", "正在生成内容"];
24
- readonly preparingTool: readonly ["工具", "正在准备工具"];
25
- readonly tool: readonly ["工具", "正在运行工具"];
26
- readonly confirming: readonly ["确认", "等待用户确认"];
27
- readonly stopping: readonly ["停止中", "停止中"];
28
- readonly compacting: readonly ["压缩", "正在压缩上下文"];
29
- readonly retrying: readonly ["重试", "等待重试"];
30
- readonly ended: readonly ["结束", "已结束"];
31
- readonly interrupted: readonly ["中断", "已中断"];
32
- readonly failed: readonly ["失败", "运行失败"];
33
- readonly readFile: readonly ["读文件", "正在读取文件"];
34
- readonly writeFile: readonly ["改文件", "正在修改文件"];
35
- readonly command: readonly ["命令", "正在执行命令"];
36
- readonly queue: "队";
37
- readonly pending: "待处理";
38
- readonly context: "上下文";
39
- readonly contextUnknown: "上下文未知";
40
- readonly sessionInput: "会话 输入";
41
- readonly output: "输出";
42
- readonly estimatedCost: "会话估算 USD";
43
- readonly speed: "本轮均速";
44
- readonly elapsed: "耗时";
45
- readonly services: "服务";
46
- readonly send: "Enter 发送";
47
- readonly enqueue: "Enter 入队";
48
- readonly newline: "^J 换行";
49
- readonly sendLong: "Enter:发送";
50
- readonly enqueueLong: "Enter:加入队列";
51
- readonly newlineLong: "Ctrl+J:换行";
52
- readonly interrupt: "Esc×2 中断";
53
- readonly interruptAgain: "再按 Esc 中断";
54
- readonly forceStop: "再按 Esc 强制停止";
55
- readonly startingHint: "启动中,请稍候";
56
- readonly cancelStart: "Esc×2 取消启动";
57
- readonly cancelStartAgain: "再按 Esc 取消启动";
58
- readonly copy: "Ctrl+C 复制";
59
- readonly copying: "正在复制";
60
- readonly copyFailed: "复制失败";
61
- readonly copyCleanup: "复制失败,正在清理";
62
- readonly close: "Esc 关闭";
63
- readonly closeCompletion: "Esc 关闭补全";
64
- readonly executeCommand: "Enter 执行命令";
65
- readonly complete: "Tab 补全";
66
- readonly select: "Up/Down 选择";
67
- readonly scroll: "PgUp/PgDn 滚动";
68
- readonly confirm: "Enter 确认";
69
- readonly stopServices: "Ctrl+C 停止服务";
70
- readonly exit: "Ctrl+C×2 退出";
71
- readonly help: "/help 帮助";
72
- readonly shiftNewline: "Shift+Enter 换行需终端支持";
73
- readonly mode: "Shift+Tab 切换模式";
74
- readonly update: "/update 更新";
75
- readonly idlePlaceholder: "输入任务或问题…";
76
- readonly runningPlaceholder: "输入补充说明…";
14
+ readonly stopServicesShort: "^C stop services";
15
+ readonly approveTool: "y approve";
16
+ readonly rejectTool: "n/Enter reject";
17
+ readonly saveSettings: "Enter save";
18
+ readonly modeLabel: "switch mode";
19
+ readonly idle: readonly ["Idle", "Idle"];
20
+ readonly starting: readonly ["Start", "Starting"];
21
+ readonly waiting: readonly ["Wait", "Waiting for model"];
22
+ readonly thinking: readonly ["Think", "Thinking"];
23
+ readonly generating: readonly ["Write", "Generating"];
24
+ readonly preparingTool: readonly ["Prep", "Preparing tool"];
25
+ readonly tool: readonly ["Tool", "Running tool"];
26
+ readonly confirming: readonly ["Ask", "Awaiting confirmation"];
27
+ readonly stopping: readonly ["Stop", "Stopping"];
28
+ readonly compacting: readonly ["Pack", "Compacting context"];
29
+ readonly retrying: readonly ["Retry", "Retrying"];
30
+ readonly ended: readonly ["Done", "Done"];
31
+ readonly interrupted: readonly ["Abort", "Interrupted"];
32
+ readonly failed: readonly ["Error", "Failed"];
33
+ readonly readFile: readonly ["Tool", "Running tool read_file"];
34
+ readonly writeFile: readonly ["Tool", "Running tool write_file"];
35
+ readonly command: readonly ["Tool", "Running tool bash"];
36
+ readonly queue: "Q";
37
+ readonly pending: "pending";
38
+ readonly context: "Context";
39
+ readonly contextUnknown: "Context ?";
40
+ readonly sessionInput: "Session input";
41
+ readonly output: "output";
42
+ readonly estimatedCost: "Cost";
43
+ readonly speed: "Run average";
44
+ readonly elapsed: "Elapsed";
45
+ readonly services: "Services";
46
+ readonly send: "Enter send";
47
+ readonly enqueue: "Enter queue";
48
+ readonly newline: "^J newline";
49
+ readonly sendLong: "Enter send";
50
+ readonly enqueueLong: "Enter queue";
51
+ readonly newlineLong: "Ctrl+J newline";
52
+ readonly interrupt: "Esc x2 stop";
53
+ readonly interruptAgain: "Esc stop";
54
+ readonly forceStop: "Esc force";
55
+ readonly startingHint: "Starting; please wait";
56
+ readonly cancelStart: "Esc x2 cancel";
57
+ readonly cancelStartAgain: "Esc cancel";
58
+ readonly copy: "Ctrl+C copy";
59
+ readonly copying: "Copying";
60
+ readonly copyFailed: "Copy failed";
61
+ readonly copyCleanup: "Copy failed; cleaning up";
62
+ readonly close: "Esc close";
63
+ readonly closeCompletion: "Esc menu";
64
+ readonly executeCommand: "Enter command";
65
+ readonly complete: "Tab complete";
66
+ readonly select: "Up/Down select";
67
+ readonly scroll: "PgUp/PgDn scroll";
68
+ readonly confirm: "Enter confirm";
69
+ readonly stopServices: "Ctrl+C stop services";
70
+ readonly exit: "Ctrl+C x2 exit";
71
+ readonly help: "/help";
72
+ readonly shiftNewline: "Shift+Enter newline needs terminal support";
73
+ readonly mode: "Shift+Tab switch mode";
74
+ readonly update: "/update";
75
+ readonly idlePlaceholder: "Ask a question or describe a task...";
76
+ readonly runningPlaceholder: "Add a follow-up...";
77
77
  };
@@ -1,40 +1,32 @@
1
- /** Product copy is centralized here; Unicode escapes keep UI source ASCII-only. */
1
+ /** Built-in product text; user content retains its original language. */
2
2
  export const interactionCopy = {
3
- copied: '\u5df2\u590d\u5236', lines: '\u884c',
4
- copySent: '\u590d\u5236\u8bf7\u6c42\u5df2\u53d1\u9001\uff0c\u65e0\u6cd5\u786e\u8ba4\u526a\u8d34\u677f',
5
- copyReasons: {
6
- empty: '\u6ca1\u6709\u53ef\u590d\u5236\u7684\u5185\u5bb9',
7
- unavailable: '\u526a\u8d34\u677f\u5de5\u5177\u4e0d\u53ef\u7528',
8
- write: '\u5199\u5165\u526a\u8d34\u677f\u5931\u8d25',
9
- timeout: '\u526a\u8d34\u677f\u5de5\u5177\u8d85\u65f6',
10
- 'too-large': '\u5185\u5bb9\u8d85\u8fc7\u526a\u8d34\u677f\u9650\u5236',
11
- cancelled: '\u590d\u5236\u5df2\u53d6\u6d88',
12
- },
13
- stopServicesShort: '^C \u505c\u670d\u52a1',
14
- approveTool: 'y \u6279\u51c6', rejectTool: 'n/Enter \u62d2\u7edd',
15
- saveSettings: 'Enter \u4fdd\u5b58',
16
- modeLabel: '\u5207\u6362\u6a21\u5f0f',
17
- idle: ['\u7a7a\u95f2', '\u7a7a\u95f2'], starting: ['\u542f\u52a8', '\u542f\u52a8\u4e2d'], waiting: ['\u7b49\u5f85', '\u7b49\u5f85\u6a21\u578b'],
18
- thinking: ['\u601d\u8003', '\u6b63\u5728\u601d\u8003'], generating: ['\u751f\u6210', '\u6b63\u5728\u751f\u6210\u5185\u5bb9'],
19
- preparingTool: ['\u5de5\u5177', '\u6b63\u5728\u51c6\u5907\u5de5\u5177'], tool: ['\u5de5\u5177', '\u6b63\u5728\u8fd0\u884c\u5de5\u5177'],
20
- confirming: ['\u786e\u8ba4', '\u7b49\u5f85\u7528\u6237\u786e\u8ba4'], stopping: ['\u505c\u6b62\u4e2d', '\u505c\u6b62\u4e2d'],
21
- compacting: ['\u538b\u7f29', '\u6b63\u5728\u538b\u7f29\u4e0a\u4e0b\u6587'], retrying: ['\u91cd\u8bd5', '\u7b49\u5f85\u91cd\u8bd5'],
22
- ended: ['\u7ed3\u675f', '\u5df2\u7ed3\u675f'], interrupted: ['\u4e2d\u65ad', '\u5df2\u4e2d\u65ad'], failed: ['\u5931\u8d25', '\u8fd0\u884c\u5931\u8d25'],
23
- readFile: ['\u8bfb\u6587\u4ef6', '\u6b63\u5728\u8bfb\u53d6\u6587\u4ef6'], writeFile: ['\u6539\u6587\u4ef6', '\u6b63\u5728\u4fee\u6539\u6587\u4ef6'],
24
- command: ['\u547d\u4ee4', '\u6b63\u5728\u6267\u884c\u547d\u4ee4'],
25
- queue: '\u961f', pending: '\u5f85\u5904\u7406', context: '\u4e0a\u4e0b\u6587', contextUnknown: '\u4e0a\u4e0b\u6587\u672a\u77e5',
26
- sessionInput: '\u4f1a\u8bdd \u8f93\u5165', output: '\u8f93\u51fa', estimatedCost: '\u4f1a\u8bdd\u4f30\u7b97 USD',
27
- speed: '\u672c\u8f6e\u5747\u901f', elapsed: '\u8017\u65f6', services: '\u670d\u52a1',
28
- send: 'Enter \u53d1\u9001', enqueue: 'Enter \u5165\u961f', newline: '^J \u6362\u884c',
29
- sendLong: 'Enter\uff1a\u53d1\u9001', enqueueLong: 'Enter\uff1a\u52a0\u5165\u961f\u5217', newlineLong: 'Ctrl+J\uff1a\u6362\u884c',
30
- interrupt: 'Esc\u00d72 \u4e2d\u65ad', interruptAgain: '\u518d\u6309 Esc \u4e2d\u65ad', forceStop: '\u518d\u6309 Esc \u5f3a\u5236\u505c\u6b62',
31
- startingHint: '\u542f\u52a8\u4e2d\uff0c\u8bf7\u7a0d\u5019', cancelStart: 'Esc\u00d72 \u53d6\u6d88\u542f\u52a8',
32
- cancelStartAgain: '\u518d\u6309 Esc \u53d6\u6d88\u542f\u52a8',
33
- copy: 'Ctrl+C \u590d\u5236', copying: '\u6b63\u5728\u590d\u5236', copyFailed: '\u590d\u5236\u5931\u8d25', copyCleanup: '\u590d\u5236\u5931\u8d25\uff0c\u6b63\u5728\u6e05\u7406',
34
- close: 'Esc \u5173\u95ed', closeCompletion: 'Esc \u5173\u95ed\u8865\u5168', executeCommand: 'Enter \u6267\u884c\u547d\u4ee4',
35
- complete: 'Tab \u8865\u5168', select: 'Up/Down \u9009\u62e9', scroll: 'PgUp/PgDn \u6eda\u52a8',
36
- confirm: 'Enter \u786e\u8ba4', stopServices: 'Ctrl+C \u505c\u6b62\u670d\u52a1', exit: 'Ctrl+C\u00d72 \u9000\u51fa',
37
- help: '/help \u5e2e\u52a9', shiftNewline: 'Shift+Enter \u6362\u884c\u9700\u7ec8\u7aef\u652f\u6301',
38
- mode: 'Shift+Tab \u5207\u6362\u6a21\u5f0f', update: '/update \u66f4\u65b0',
39
- idlePlaceholder: '\u8f93\u5165\u4efb\u52a1\u6216\u95ee\u9898\u2026', runningPlaceholder: '\u8f93\u5165\u8865\u5145\u8bf4\u660e\u2026',
3
+ copied: 'Copied', lines: 'lines',
4
+ copySent: 'Copy sent; clipboard confirmation unavailable',
5
+ copyReasons: { empty: 'Nothing to copy', unavailable: 'Clipboard tool unavailable',
6
+ write: 'Clipboard write failed', timeout: 'Clipboard tool timed out',
7
+ 'too-large': 'Content exceeds clipboard limit', cancelled: 'Copy cancelled' },
8
+ stopServicesShort: '^C stop services', approveTool: 'y approve', rejectTool: 'n/Enter reject',
9
+ saveSettings: 'Enter save', modeLabel: 'switch mode',
10
+ idle: ['Idle', 'Idle'], starting: ['Start', 'Starting'], waiting: ['Wait', 'Waiting for model'],
11
+ thinking: ['Think', 'Thinking'], generating: ['Write', 'Generating'],
12
+ preparingTool: ['Prep', 'Preparing tool'], tool: ['Tool', 'Running tool'],
13
+ confirming: ['Ask', 'Awaiting confirmation'], stopping: ['Stop', 'Stopping'],
14
+ compacting: ['Pack', 'Compacting context'], retrying: ['Retry', 'Retrying'],
15
+ ended: ['Done', 'Done'], interrupted: ['Abort', 'Interrupted'], failed: ['Error', 'Failed'],
16
+ readFile: ['Tool', 'Running tool read_file'], writeFile: ['Tool', 'Running tool write_file'],
17
+ command: ['Tool', 'Running tool bash'],
18
+ queue: 'Q', pending: 'pending', context: 'Context', contextUnknown: 'Context ?',
19
+ sessionInput: 'Session input', output: 'output', estimatedCost: 'Cost',
20
+ speed: 'Run average', elapsed: 'Elapsed', services: 'Services',
21
+ send: 'Enter send', enqueue: 'Enter queue', newline: '^J newline',
22
+ sendLong: 'Enter send', enqueueLong: 'Enter queue', newlineLong: 'Ctrl+J newline',
23
+ interrupt: 'Esc x2 stop', interruptAgain: 'Esc stop', forceStop: 'Esc force',
24
+ startingHint: 'Starting; please wait', cancelStart: 'Esc x2 cancel', cancelStartAgain: 'Esc cancel',
25
+ copy: 'Ctrl+C copy', copying: 'Copying', copyFailed: 'Copy failed', copyCleanup: 'Copy failed; cleaning up',
26
+ close: 'Esc close', closeCompletion: 'Esc menu', executeCommand: 'Enter command',
27
+ complete: 'Tab complete', select: 'Up/Down select', scroll: 'PgUp/PgDn scroll',
28
+ confirm: 'Enter confirm', stopServices: 'Ctrl+C stop services', exit: 'Ctrl+C x2 exit',
29
+ help: '/help', shiftNewline: 'Shift+Enter newline needs terminal support',
30
+ mode: 'Shift+Tab switch mode', update: '/update',
31
+ idlePlaceholder: 'Ask a question or describe a task...', runningPlaceholder: 'Add a follow-up...',
40
32
  };
@@ -1,7 +1,11 @@
1
+ import type { StatusFeedback, EscapeAction } from './status-feedback.js';
1
2
  export interface ActionHintInput {
2
3
  cols: number;
3
4
  interactionPhase: 'idle' | 'starting' | 'running';
4
5
  interruptHint?: string;
6
+ interruptPhase?: 'ready' | 'armed' | 'stopping';
7
+ escapeAction?: EscapeAction;
8
+ feedback?: StatusFeedback;
5
9
  selectionPending?: boolean;
6
10
  copyInFlight?: boolean;
7
11
  copyCleanupPending?: boolean;
@@ -14,5 +18,8 @@ export interface ActionHintInput {
14
18
  exitHint?: string;
15
19
  modeToggleKey?: string;
16
20
  }
17
- /** Esc describes its actual owner, including while transient feedback has the row. */
21
+ export declare function escapeClause(input: ActionHintInput): string;
22
+ export declare function clipStatusText(text: string, cells: number): string;
23
+ /** Ordered actions before width allocation. No interpretation of translated feedback text. */
24
+ export declare function actionCandidates(input: ActionHintInput): string[];
18
25
  export declare function buildActionClauses(input: ActionHintInput): readonly string[];
@@ -1,92 +1,79 @@
1
1
  import stringWidth from 'string-width';
2
2
  import { interactionCopy as copy } from './interaction-copy.js';
3
3
  import { cleanStatusText } from './layout/status-layout.js';
4
- function escapeClause(input) {
4
+ import { graphemes } from './editor-navigation.js';
5
+ export function escapeClause(input) {
5
6
  if (input.overlay)
6
7
  return copy.close;
7
- if (input.completion && input.completion !== 'none')
8
+ if (input.completion && input.completion !== 'none' || input.escapeAction === 'menu')
8
9
  return copy.closeCompletion;
10
+ if (input.escapeAction === 'force-stop' || input.interruptPhase === 'stopping')
11
+ return copy.forceStop;
9
12
  if (input.interactionPhase === 'starting')
10
- return /again|confirm/i.test(input.interruptHint ?? '')
11
- ? copy.cancelStartAgain : copy.cancelStart;
13
+ return input.interruptPhase === 'armed' ? copy.cancelStartAgain : copy.cancelStart;
12
14
  if (input.interactionPhase !== 'running')
13
15
  return '';
14
- if (/force|\u5f3a\u5236/i.test(input.interruptHint ?? ''))
15
- return copy.forceStop;
16
- if (/again|confirm|\u518d\u6309/i.test(input.interruptHint ?? ''))
17
- return copy.interruptAgain;
18
- return copy.interrupt;
16
+ return input.interruptPhase === 'armed' ? copy.interruptAgain : copy.interrupt;
19
17
  }
20
- function contextClauses(input, escape) {
18
+ export function clipStatusText(text, cells) {
19
+ let clipped = '';
20
+ for (const part of graphemes(cleanStatusText(text))) {
21
+ if (stringWidth(clipped + part.text) > cells)
22
+ break;
23
+ clipped += part.text;
24
+ }
25
+ return clipped;
26
+ }
27
+ /** Ordered actions before width allocation. No interpretation of translated feedback text. */
28
+ export function actionCandidates(input) {
21
29
  if (input.overlay) {
22
30
  if (input.overlay === 'confirm')
23
- return [escape, copy.approveTool, copy.rejectTool];
24
- const selects = /question|model|session|settings/.test(input.overlay);
25
- return [escape, selects ? copy.select : copy.scroll,
31
+ return [copy.approveTool, copy.rejectTool];
32
+ const selects = ['question', 'model', 'session', 'settings'].includes(input.overlay);
33
+ return [selects ? copy.select : copy.scroll,
26
34
  ...(selects ? [input.overlay === 'settings' ? copy.saveSettings : copy.confirm] : [])];
27
35
  }
36
+ const actions = [];
37
+ if (input.selectionPending)
38
+ actions.push(copy.copy);
39
+ else if (input.services)
40
+ actions.push(copy.stopServicesShort);
28
41
  if (input.completion && input.completion !== 'none') {
29
- const candidates = input.completion === 'slash'
30
- ? [copy.executeCommand, escape, copy.complete, copy.select, copy.newline]
31
- : [copy.complete, escape, copy.select, copy.newline];
32
- if (input.completion === 'file' && input.interactionPhase !== 'starting')
33
- candidates.push(input.interactionPhase === 'running' ? copy.enqueue : copy.send);
34
- return candidates;
42
+ actions.push(...(input.completion === 'slash' ? [copy.executeCommand] : []), copy.complete, copy.select);
43
+ return actions;
35
44
  }
36
- if (input.interactionPhase === 'starting')
37
- return [copy.startingHint, escape];
38
- const running = input.interactionPhase === 'running';
39
- const narrow = input.cols < 72;
40
- return [running ? (narrow ? copy.enqueue : copy.enqueueLong) : (narrow ? copy.send : copy.sendLong),
41
- narrow ? copy.newline : copy.newlineLong, ...(escape ? [escape] : [])];
45
+ if (input.hintsEnabled !== false && input.interactionPhase !== 'starting')
46
+ actions.push(input.interactionPhase === 'running' ? copy.enqueue : copy.send, input.cols < 72 ? copy.newline : copy.newlineLong);
47
+ return actions;
42
48
  }
43
- function fitClauses(candidates, budget) {
44
- const result = [];
45
- for (const text of candidates.filter(Boolean)) {
46
- if (stringWidth([...result, text].join(' | ')) <= budget)
47
- result.push(text);
48
- }
49
- return result;
50
- }
51
- /** Esc describes its actual owner, including while transient feedback has the row. */
52
49
  export function buildActionClauses(input) {
53
50
  const budget = Math.max(0, Math.floor(input.cols) - 1);
54
51
  const escape = escapeClause(input);
55
- const copyFeedback = input.copyCleanupPending
56
- ? (input.toast?.includes(copy.copyFailed) ? input.toast : copy.copyCleanup)
57
- : input.copyInFlight ? copy.copying : '';
58
- const feedback = input.exitHint || copyFeedback || input.toast || '';
52
+ const result = [];
53
+ const feedback = input.feedback?.text || input.exitHint || (input.copyCleanupPending ? copy.copyCleanup
54
+ : input.copyInFlight ? copy.copying : input.toast) || '';
55
+ const reserve = escape ? stringWidth(escape) + 3 : 0;
59
56
  if (feedback) {
60
- const reserved = escape ? [escape] : [];
61
- const remaining = budget - stringWidth(reserved.join(' | ')) - (reserved.length ? 3 : 0);
62
- let text = cleanStatusText(feedback);
63
- if (stringWidth(text) > remaining) {
64
- const minimum = text.includes(copy.copyFailed) ? copy.copyFailed : '';
65
- let clipped = '';
66
- for (const char of text) {
67
- if (stringWidth(clipped + char) > remaining)
68
- break;
69
- clipped += char;
70
- }
71
- text = minimum && stringWidth(minimum) <= remaining ? minimum : clipped;
72
- }
73
- return fitClauses([text, ...reserved], budget);
57
+ const text = clipStatusText(feedback, Math.max(0, budget - reserve));
58
+ if (text)
59
+ result.push(text);
74
60
  }
75
- const clauses = contextClauses(input, escape);
76
- if (input.selectionPending)
77
- clauses.unshift(copy.copy);
78
- if (input.services && !input.selectionPending)
79
- clauses.unshift(input.cols < 72
80
- ? `${copy.stopServicesShort} ${input.services}` : `${copy.services} ${input.services} ${copy.stopServices}`);
81
- if (input.updateAvailable && input.interactionPhase === 'idle' && !input.overlay)
82
- clauses.unshift(copy.update);
83
- if (!input.selectionPending && !input.services)
84
- clauses.push(copy.exit);
85
- if (input.hintsEnabled !== false && !input.overlay)
86
- clauses.push(copy.shiftNewline, `${(input.modeToggleKey ?? 'shift+tab').split('+').map((key) => key.length === 1 ? key.toUpperCase() : key[0].toUpperCase() + key.slice(1).toLowerCase()).join('+')} ${copy.modeLabel}`, copy.help, '/ commands', '@ files');
87
- // Reserve Esc before lower-priority clauses, but preserve the documented display order.
88
- if (!escape)
89
- return fitClauses(clauses, budget);
90
- const selected = fitClauses(clauses.filter((clause) => clause !== escape), budget - stringWidth(escape) - 3);
91
- return clauses.filter((clause) => clause === escape || selected.includes(clause));
61
+ const add = (text, remaining = budget) => {
62
+ if (stringWidth([...result, text].join(' | ')) <= remaining)
63
+ result.push(cleanStatusText(text));
64
+ };
65
+ if (!feedback) {
66
+ for (const action of actionCandidates(input))
67
+ add(action, budget - reserve);
68
+ }
69
+ if (escape)
70
+ add(escape);
71
+ if (!feedback && input.hintsEnabled !== false && !input.overlay) {
72
+ if (input.updateAvailable)
73
+ add(copy.update);
74
+ add(copy.exit);
75
+ add((input.modeToggleKey ?? 'Shift+Tab') + ' ' + copy.modeLabel);
76
+ add(copy.help);
77
+ }
78
+ return result;
92
79
  }
@@ -5,9 +5,13 @@ export interface AppShellProps {
5
5
  cols: number;
6
6
  header: React.ReactNode;
7
7
  viewport: React.ReactNode;
8
- toast: React.ReactNode;
8
+ composer: React.ReactNode;
9
+ composerSlotRows: number;
10
+ viewportRows: number;
11
+ details?: React.ReactNode;
12
+ statusRows: 0 | 1 | 2;
9
13
  status: React.ReactNode;
10
14
  inactive?: boolean;
11
15
  placeholder?: React.ReactNode;
12
16
  }
13
- export declare function AppShell({ rows, cols, header, viewport, toast, status, inactive, placeholder, }: AppShellProps): React.ReactElement;
17
+ export declare function AppShell({ rows, cols, header, viewport, composer, composerSlotRows, viewportRows, details, statusRows, status, inactive, placeholder, }: AppShellProps): React.ReactElement;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box } from 'ink';
3
3
  import { frameHeight } from './frame.js';
4
- export function AppShell({ rows, cols, header, viewport, toast, status, inactive = false, placeholder, }) {
5
- return (_jsxs(Box, { flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", children: [_jsxs(Box, { display: inactive ? 'none' : 'flex', flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", children: [_jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: header }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: viewport }), _jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: toast }), _jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: status })] }), _jsx(Box, { display: inactive ? 'flex' : 'none', height: frameHeight(rows), overflow: "hidden", children: placeholder })] }));
4
+ export function AppShell({ rows, cols, header, viewport, composer, composerSlotRows, viewportRows, details, statusRows, status, inactive = false, placeholder, }) {
5
+ return (_jsxs(Box, { flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", flexShrink: 0, children: [_jsxs(Box, { display: inactive ? 'none' : 'flex', flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", flexShrink: 0, children: [_jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: header }), _jsx(Box, { flexDirection: "column", height: viewportRows, flexShrink: 0, overflow: "hidden", children: viewport }), _jsx(Box, { flexDirection: "column", height: composerSlotRows, justifyContent: "flex-end", flexShrink: 0, overflow: "hidden", children: composer }), _jsx(Box, { height: statusRows > 0 ? 1 : 0, flexShrink: 0, overflow: "hidden", children: status }), statusRows === 2 && _jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: details })] }), _jsx(Box, { display: inactive ? 'flex' : 'none', height: frameHeight(rows), overflow: "hidden", children: placeholder })] }));
6
6
  }
@@ -35,4 +35,6 @@ interface OverlayFrameSelfManaged extends OverlayFrameBase {
35
35
  }
36
36
  export type OverlayFrameProps = OverlayFrameControlled | OverlayFrameSelfManaged;
37
37
  export declare function OverlayFrame(props: OverlayFrameProps): React.ReactElement;
38
+ /** Body rows shared by controlled overlays and self-managed focused lists. */
39
+ export declare function overlayBodyRows(maxRows: number, cols: number): number;
38
40
  export {};
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect } from 'react';
2
+ import { useEffect, useMemo } from 'react';
3
3
  import { Box, Text } from 'ink';
4
+ import stringWidth from 'string-width';
4
5
  import { pickGlyphs } from '../glyphs.js';
5
6
  import { sliceWindow } from './overlay-window.js';
6
7
  /** Below this width the round border costs more than it conveys (§5.2). */
@@ -19,14 +20,22 @@ const MARGIN_ROWS = 1;
19
20
  export function OverlayFrame(props) {
20
21
  const { title, hint, maxRows, cols = 80, theme, caps } = props;
21
22
  const glyphs = pickGlyphs(caps);
22
- const bordered = cols >= BORDERLESS_COLS;
23
+ // Ink applies changed style keys alone. Recreating this object resets the
24
+ // unchanged borderTop/borderBottom=false edges to one cell on a later render.
25
+ const railBorderStyle = useMemo(() => ({
26
+ topLeft: glyphs.railVertical, top: '', topRight: '', right: '',
27
+ bottomRight: '', bottom: '', bottomLeft: glyphs.railVertical,
28
+ left: glyphs.railVertical,
29
+ }), [glyphs.railVertical]);
30
+ const compact = Number.isFinite(maxRows) && maxRows < (cols >= BORDERLESS_COLS ? 6 : 4);
31
+ const bordered = !compact && cols >= BORDERLESS_COLS;
23
32
  const unbounded = !Number.isFinite(maxRows);
24
33
  let body;
25
34
  let position = '';
26
35
  /** Set only in controlled mode; `null` means "nothing to report back". */
27
36
  let overflowOffset = null;
28
37
  if (props.rows) {
29
- const chrome = MARGIN_ROWS + (bordered ? CHROME_ROWS : CHROME_ROWS - 2);
38
+ const chrome = compact ? 2 : MARGIN_ROWS + (bordered ? CHROME_ROWS : CHROME_ROWS - 2);
30
39
  const height = unbounded ? props.rows.length : Math.max(1, Math.floor(maxRows) - chrome);
31
40
  const win = sliceWindow(props.rows.length, props.scrollOffset, height);
32
41
  body = props.rows.slice(win.start, win.start + win.visible);
@@ -49,20 +58,20 @@ export function OverlayFrame(props) {
49
58
  if (overflowOffset !== null)
50
59
  onScrollClamp?.(overflowOffset);
51
60
  }, [overflowOffset, onScrollClamp]);
52
- const inner = (_jsxs(_Fragment, { children: [_jsx(Text, { wrap: "truncate", color: theme.primary, bold: true, children: title }), body, _jsxs(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: [hint, position] })] }));
61
+ const inner = (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "row", height: 1, flexShrink: 0, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { wrap: "truncate", color: theme.primary, bold: true, children: title }) }), position && _jsx(Box, { width: stringWidth(position), flexShrink: 0, children: _jsx(Text, { color: theme.muted, children: position }) })] }), _jsx(Box, { flexDirection: "column", flexShrink: 0, height: unbounded ? undefined : Math.max(1, maxRows - (compact ? 2 : bordered ? 5 : 3)), overflowY: "hidden", children: body }), _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: hint })] }));
62
+ if (compact)
63
+ return _jsx(Box, { width: cols, height: Math.max(0, maxRows), flexDirection: "column", flexShrink: 0, overflowY: "hidden", children: inner });
53
64
  if (!bordered) {
54
65
  // Narrow terminals trade the box for a rail: same grouping, 2 fewer rows
55
66
  // and 4 fewer columns (§5.2).
56
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, marginTop: 1, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: {
57
- topLeft: glyphs.railVertical,
58
- top: '',
59
- topRight: '',
60
- right: '',
61
- bottomRight: '',
62
- bottom: '',
63
- bottomLeft: glyphs.railVertical,
64
- left: glyphs.railVertical,
65
- }, borderTop: false, borderBottom: false, borderRight: false, borderColor: theme.primary, paddingLeft: 1, children: inner }) }));
67
+ return (_jsx(Box, { flexDirection: "column", flexShrink: 0, marginTop: 1, children: _jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: railBorderStyle, borderTop: false, borderBottom: false, borderRight: false, borderColor: theme.primary, paddingLeft: 1, children: inner }) }));
66
68
  }
67
69
  return (_jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: theme.primary, paddingX: 1, marginTop: 1, children: inner }));
68
70
  }
71
+ /** Body rows shared by controlled overlays and self-managed focused lists. */
72
+ export function overlayBodyRows(maxRows, cols) {
73
+ if (!Number.isFinite(maxRows))
74
+ return 20;
75
+ const compact = maxRows < (cols >= BORDERLESS_COLS ? 6 : 4);
76
+ return Math.max(1, Math.floor(maxRows) - (compact ? 2 : cols >= BORDERLESS_COLS ? 5 : 3));
77
+ }
@@ -13,6 +13,8 @@ export interface ScrollViewportProps {
13
13
  };
14
14
  pinToBottomNonce?: number;
15
15
  resetKey?: unknown;
16
+ geometryKey?: string;
17
+ onGeometryChange?: () => void;
16
18
  onScrolledLinesChange?: (lines: number) => void;
17
19
  onViewportShiftChange?: (shift: number) => void;
18
20
  tailRowsRef?: TailSink;
@@ -34,5 +36,5 @@ export interface ScrollViewportProps {
34
36
  caps: TermCapabilities;
35
37
  children: React.ReactNode;
36
38
  }
37
- /** One offset for messages, padding and editor; hidden trees keep their local state. */
39
+ /** One offset for messages, padding and panels; hidden trees keep their local state. */
38
40
  export declare function ScrollViewport(props: ScrollViewportProps): React.ReactElement;
@@ -26,7 +26,7 @@ function elementVisible(element, clip) {
26
26
  return current === clip && top >= 0 &&
27
27
  top + measureElement(element).height <= measureElement(clip).height;
28
28
  }
29
- /** One offset for messages, padding and editor; hidden trees keep their local state. */
29
+ /** One offset for messages, padding and panels; hidden trees keep their local state. */
30
30
  export function ScrollViewport(props) {
31
31
  const { stdout } = useStdout();
32
32
  const { active = true, hold = false, resumeMs = 0, scrollbar } = props;
@@ -55,6 +55,7 @@ export function ScrollViewport(props) {
55
55
  const previousActive = useRef(active);
56
56
  const geometryRevision = useRef(0);
57
57
  const geometryBounds = useRef('');
58
+ const lastGeometryKey = useRef(props.geometryKey);
58
59
  const clearResume = useCallback(() => {
59
60
  if (resumeTimer.current)
60
61
  clearTimeout(resumeTimer.current);
@@ -125,6 +126,13 @@ export function ScrollViewport(props) {
125
126
  }, []);
126
127
  useEffect(() => { armResume(); }, [hold, resumeMs, armResume]);
127
128
  useLayoutEffect(() => {
129
+ if (lastGeometryKey.current !== props.geometryKey) {
130
+ props.onGeometryChange?.();
131
+ scrollbar?.controller?.cancel();
132
+ scrollbar?.invalidate();
133
+ geometryRevision.current++;
134
+ lastGeometryKey.current = props.geometryKey;
135
+ }
128
136
  const reset = lastReset.current !== props.resetKey;
129
137
  lastReset.current = props.resetKey;
130
138
  const pin = lastPin.current !== props.pinToBottomNonce;
@@ -148,7 +156,7 @@ export function ScrollViewport(props) {
148
156
  const viewport = clipRef.current ? measureElement(clipRef.current).height : 0;
149
157
  const body = bodyRef.current ? measureElement(bodyRef.current).height : 0;
150
158
  const footer = footerRef.current ? measureElement(footerRef.current).height : 0;
151
- const layout = buildDocumentLayout({ rows: viewport + 4, bodyRows: body, footerRows: footer });
159
+ const layout = buildDocumentLayout({ viewportRows: viewport, bodyRows: body, footerRows: footer });
152
160
  const padding = props.footer === undefined ? 0 : layout.paddingRows;
153
161
  const content = body + footer + padding;
154
162
  const tail = props.tailRowsRef?.current.rows ?? 0;