nolo-cli 0.11.0 → 0.12.0-alpha.1

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 (123) hide show
  1. package/{_executeModel-ZEDPX6OF.js → _executeModel-MWAY74HA.js} +51 -50
  2. package/{action-AA4IJ5K2.js → action-RAXNQQKC.js} +8 -9
  3. package/{addContentAction-QYVO4SYP.js → addContentAction-ZHI7TVV3.js} +7 -9
  4. package/{addDialogAgentAction-WVVKT5WQ.js → addDialogAgentAction-JWXPKXNC.js} +9 -10
  5. package/{agentDeleteCommand-RDPGYIWF.js → agentDeleteCommand-LXR5A5FV.js} +38 -28
  6. package/{agentEmailCommands-2HJJKS5F.js → agentEmailCommands-DXRAP7BB.js} +64 -62
  7. package/{agentListCommands-HFEVV3ZW.js → agentListCommands-ZFMS5ZLY.js} +127 -31
  8. package/{agentMachineCommands-25ZOENJU.js → agentMachineCommands-ZJLYVV52.js} +37 -26
  9. package/{agentPullCommand-GOL2S4W2.js → agentPullCommand-KRBRIFIY.js} +7 -7
  10. package/{agentRecordCommands-GTCOAM3L.js → agentRecordCommands-DFS626TB.js} +56 -54
  11. package/{agentRunCommand-YBS3JDLD.js → agentRunCommand-GWBIE2TS.js} +59 -59
  12. package/{agentRunControl-K4VKNEHF.js → agentRunControl-IX4F5W2C.js} +3 -3
  13. package/{agentRuntimeLocal-UQBMQI2M.js → agentRuntimeLocal-YNZSHMZC.js} +30 -15
  14. package/{agentSlice-6EKOBCCS.js → agentSlice-T2PXOEPQ.js} +8 -9
  15. package/{authCommand-O2OWACN4.js → authCommand-DMW336GT.js} +15 -15
  16. package/{authCommands-H7365TZP.js → authCommands-GH5T6RYV.js} +6 -6
  17. package/{chunk-7D3JY6C2.js → chunk-3HHTVM6G.js} +64 -5
  18. package/{chunk-AFON4QUU.js → chunk-3QKO4EHV.js} +3 -3
  19. package/{chunk-4SOKXIW4.js → chunk-43UD5ZP7.js} +8 -11
  20. package/{chunk-S3GS2PRL.js → chunk-4BTHUYMY.js} +597 -68
  21. package/{chunk-SV5MWASH.js → chunk-4EH7AR52.js} +12 -12
  22. package/{chunk-FXXSGWAJ.js → chunk-4GBNEA5B.js} +1 -1
  23. package/{chunk-PXZAAB4L.js → chunk-4LG74DRG.js} +4 -4
  24. package/{chunk-2FTTDCD4.js → chunk-4ON5AV72.js} +2 -1
  25. package/{chunk-IWX4UW2E.js → chunk-524BDL33.js} +89 -8
  26. package/{chunk-X2S7JTIG.js → chunk-5ITRDSHY.js} +1240 -136
  27. package/{chunk-ISNCLGUZ.js → chunk-5NBHQD4D.js} +4 -3
  28. package/{chunk-GBDSZATH.js → chunk-6LMFFG3K.js} +3 -3
  29. package/{chunk-OUI3ZHYD.js → chunk-6OVCRC72.js} +1 -1
  30. package/{chunk-CKOJQDKR.js → chunk-7JJCGV7N.js} +146 -3
  31. package/{chunk-MQI77IRB.js → chunk-7MYHLZ3F.js} +2 -2
  32. package/{chunk-5MOUAAMK.js → chunk-AM53Q2SD.js} +194 -2564
  33. package/chunk-BYXBJQAS.js +0 -0
  34. package/{chunk-LAX2JSTX.js → chunk-DE5BA4O6.js} +20 -2
  35. package/{chunk-BX6U3ETE.js → chunk-DRMGJFGQ.js} +293 -89
  36. package/{chunk-KJR5KAUQ.js → chunk-EBH7G34M.js} +20 -7
  37. package/{chunk-E4HNJSHC.js → chunk-G3B356GF.js} +16 -18
  38. package/{chunk-KQQVGRA3.js → chunk-GDBCUWQ3.js} +35 -29
  39. package/{chunk-Q3UKJVU4.js → chunk-GPZM7QH6.js} +10 -10
  40. package/chunk-IKXL2HZ3.js +70 -0
  41. package/{chunk-QNMFBBZK.js → chunk-IVAOVLM7.js} +1 -1
  42. package/{chunk-EVZKZF32.js → chunk-JUN3HFMN.js} +182 -75
  43. package/{chunk-NEM67FX7.js → chunk-JZDIW766.js} +1 -1
  44. package/{chunk-TACHTH5D.js → chunk-LHSJMY5V.js} +18 -18
  45. package/{chunk-XYZA7S7V.js → chunk-MEVCDATU.js} +6 -6
  46. package/{chunk-YOCHZD7D.js → chunk-NCGFB7NN.js} +5 -3
  47. package/{chunk-O26GI2JP.js → chunk-NITP2JDG.js} +39 -8
  48. package/{chunk-YPRZSPF3.js → chunk-OCDEWRE2.js} +1 -1
  49. package/{chunk-PNEW2Z3T.js → chunk-OHEKYYJF.js} +7 -7
  50. package/chunk-OOIN7HSO.js +146 -0
  51. package/{chunk-DWGAEFJL.js → chunk-OPKKB25T.js} +1 -1
  52. package/{chunk-AEZAORLG.js → chunk-QCA3EPYK.js} +5 -5
  53. package/{chunk-C4FTUXVF.js → chunk-QLUFR47U.js} +11 -1
  54. package/{chunk-C2MDQQ4O.js → chunk-RNQ5BLR2.js} +1 -1
  55. package/{chunk-L53FGHA4.js → chunk-RSEUHW6T.js} +1 -1
  56. package/{chunk-2W2VYVI5.js → chunk-RZ735TLX.js} +527 -176
  57. package/chunk-S3UTETIC.js +4885 -0
  58. package/chunk-S73OEOHH.js +2286 -0
  59. package/{chunk-YTBMA7NO.js → chunk-TSKWE74T.js} +58 -8
  60. package/{chunk-5OIFWVX7.js → chunk-UG3WOFU6.js} +1 -1
  61. package/{chunk-4ZDM73SD.js → chunk-V2J635L5.js} +1 -1
  62. package/{chunk-IPYUUY4E.js → chunk-WPHL6ALY.js} +1 -1
  63. package/{chunk-3NSIQZZ6.js → chunk-WTYKCU4W.js} +9 -9
  64. package/{chunk-LP65VRCI.js → chunk-WWU275LU.js} +6 -6
  65. package/{chunk-XFOLQLDN.js → chunk-XHQWJUW4.js} +2 -2
  66. package/{chunk-D6OQHXVG.js → chunk-YDCZVUFP.js} +7 -7
  67. package/{chunk-PXQN7IBQ.js → chunk-YTQLJYLB.js} +2 -2
  68. package/{chunk-ZEYDOXHF.js → chunk-ZPFRLGIZ.js} +1467 -869
  69. package/{chunk-ZPRK73XY.js → chunk-ZR4VHQ5D.js} +14 -13
  70. package/cliEnvHelpers-VYC2OPTB.js +30 -0
  71. package/{createAgentAutomationAction-LTI5QU35.js → createAgentAutomationAction-G32GKDGV.js} +25 -26
  72. package/{createDialogAction-OJJ7L3L3.js → createDialogAction-TISANRZK.js} +26 -27
  73. package/{createPageAction-5MXWVRGY.js → createPageAction-O7KOYNV4.js} +26 -27
  74. package/{dbSlice-RQFQQVHT.js → dbSlice-WDP2ZZHK.js} +6 -7
  75. package/{deleteFile-7XP5BGC7.js → deleteFile-M7UNIUOO.js} +4 -4
  76. package/{dialogCommands-JTJ3RPPW.js → dialogCommands-6JPB3YQY.js} +33 -23
  77. package/{dialogSlice-OWROKURJ.js → dialogSlice-PRHHYSP5.js} +26 -27
  78. package/{docCreateCommands-WJBEW2V7.js → docCreateCommands-75E6TZNN.js} +33 -23
  79. package/{docDeleteCommands-BVEHJOY5.js → docDeleteCommands-EJHLN7LW.js} +34 -25
  80. package/{docReadCommands-NEWOZIJV.js → docReadCommands-RJYBC4FS.js} +33 -23
  81. package/{docStore-3DXRYBMD.js → docStore-H5VWKT5D.js} +25 -26
  82. package/{docUpdateCommands-C7WHT7UU.js → docUpdateCommands-MG4CNISV.js} +33 -23
  83. package/fetchMessages-KTNM7QAR.js +1 -1
  84. package/{getPricing-3E6UNPKL.js → getPricing-6TOZXPAU.js} +7 -3
  85. package/{handleSendMessageAction-B5PRR7BE.js → handleSendMessageAction-QTUGICSZ.js} +29 -30
  86. package/index.js +77 -76
  87. package/{localDialogRead-YSKFFRTT.js → localDialogRead-2VRI24F4.js} +1 -1
  88. package/{machineCommands-IIIREAEK.js → machineCommands-MAV7M3TK.js} +63 -61
  89. package/{machineWsRunDispatch-3SIU6ILW.js → machineWsRunDispatch-ZOEGTKHC.js} +55 -53
  90. package/{memoryCommands-O4MIPIWQ.js → memoryCommands-AFMES3LN.js} +5 -5
  91. package/{models-4OPOOLFS.js → models-LSCRJJ6P.js} +2 -2
  92. package/package.json +1 -1
  93. package/{read-QJNKRROM.js → read-2MIMRBZ2.js} +4 -4
  94. package/{readDocTool-H6X6KO6X.js → readDocTool-QHVGQHUJ.js} +1 -1
  95. package/{readlineWorkspace-HGP3I3OC.js → readlineWorkspace-SWQKC7ZS.js} +1113 -678
  96. package/{removeDialogAgentAction-TCT35SOF.js → removeDialogAgentAction-GTX5AN54.js} +13 -13
  97. package/{runAgentClientLoop-EFM4PTVI.js → runAgentClientLoop-P25HACXX.js} +47 -47
  98. package/{runtimeDoctorCommands-ID43Z7VX.js → runtimeDoctorCommands-QNF3J3UV.js} +6 -6
  99. package/{setDialogExtraReferencesAction-CGYLZMFH.js → setDialogExtraReferencesAction-WFQ63KYO.js} +9 -10
  100. package/{setPrimaryDialogAgentAction-F6BFQDJU.js → setPrimaryDialogAgentAction-P57FXTSU.js} +13 -13
  101. package/{settingSlice-2I5HMG47.js → settingSlice-CK5YZGNI.js} +7 -8
  102. package/{skillDiscovery-UGSQGX2U.js → skillDiscovery-ZYHSX7VD.js} +3 -3
  103. package/{spaceCommands-MT7BWCIL.js → spaceCommands-6O5EFEM5.js} +37 -29
  104. package/{spaceSlice-BS5TSW3W.js → spaceSlice-4EXTYXS4.js} +28 -27
  105. package/{streamAgentChatTurn-I3VBFNR3.js → streamAgentChatTurn-Q3FJAHG2.js} +222 -128
  106. package/{toolApiClient-C7P7YSE4.js → toolApiClient-QI3YBNWB.js} +2 -2
  107. package/{tools-XSD2BOEN.js → tools-AHNQE4GP.js} +34 -34
  108. package/{tuiSummaryLlmCaller-32NRHTT2.js → tuiSummaryLlmCaller-TXLF4MEI.js} +8 -6
  109. package/{updateCommands-VOSPKXFF.js → updateCommands-XZDOOYFH.js} +13 -5
  110. package/{updateDialogTitleAction-7FYNJOGP.js → updateDialogTitleAction-XZDVLKVX.js} +29 -29
  111. package/chunk-5MMAK75Q.js +0 -15
  112. package/chunk-AZPCASRE.js +0 -36
  113. package/chunk-BBYVOSU2.js +0 -792
  114. package/chunk-DCOA4QSN.js +0 -105
  115. package/chunk-UAD2WT54.js +0 -64
  116. package/{chunk-JK7VW2FN.js → chunk-5MIDPSSK.js} +3 -3
  117. package/{chunk-5CS33IBL.js → chunk-PDL2AV3E.js} +3 -3
  118. package/{chunk-5MMZUWSG.js → chunk-TOILAPLH.js} +5 -5
  119. package/{chunk-OOM6SJNR.js → chunk-TSUJOM6N.js} +6 -6
  120. package/{db-6QED3ZVO.js → db-HCJ5PRB5.js} +3 -3
  121. package/{offlineMarxistsAgentCommand-BCPJITJL.js → offlineMarxistsAgentCommand-5OOWZLJI.js} +3 -3
  122. package/{tableCommands-M3DJUETS.js → tableCommands-E4SQWDOX.js} +6 -6
  123. package/{understanding-ZOESBLF3.js → understanding-3CUQQ6SV.js} +3 -3
@@ -3,55 +3,81 @@ import {
3
3
  normalizeDialogRecord,
4
4
  readDialogSnapshot,
5
5
  sortDialogs
6
- } from "./chunk-SV5MWASH.js";
6
+ } from "./chunk-4EH7AR52.js";
7
7
  import {
8
+ ANSI_ESCAPE_REGEX,
9
+ CLI_AUTO_TIER_AGENT_KEYS,
8
10
  CLI_AUTO_TIER_AGENT_KEY_TABLE,
9
11
  CLI_IMAGE_AGENT_KEY,
12
+ COLLAPSED_PASTE_PLACEHOLDER_RE,
10
13
  THEME_PALETTES,
14
+ allocateCollapsedPaste,
15
+ applyTerminalOutputToText,
11
16
  buildSkillContextBlocks,
12
17
  classifyCliAutoRoute,
18
+ clearCollapsedPasteStore,
19
+ countPhysicalLines,
20
+ createCollapsedPasteStore,
13
21
  dimCliText,
22
+ displayWidth,
23
+ expandRangeToCollapsedPasteChips,
24
+ findCollapsedPasteSpanAt,
25
+ fitAnsiLine,
14
26
  formatAssistantDisplay,
15
27
  formatTokenCount,
16
28
  getActiveBrightness,
17
29
  getActiveDensity,
18
30
  getActiveThemeName,
19
- getCliLocale,
20
- highlightMarkdown,
21
- initCliLocale,
22
31
  isBalanceExhaustedError,
23
32
  isQuotaExhaustedError,
24
33
  normalizeThinkingDisplayMode,
25
34
  normalizeToolDisplayMode,
26
- parseCliLocale,
35
+ padOrTruncateToWidth,
36
+ releaseCollapsedPaste,
37
+ releaseCollapsedPasteReferences,
27
38
  renderTokenStatus,
39
+ replaceCollapsedPastesWithReferences,
40
+ resolveAgentContextWindow,
41
+ resolveCliAutoFallbackTier,
28
42
  resolveCliColorEnabled,
29
43
  resolveSkillReference,
30
44
  resolveTuiBrightness,
31
45
  runAgentTurn,
32
46
  setActiveBrightness,
33
47
  setActiveDensity,
48
+ setActiveTerminalBaseHex,
34
49
  setActiveThemeName,
35
- setCliLocale,
50
+ shouldCollapsePaste,
51
+ stripAnsi,
36
52
  surfaceBackgroundSequence,
37
- t,
53
+ takeDisplayWidth,
38
54
  themeColorSequence,
39
- themeText
40
- } from "./chunk-ZEYDOXHF.js";
55
+ themeText,
56
+ truncateAnsi,
57
+ visibleWidth,
58
+ wrapTextToLines,
59
+ wrapTranscriptLine
60
+ } from "./chunk-ZPFRLGIZ.js";
61
+ import "./chunk-3QAF2LTL.js";
41
62
  import "./chunk-6HYVJV7B.js";
42
63
  import {
43
64
  listFavoriteAgentIdsAcrossServers,
44
65
  listLocalCachedAgents,
45
66
  listRemoteAgents,
46
67
  listRemoteAgentsAcrossServers
47
- } from "./chunk-LAX2JSTX.js";
68
+ } from "./chunk-DE5BA4O6.js";
48
69
  import "./chunk-ID4G2MAR.js";
49
70
  import {
50
71
  listUserRecordsFromServers
51
- } from "./chunk-DWGAEFJL.js";
52
- import "./chunk-3QAF2LTL.js";
53
- import "./chunk-X2S7JTIG.js";
54
- import "./chunk-5MMZUWSG.js";
72
+ } from "./chunk-OPKKB25T.js";
73
+ import {
74
+ createCliLocalRuntimeAdapter,
75
+ initCliLocale,
76
+ parseCliLocale,
77
+ setCliLocale,
78
+ t
79
+ } from "./chunk-5ITRDSHY.js";
80
+ import "./chunk-TOILAPLH.js";
55
81
  import "./chunk-CMKUBIGW.js";
56
82
  import "./chunk-JBTJAPUF.js";
57
83
  import {
@@ -60,135 +86,148 @@ import {
60
86
  import {
61
87
  queryUserRecords,
62
88
  readDbRecord
63
- } from "./chunk-AEZAORLG.js";
64
- import "./chunk-C2MDQQ4O.js";
89
+ } from "./chunk-QCA3EPYK.js";
90
+ import "./chunk-RNQ5BLR2.js";
65
91
  import {
66
92
  resolveCliAgentKeyInput
67
93
  } from "./chunk-VHBWHNVT.js";
68
94
  import "./chunk-LTRXGFMS.js";
69
- import "./chunk-5OIFWVX7.js";
95
+ import "./chunk-UG3WOFU6.js";
70
96
  import "./chunk-ATNTHIZI.js";
71
97
  import "./chunk-JEXVIMYZ.js";
72
- import "./chunk-PXQN7IBQ.js";
73
- import "./chunk-FXXSGWAJ.js";
74
- import "./chunk-2W2VYVI5.js";
75
- import "./chunk-D6OQHXVG.js";
98
+ import {
99
+ prepareTools
100
+ } from "./chunk-YTQLJYLB.js";
101
+ import "./chunk-4GBNEA5B.js";
102
+ import {
103
+ expandEnabledPacks
104
+ } from "./chunk-RZ735TLX.js";
105
+ import "./chunk-YDCZVUFP.js";
106
+ import "./chunk-OOIN7HSO.js";
76
107
  import "./chunk-KQHGZWSV.js";
77
- import "./chunk-QNMFBBZK.js";
108
+ import "./chunk-IVAOVLM7.js";
78
109
  import "./chunk-3LY4F7SV.js";
79
110
  import "./chunk-VP4WJWKW.js";
80
111
  import "./chunk-63W5N6P3.js";
81
112
  import "./chunk-VF4K2QE6.js";
82
- import "./chunk-XYZA7S7V.js";
83
- import "./chunk-E4HNJSHC.js";
113
+ import "./chunk-MEVCDATU.js";
114
+ import "./chunk-G3B356GF.js";
84
115
  import "./chunk-VC6OZXO5.js";
85
116
  import "./chunk-OWOHNGCN.js";
86
117
  import {
87
- DEFAULT_CONTEXT_WINDOW,
88
- extractReferenceKeysFromMessage,
89
- getModelContextWindow,
90
- planCompression,
91
- serializeMessageContent
92
- } from "./chunk-5MOUAAMK.js";
118
+ extractReferenceKeysFromMessage
119
+ } from "./chunk-AM53Q2SD.js";
93
120
  import {
94
121
  buildBuiltinSummaryContent
95
- } from "./chunk-4ZDM73SD.js";
122
+ } from "./chunk-V2J635L5.js";
96
123
  import "./chunk-MRC4Q34M.js";
97
124
  import "./chunk-MTU75CZ2.js";
98
125
  import "./chunk-CWMSB67P.js";
99
- import "./chunk-Q3UKJVU4.js";
100
- import "./chunk-GBDSZATH.js";
126
+ import "./chunk-GPZM7QH6.js";
127
+ import "./chunk-6LMFFG3K.js";
101
128
  import "./chunk-Z4NR32XR.js";
102
129
  import "./chunk-M76ECQRD.js";
103
- import "./chunk-MQI77IRB.js";
104
- import "./chunk-3NSIQZZ6.js";
105
- import "./chunk-Y5GCAMGP.js";
106
- import "./chunk-SOTZ6RDN.js";
107
- import "./chunk-7D3JY6C2.js";
108
- import "./chunk-UAD2WT54.js";
109
- import "./chunk-O26GI2JP.js";
110
- import "./chunk-AZPCASRE.js";
111
- import "./chunk-DCOA4QSN.js";
112
- import "./chunk-HNW7IJSK.js";
113
- import "./chunk-XENYHRUE.js";
114
- import "./chunk-IPYUUY4E.js";
115
- import {
116
- runSelfUpdate
117
- } from "./chunk-CKOJQDKR.js";
118
- import {
119
- saveProfileAgentSelection,
120
- saveProfileLocale
121
- } from "./chunk-ROWQEJRP.js";
122
- import {
123
- readPipeText,
124
- spawnProcess
125
- } from "./chunk-IDZTNRP2.js";
126
130
  import {
127
131
  buildSkillDiscoveryContextBlock
128
- } from "./chunk-AFON4QUU.js";
129
- import "./chunk-C4FTUXVF.js";
130
- import "./chunk-KKY4B2Q2.js";
132
+ } from "./chunk-3QKO4EHV.js";
131
133
  import "./chunk-WZSRQNQG.js";
132
134
  import {
135
+ buildLocalWorkspaceOpenAiTools,
136
+ buildLocalWorkspaceToolset,
133
137
  getProcessRegistry
134
- } from "./chunk-BX6U3ETE.js";
135
- import "./chunk-ZPRK73XY.js";
138
+ } from "./chunk-DRMGJFGQ.js";
136
139
  import "./chunk-ZNFJWEN4.js";
137
- import "./chunk-YOCHZD7D.js";
140
+ import "./chunk-ZR4VHQ5D.js";
141
+ import "./chunk-NCGFB7NN.js";
138
142
  import {
139
143
  readCommandActionGatePayload,
140
144
  resolveAgentImageInputSupport
141
- } from "./chunk-S3GS2PRL.js";
145
+ } from "./chunk-4BTHUYMY.js";
142
146
  import "./chunk-OPZYGHUC.js";
143
- import "./chunk-OUI3ZHYD.js";
144
- import "./chunk-YTBMA7NO.js";
145
- import "./chunk-4SOKXIW4.js";
146
147
  import {
147
- clipCompactText
148
- } from "./chunk-5MMAK75Q.js";
149
- import "./chunk-BBYVOSU2.js";
150
- import "./chunk-5CS33IBL.js";
148
+ buildCurrentTimeBlock,
149
+ buildIdentityBlock,
150
+ buildRuntimeGuidanceBlocks
151
+ } from "./chunk-6OVCRC72.js";
152
+ import "./chunk-TSKWE74T.js";
153
+ import {
154
+ canonicalizeToolNames
155
+ } from "./chunk-43UD5ZP7.js";
156
+ import {
157
+ DEFAULT_CONTEXT_WINDOW,
158
+ clipCompactText,
159
+ estimateTokenCount,
160
+ getModelContextWindow,
161
+ planCompression,
162
+ serializeMessageContent
163
+ } from "./chunk-S73OEOHH.js";
164
+ import "./chunk-S3UTETIC.js";
165
+ import "./chunk-PDL2AV3E.js";
151
166
  import "./chunk-JZPBVBIY.js";
152
- import "./chunk-KX6YBIHG.js";
153
- import "./chunk-IWX4UW2E.js";
154
- import "./chunk-O4BU24WZ.js";
155
- import "./chunk-I42VC5KR.js";
156
- import "./chunk-WWC7I6TA.js";
157
- import "./chunk-JK7VW2FN.js";
167
+ import "./chunk-BYXBJQAS.js";
168
+ import "./chunk-QLUFR47U.js";
169
+ import "./chunk-KKY4B2Q2.js";
170
+ import "./chunk-5MIDPSSK.js";
158
171
  import {
159
172
  extractCustomId
160
173
  } from "./chunk-DFFIZ25G.js";
161
174
  import "./chunk-3ASDTTEI.js";
175
+ import "./chunk-4ON5AV72.js";
162
176
  import {
163
- asTrimmedLowercaseString
164
- } from "./chunk-QSJ3HFEF.js";
177
+ toErrorMessage
178
+ } from "./chunk-HNMXYYJR.js";
179
+ import "./chunk-H33743XH.js";
180
+ import "./chunk-LM6IYQ3F.js";
181
+ import "./chunk-SOTZ6RDN.js";
182
+ import "./chunk-HNW7IJSK.js";
183
+ import "./chunk-XENYHRUE.js";
165
184
  import {
166
185
  compactWhitespace
167
186
  } from "./chunk-DWLEOOGR.js";
168
- import "./chunk-2FTTDCD4.js";
187
+ import {
188
+ runSelfUpdate
189
+ } from "./chunk-7JJCGV7N.js";
190
+ import {
191
+ saveProfileAgentSelection,
192
+ saveProfileLocale
193
+ } from "./chunk-ROWQEJRP.js";
194
+ import {
195
+ readPipeText,
196
+ spawnProcess
197
+ } from "./chunk-IDZTNRP2.js";
198
+ import "./chunk-IKXL2HZ3.js";
199
+ import "./chunk-524BDL33.js";
200
+ import "./chunk-7MYHLZ3F.js";
201
+ import "./chunk-WTYKCU4W.js";
202
+ import "./chunk-Y5GCAMGP.js";
203
+ import "./chunk-KX6YBIHG.js";
204
+ import "./chunk-3HHTVM6G.js";
205
+ import "./chunk-NITP2JDG.js";
206
+ import "./chunk-WPHL6ALY.js";
207
+ import "./chunk-O4BU24WZ.js";
208
+ import "./chunk-I42VC5KR.js";
209
+ import "./chunk-WWC7I6TA.js";
210
+ import {
211
+ asTrimmedLowercaseString
212
+ } from "./chunk-QSJ3HFEF.js";
213
+ import "./chunk-XHQWJUW4.js";
214
+ import "./chunk-TSUJOM6N.js";
215
+ import "./chunk-OOSFSFMA.js";
216
+ import "./chunk-GBRG75BQ.js";
217
+ import "./chunk-KUG52IAP.js";
218
+ import "./chunk-3EUW3WEO.js";
219
+ import "./chunk-U6VC4MU4.js";
169
220
  import {
170
221
  parseUserIdFromAuthToken,
171
222
  resolveAuthToken,
172
223
  resolveServerCandidates,
173
224
  resolveServerUrl
174
- } from "./chunk-ISNCLGUZ.js";
225
+ } from "./chunk-5NBHQD4D.js";
175
226
  import {
176
227
  DEFAULT_NOLO_SERVER_URL
177
228
  } from "./chunk-OLZBQWZU.js";
178
- import {
179
- toErrorMessage
180
- } from "./chunk-HNMXYYJR.js";
181
- import "./chunk-H33743XH.js";
182
- import "./chunk-LM6IYQ3F.js";
183
- import "./chunk-XFOLQLDN.js";
184
- import "./chunk-OOM6SJNR.js";
185
- import "./chunk-OOSFSFMA.js";
186
- import "./chunk-GBRG75BQ.js";
187
- import "./chunk-3EUW3WEO.js";
188
- import "./chunk-U6VC4MU4.js";
189
- import "./chunk-KUG52IAP.js";
190
- import "./chunk-4SN6J65J.js";
191
229
  import "./chunk-LCBAMY2M.js";
230
+ import "./chunk-4SN6J65J.js";
192
231
  import "./chunk-CF7PE763.js";
193
232
  import {
194
233
  asOptionalTrimmedString
@@ -198,8 +237,81 @@ import "./chunk-4VNS5WPM.js";
198
237
  // packages/cli/tui/readlineWorkspace.ts
199
238
  import { createInterface } from "node:readline";
200
239
  import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
201
- import { existsSync as existsSync2, readFileSync } from "node:fs";
240
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
241
+ import { join as join2 } from "node:path";
242
+
243
+ // packages/cli/client/estimateCliContext.ts
244
+ import { existsSync, readFileSync } from "node:fs";
202
245
  import { join } from "node:path";
246
+ var DEFAULT_CLI_PACKS = ["code", "agent-orchestration", "skills"];
247
+ var DEFAULT_CLI_PLATFORM_TOOLS = [
248
+ "ui_ask_choice",
249
+ "exa_search",
250
+ "fetchWebpage"
251
+ ];
252
+ var DEFAULT_FLASH_PROMPT = "\u4F60\u662F\u5171\u4EAB\u7A7A\u95F4\u91CC\u7684\u9AD8\u6027\u4EF7\u6BD4\u901A\u7528 AI \u52A9\u624B\u3002\u4F18\u5148\u5FEB\u901F\u3001\u76F4\u63A5\u3001\u7A33\u5B9A\u5730\u5B8C\u6210\u4EFB\u52A1\uFF1B\u9700\u8981\u63A8\u7406\u65F6\u4FDD\u6301\u6B65\u9AA4\u6E05\u6670\u3002";
253
+ function readAgentsMd(cwd) {
254
+ for (const name of ["AGENTS.md", "agents.md"]) {
255
+ const path = join(cwd, name);
256
+ if (!existsSync(path)) continue;
257
+ try {
258
+ return readFileSync(path, "utf8");
259
+ } catch {
260
+ return "";
261
+ }
262
+ }
263
+ return "";
264
+ }
265
+ function estimateToolSchemas(toolNames) {
266
+ const toolset = buildLocalWorkspaceToolset({
267
+ declaredToolNames: toolNames,
268
+ exposeShellTools: true
269
+ });
270
+ const workspaceTools = buildLocalWorkspaceOpenAiTools({
271
+ toolNames: toolset.toolNames,
272
+ exposeShellTools: true
273
+ });
274
+ const platformNames = DEFAULT_CLI_PLATFORM_TOOLS.filter(
275
+ (name) => toolNames.includes(name)
276
+ );
277
+ const platformTools = platformNames.length > 0 ? prepareTools([...platformNames]) : [];
278
+ return estimateTokenCount(JSON.stringify([...workspaceTools, ...platformTools]));
279
+ }
280
+ function estimateDefaultCliContextTokens(opts = {}) {
281
+ const cwd = opts.cwd?.trim() || process.cwd();
282
+ const model = opts.model?.trim() || "deepseek-v4-flash";
283
+ const agentName = opts.agentName?.trim() || "DeepSeek V4 Flash";
284
+ const agentKey = opts.agentKey?.trim() || "agent-pub-deepseek-v4-flash";
285
+ const prompt = opts.agentPrompt?.trim() || DEFAULT_FLASH_PROMPT;
286
+ const packTools = canonicalizeToolNames(
287
+ expandEnabledPacks([...DEFAULT_CLI_PACKS], [])
288
+ );
289
+ const toolNames = canonicalizeToolNames([
290
+ ...packTools,
291
+ ...DEFAULT_CLI_PLATFORM_TOOLS
292
+ ]);
293
+ const guidance = buildRuntimeGuidanceBlocks(toolNames);
294
+ const systemParts = [
295
+ prompt,
296
+ buildIdentityBlock({
297
+ agentName,
298
+ agentId: agentKey,
299
+ model
300
+ }),
301
+ guidance.startupProtocol,
302
+ guidance.contextLayerContract,
303
+ guidance.emailRegistrationWorkflow,
304
+ guidance.webResearchToolPolicy,
305
+ buildCurrentTimeBlock(/* @__PURE__ */ new Date()),
306
+ readAgentsMd(cwd),
307
+ buildSkillDiscoveryContextBlock(cwd) ?? ""
308
+ ];
309
+ const systemTokens = estimateTokenCount(
310
+ systemParts.filter((part) => part?.trim()).join("\n\n")
311
+ );
312
+ const toolTokens = estimateToolSchemas(toolNames);
313
+ return Math.max(0, systemTokens + toolTokens);
314
+ }
203
315
 
204
316
  // packages/cli/client/compactDialog.ts
205
317
  import { ulid } from "ulid";
@@ -480,6 +592,12 @@ function renderDialogRow(args, colorEnabled = resolveCliColorEnabled()) {
480
592
  function renderDialogOverflow(text, colorEnabled = resolveCliColorEnabled()) {
481
593
  return themeText(` ${text}`, "chrome", colorEnabled);
482
594
  }
595
+ function renderOverflowAbove(count, colorEnabled = resolveCliColorEnabled()) {
596
+ return renderDialogOverflow(t("dialogMoreAbove", String(count)), colorEnabled);
597
+ }
598
+ function renderOverflowBelow(count, colorEnabled = resolveCliColorEnabled()) {
599
+ return renderDialogOverflow(t("dialogMoreBelow", String(count)), colorEnabled);
600
+ }
483
601
  function renderDialogCommand(text, colorEnabled = resolveCliColorEnabled()) {
484
602
  return themeText(` ${text}`, "danger", colorEnabled);
485
603
  }
@@ -578,7 +696,7 @@ function renderSelectDialog(args) {
578
696
  ];
579
697
  const lines = [...titleLines, ""];
580
698
  if (window.start > 0) {
581
- lines.push(renderDialogOverflow(`\u2191 ${window.start} more`));
699
+ lines.push(renderOverflowAbove(window.start));
582
700
  }
583
701
  for (let index = window.start; index < window.end; index += 1) {
584
702
  const item = args.items[index];
@@ -591,7 +709,7 @@ function renderSelectDialog(args) {
591
709
  );
592
710
  }
593
711
  if (window.end < total) {
594
- lines.push("", renderDialogOverflow(`\u2193 ${total - window.end} more`));
712
+ lines.push("", renderOverflowBelow(total - window.end));
595
713
  }
596
714
  return lines.join("\n");
597
715
  }
@@ -820,7 +938,7 @@ async function runSelectDialog(args) {
820
938
  // packages/cli/tui/confirmDialog.ts
821
939
  var FALLBACK_TERMINAL_WIDTH = 80;
822
940
  var WIDE_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/;
823
- function displayWidth(text) {
941
+ function displayWidth2(text) {
824
942
  let width = 0;
825
943
  for (const char of text) width += WIDE_CHAR.test(char) ? 2 : 1;
826
944
  return width;
@@ -828,9 +946,9 @@ function displayWidth(text) {
828
946
  function truncateCommand(command, maxWidth) {
829
947
  const indent = 2;
830
948
  const budget = Math.max(10, maxWidth - indent);
831
- if (displayWidth(command) <= budget) return command;
949
+ if (displayWidth2(command) <= budget) return command;
832
950
  const marker = ` ${t("dialogConfirmCommandTruncated")}`;
833
- const keep = Math.max(1, budget - displayWidth(marker));
951
+ const keep = Math.max(1, budget - displayWidth2(marker));
834
952
  let kept = "";
835
953
  let width = 0;
836
954
  for (const char of command) {
@@ -860,9 +978,15 @@ async function runConfirmDialog(args) {
860
978
  value: false
861
979
  }
862
980
  ];
863
- const isDestructiveShell = args.request.action === "destructive_shell_command";
864
- const titleText = isDestructiveShell ? t("dialogConfirmTitle") : args.request.title;
865
- const bodyText = isDestructiveShell ? t("dialogConfirmBody") : args.request.body;
981
+ let titleText = args.request.title;
982
+ let bodyText = args.request.body;
983
+ if (args.request.action === "destructive_shell_command") {
984
+ titleText = t("dialogConfirmTitle");
985
+ bodyText = t("dialogConfirmBody");
986
+ } else if (args.request.action === "external_file_access") {
987
+ titleText = t("dialogConfirmExternalFileTitle");
988
+ bodyText = t("dialogConfirmExternalFileBody");
989
+ }
866
990
  const terminalWidth = typeof output.columns === "number" && output.columns > 0 ? output.columns : FALLBACK_TERMINAL_WIDTH;
867
991
  const titleLines = [
868
992
  renderDialogTitle(`${titleText} ${t("dialogConfirmHint")}`)
@@ -1112,19 +1236,23 @@ function renderTabBar(questions, activeIndex, colorEnabled) {
1112
1236
  }
1113
1237
  return colorEnabled ? themeText(` ${label} `, "muted", colorEnabled) : ` ${label} `;
1114
1238
  });
1115
- const submitLabel = "Submit";
1239
+ const submitLabel = t("askChoiceSubmit");
1116
1240
  return tabs.join(" ") + " " + (colorEnabled ? themeText(submitLabel, "chrome", colorEnabled) : submitLabel);
1117
1241
  }
1118
- function renderFooter(colorEnabled) {
1119
- const hints = "\u21B5 pick/submit \xB7 space toggle \xB7 tab switch \xB7 esc cancel";
1242
+ function renderFooter(multiSelect, colorEnabled) {
1243
+ const hints = multiSelect ? t("askChoiceFooterMulti") : t("askChoiceFooterSingle");
1120
1244
  return colorEnabled ? themeText(` ${hints}`, "chrome", colorEnabled) : ` ${hints}`;
1121
1245
  }
1246
+ function otherRowPrefix(index) {
1247
+ return ` [${index + 1}] ${t("askChoiceOtherLabel")}: `;
1248
+ }
1122
1249
  function renderAskChoiceFrame(state) {
1123
1250
  const colorEnabled = resolveCliColorEnabled();
1124
1251
  const q = state.questions[state.activeIndex];
1125
1252
  const qs = state.questionStates[state.activeIndex];
1126
1253
  const lines = [];
1127
- lines.push(renderDialogTitle("question"));
1254
+ let otherCursor = null;
1255
+ lines.push(renderDialogTitle(t("askChoiceTitle")));
1128
1256
  lines.push("");
1129
1257
  if (state.questions.length > 1) {
1130
1258
  lines.push(renderTabBar(state.questions, state.activeIndex, colorEnabled));
@@ -1135,11 +1263,11 @@ function renderAskChoiceFrame(state) {
1135
1263
  );
1136
1264
  if (q.multiSelect) {
1137
1265
  lines.push(
1138
- colorEnabled ? themeText(" Space to toggle, Enter to confirm selection.", "muted", colorEnabled) : " Space to toggle, Enter to confirm selection."
1266
+ colorEnabled ? themeText(` ${t("askChoiceHintMulti")}`, "muted", colorEnabled) : ` ${t("askChoiceHintMulti")}`
1139
1267
  );
1140
1268
  } else {
1141
1269
  lines.push(
1142
- colorEnabled ? themeText(" Type your answer, then press Enter to save.", "muted", colorEnabled) : " Type your answer, then press Enter to save."
1270
+ colorEnabled ? themeText(` ${t("askChoiceHintSingle")}`, "muted", colorEnabled) : ` ${t("askChoiceHintSingle")}`
1143
1271
  );
1144
1272
  }
1145
1273
  lines.push("");
@@ -1149,9 +1277,7 @@ function renderAskChoiceFrame(state) {
1149
1277
  total: totalRows
1150
1278
  });
1151
1279
  if (window.start > 0) {
1152
- lines.push(
1153
- colorEnabled ? themeText(` \u2191 ${window.start} more`, "chrome", colorEnabled) : ` \u2191 ${window.start} more`
1154
- );
1280
+ lines.push(renderOverflowAbove(window.start));
1155
1281
  }
1156
1282
  for (let i = window.start; i < window.end; i++) {
1157
1283
  if (i < q.choices.length) {
@@ -1169,25 +1295,29 @@ function renderAskChoiceFrame(state) {
1169
1295
  } else {
1170
1296
  const focused = qs.cursorIndex === i;
1171
1297
  const marker = focused ? DIALOG_CURSOR : " ";
1172
- const otherContent = qs.otherFocused ? `${qs.otherText}\u2588` : qs.otherText || "";
1173
- const row = `${marker} [${i + 1}] Other: ${otherContent}`;
1298
+ const plainPrefix = `${marker}${otherRowPrefix(i)}`;
1299
+ const plainRow = `${plainPrefix}${qs.otherText}`;
1174
1300
  if (focused && colorEnabled) {
1175
1301
  lines.push(
1176
- `\x1B[1m${themeColorSequence("accent")}${row}\x1B[0m`
1302
+ `\x1B[1m${themeColorSequence("accent")}${plainRow}\x1B[0m`
1177
1303
  );
1178
1304
  } else {
1179
- lines.push(row);
1305
+ lines.push(plainRow);
1306
+ }
1307
+ if (qs.otherFocused) {
1308
+ otherCursor = {
1309
+ lineIndex: lines.length - 1,
1310
+ col: displayWidth(plainPrefix + qs.otherText)
1311
+ };
1180
1312
  }
1181
1313
  }
1182
1314
  }
1183
1315
  if (window.end < totalRows) {
1184
- lines.push(
1185
- colorEnabled ? themeText(` \u2193 ${totalRows - window.end} more`, "chrome", colorEnabled) : ` \u2193 ${totalRows - window.end} more`
1186
- );
1316
+ lines.push(renderOverflowBelow(totalRows - window.end));
1187
1317
  }
1188
1318
  lines.push("");
1189
- lines.push(renderFooter(colorEnabled));
1190
- return lines.join("\n");
1319
+ lines.push(renderFooter(q.multiSelect, colorEnabled));
1320
+ return { text: lines.join("\n"), otherCursor };
1191
1321
  }
1192
1322
  async function runAskChoiceDialog(args) {
1193
1323
  const { request } = args;
@@ -1200,9 +1330,6 @@ async function runAskChoiceDialog(args) {
1200
1330
  if (normalized.questions.length === 0) {
1201
1331
  return { kind: "cancelled" };
1202
1332
  }
1203
- const q0 = normalized.questions[0];
1204
- if (normalized.questions.length === 1 && !q0.multiSelect && !q0.allowOther) {
1205
- }
1206
1333
  let state = createInitialAskChoiceState(normalized.questions);
1207
1334
  const output = args.output ?? process.stdout;
1208
1335
  const input = args.input ?? process.stdin;
@@ -1216,9 +1343,15 @@ async function runAskChoiceDialog(args) {
1216
1343
  typeof args.bottomRow === "function" ? args.bottomRow() : args.bottomRow ?? 0
1217
1344
  );
1218
1345
  let lastBottomRow = 0;
1346
+ let unanchoredCursorLift = 0;
1347
+ const restoreUnanchoredCursor = () => {
1348
+ if (unanchoredCursorLift <= 0) return;
1349
+ output.write(`\x1B[${unanchoredCursorLift}B`);
1350
+ unanchoredCursorLift = 0;
1351
+ };
1219
1352
  const paint = () => {
1220
1353
  const frame = renderAskChoiceFrame(state);
1221
- const lines = frame.split("\n");
1354
+ const lines = frame.text.split("\n");
1222
1355
  const lineCount = lines.length;
1223
1356
  const canPosition = outputIsTty(output) && typeof output.write === "function";
1224
1357
  if (bottomAnchored && canPosition) {
@@ -1235,22 +1368,37 @@ async function runAskChoiceDialog(args) {
1235
1368
  }
1236
1369
  lastBottomRow = anchorRow;
1237
1370
  renderedLineCount = lineCount;
1371
+ unanchoredCursorLift = 0;
1372
+ if (frame.otherCursor) {
1373
+ const cursorRow = anchorRow - (lineCount - 1 - frame.otherCursor.lineIndex);
1374
+ if (cursorRow >= 1) {
1375
+ output.write(`\x1B[${cursorRow};${frame.otherCursor.col + 1}H`);
1376
+ }
1377
+ }
1238
1378
  return;
1239
1379
  }
1240
1380
  if (canPosition) {
1381
+ restoreUnanchoredCursor();
1241
1382
  for (let i = 0; i < renderedLineCount; i++) {
1242
1383
  output.write("\x1B[1A\x1B[2K");
1243
1384
  }
1244
- output.write(`${frame}
1385
+ output.write(`${frame.text}
1245
1386
  `);
1246
1387
  renderedLineCount = lineCount;
1388
+ if (frame.otherCursor) {
1389
+ const linesFromBottom = lineCount - frame.otherCursor.lineIndex;
1390
+ output.write(`\x1B[${linesFromBottom}A`);
1391
+ output.write(`\x1B[${frame.otherCursor.col + 1}G`);
1392
+ unanchoredCursorLift = linesFromBottom;
1393
+ }
1247
1394
  return;
1248
1395
  }
1249
1396
  if (typeof output.write === "function") {
1250
- output.write(`${frame}
1397
+ output.write(`${frame.text}
1251
1398
  `);
1252
1399
  }
1253
1400
  renderedLineCount = lineCount;
1401
+ unanchoredCursorLift = 0;
1254
1402
  };
1255
1403
  const resizeTarget = output;
1256
1404
  const onOutputResize = () => paint();
@@ -1278,6 +1426,9 @@ async function runAskChoiceDialog(args) {
1278
1426
  continue;
1279
1427
  }
1280
1428
  let action = null;
1429
+ const qs = state.questionStates[state.activeIndex];
1430
+ const q = state.questions[state.activeIndex];
1431
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
1281
1432
  if (isCancel(sequence)) {
1282
1433
  action = { type: "CANCEL" };
1283
1434
  } else if (sequence === CSI_TAB) {
@@ -1289,11 +1440,17 @@ async function runAskChoiceDialog(args) {
1289
1440
  } else if (isArrowDown(sequence)) {
1290
1441
  action = { type: "MOVE_CURSOR", delta: 1 };
1291
1442
  } else if (sequence === CSI_SPACE) {
1292
- action = { type: "TOGGLE_AT_CURSOR" };
1443
+ if (qs.otherFocused) {
1444
+ action = {
1445
+ type: "SET_OTHER_TEXT",
1446
+ text: qs.otherText + " "
1447
+ };
1448
+ } else if (q.multiSelect) {
1449
+ action = { type: "TOGGLE_AT_CURSOR" };
1450
+ } else if (isOtherRow) {
1451
+ action = { type: "FOCUS_OTHER" };
1452
+ }
1293
1453
  } else if (isSubmit(sequence)) {
1294
- const qs = state.questionStates[state.activeIndex];
1295
- const q = state.questions[state.activeIndex];
1296
- const isOtherRow = qs.cursorIndex >= q.choices.length;
1297
1454
  if (qs.otherFocused) {
1298
1455
  action = { type: "BLUR_OTHER" };
1299
1456
  } else if (isOtherRow && !qs.otherFocused) {
@@ -1304,24 +1461,29 @@ async function runAskChoiceDialog(args) {
1304
1461
  action = { type: "SELECT_AT_CURSOR" };
1305
1462
  }
1306
1463
  } else if (sequence === CSI_BACKSPACE || sequence === CSI_BACKSPACE_ALT) {
1307
- const qs = state.questionStates[state.activeIndex];
1308
1464
  if (qs.otherFocused && qs.otherText.length > 0) {
1465
+ const chars = Array.from(qs.otherText);
1309
1466
  action = {
1310
1467
  type: "SET_OTHER_TEXT",
1311
- text: qs.otherText.slice(0, -1)
1468
+ text: chars.slice(0, -1).join("")
1312
1469
  };
1313
1470
  }
1314
- } else if (sequence.length === 1 && sequence.charCodeAt(0) >= 32) {
1315
- const qs = state.questionStates[state.activeIndex];
1316
- if (qs.otherFocused) {
1317
- action = {
1318
- type: "SET_OTHER_TEXT",
1319
- text: qs.otherText + sequence
1320
- };
1471
+ } else if (sequence.length >= 1 && sequence.charCodeAt(0) >= 32 && !sequence.startsWith("\x1B")) {
1472
+ if (qs.otherFocused || isOtherRow && q.allowOther) {
1473
+ const cleaned = sequence.replace(/[\x00-\x1f\x7f]/g, "");
1474
+ if (cleaned) {
1475
+ if (!qs.otherFocused) {
1476
+ state = askChoiceReducer(state, { type: "FOCUS_OTHER" });
1477
+ }
1478
+ const currentText = state.questionStates[state.activeIndex].otherText;
1479
+ action = {
1480
+ type: "SET_OTHER_TEXT",
1481
+ text: currentText + cleaned
1482
+ };
1483
+ }
1321
1484
  }
1322
1485
  }
1323
1486
  if (action) {
1324
- const prev = state;
1325
1487
  state = askChoiceReducer(state, action);
1326
1488
  if (state.phase !== "active") break;
1327
1489
  if (action.type === "BLUR_OTHER" && state.activeIndex >= state.questions.length - 1 && canSubmit(state)) {
@@ -1345,6 +1507,7 @@ async function runAskChoiceDialog(args) {
1345
1507
  renderedLineCount
1346
1508
  );
1347
1509
  } else {
1510
+ restoreUnanchoredCursor();
1348
1511
  for (let i = 0; i < renderedLineCount; i++) {
1349
1512
  output.write("\x1B[1A\x1B[2K");
1350
1513
  }
@@ -1358,13 +1521,13 @@ async function runAskChoiceDialog(args) {
1358
1521
  }
1359
1522
  if (result.answers.length === 1) {
1360
1523
  const a = result.answers[0];
1524
+ const q = normalized.questions[0];
1525
+ const labels = a.selectedIds.map((id) => q.choices.find((c) => c.id === id)?.label ?? "").filter(Boolean);
1526
+ if (a.otherText) labels.push(a.otherText);
1361
1527
  return {
1362
1528
  kind: "selected",
1363
1529
  userMessage: a.userMessage,
1364
- label: a.selectedIds.map((id) => {
1365
- const q = normalized.questions[0];
1366
- return q.choices.find((c) => c.id === id)?.label ?? "";
1367
- }).join(", ") || a.otherText || ""
1530
+ label: labels.join(", ") || a.otherText || ""
1368
1531
  };
1369
1532
  }
1370
1533
  return {
@@ -1422,8 +1585,13 @@ function createActivityIndicator(deps) {
1422
1585
  let lastActivityAt = 0;
1423
1586
  let frameIndex = 0;
1424
1587
  let timer = null;
1588
+ let stopping = false;
1425
1589
  const elapsedSecFrom = (startedAt) => startedAt > 0 ? Math.max(0, Math.floor((now() - startedAt) / 1e3)) : 0;
1426
1590
  const tick = () => {
1591
+ if (stopping) {
1592
+ deps.onRepaint();
1593
+ return;
1594
+ }
1427
1595
  frameIndex += 1;
1428
1596
  if (explicitLabel === null && !fallbackActive && deps.isTurnActive() && lastActivityAt > 0 && now() - lastActivityAt >= fallbackDelayMs) {
1429
1597
  fallbackActive = true;
@@ -1452,9 +1620,21 @@ function createActivityIndicator(deps) {
1452
1620
  ensureTimer();
1453
1621
  deps.onRepaint();
1454
1622
  };
1623
+ const markStopping = () => {
1624
+ stopping = true;
1625
+ deps.onRepaint();
1626
+ };
1627
+ const isStopping = () => stopping;
1455
1628
  const getView = () => {
1456
1629
  if (!deps.isTurnActive()) return null;
1457
1630
  const frame = ACTIVITY_FRAMES[frameIndex % ACTIVITY_FRAMES.length];
1631
+ if (stopping) {
1632
+ return {
1633
+ frame,
1634
+ label: deps.stoppingLabel(),
1635
+ elapsedSec: 0
1636
+ };
1637
+ }
1458
1638
  if (explicitLabel !== null) {
1459
1639
  return {
1460
1640
  frame,
@@ -1481,8 +1661,9 @@ function createActivityIndicator(deps) {
1481
1661
  fallbackActive = false;
1482
1662
  fallbackStartedAt = 0;
1483
1663
  lastActivityAt = 0;
1664
+ stopping = false;
1484
1665
  };
1485
- return { report, getView, stop };
1666
+ return { report, getView, markStopping, isStopping, stop };
1486
1667
  }
1487
1668
 
1488
1669
  // packages/ai/agent/utils/sortUtils.ts
@@ -1533,12 +1714,12 @@ function sortAgentsFavoriteOwnedPublic(items) {
1533
1714
  // packages/cli/tui/agentCatalog.ts
1534
1715
  var DEFAULT_TUI_AGENT_KEY = "agent-pub-01NOLOAPPBLD000000019KCKT0";
1535
1716
  function formatAgentSourceLabel(entry) {
1536
- if (entry.kind === "platform" || entry.apiSource === "platform") return "\u5E73\u53F0";
1717
+ if (entry.kind === "platform" || entry.apiSource === "platform") return t("agentSourcePlatform");
1537
1718
  if (entry.apiSource === "cli") {
1538
- return entry.cliProvider ? `\u8BA2\u9605(${entry.cliProvider})` : "\u8BA2\u9605";
1719
+ return entry.cliProvider ? `${t("agentSourceSubscription")}(${entry.cliProvider})` : t("agentSourceSubscription");
1539
1720
  }
1540
- if (entry.apiSource === "custom") return "API";
1541
- return "\u5E73\u53F0";
1721
+ if (entry.apiSource === "custom") return t("agentSourceApi");
1722
+ return t("agentSourcePlatform");
1542
1723
  }
1543
1724
  var PLATFORM_AGENTS = [
1544
1725
  {
@@ -1722,13 +1903,25 @@ function findAgentCatalogEntry(entries, rawTarget) {
1722
1903
  if (!target) return null;
1723
1904
  if (/^\d+$/.test(target)) {
1724
1905
  const entry = entries[Number(target) - 1];
1725
- return entry ? { name: entry.name, key: entry.key } : null;
1906
+ return entry ? {
1907
+ name: entry.name,
1908
+ key: entry.key,
1909
+ model: entry.model,
1910
+ ...entry.apiSource ? { apiSource: entry.apiSource } : entry.kind === "platform" ? { apiSource: "platform" } : {}
1911
+ } : null;
1726
1912
  }
1727
1913
  const lower = target.toLowerCase();
1728
1914
  const byName = entries.find(
1729
1915
  (entry) => entry.name.toLowerCase() === lower || entry.key.toLowerCase() === lower || entry.key.toLowerCase().endsWith(`-${lower}`)
1730
1916
  );
1731
- if (byName) return { name: byName.name, key: byName.key };
1917
+ if (byName) {
1918
+ return {
1919
+ name: byName.name,
1920
+ key: byName.key,
1921
+ model: byName.model,
1922
+ ...byName.apiSource ? { apiSource: byName.apiSource } : byName.kind === "platform" ? { apiSource: "platform" } : {}
1923
+ };
1924
+ }
1732
1925
  if (target.startsWith("agent-") || target.startsWith("agent-pub-")) {
1733
1926
  return { name: target, key: target };
1734
1927
  }
@@ -1787,6 +1980,8 @@ async function runAgentPicker(args) {
1787
1980
  kind: "selected",
1788
1981
  name: selected.name,
1789
1982
  key: selected.key,
1983
+ model: selected.model,
1984
+ apiSource: selected.apiSource ?? (selected.kind === "platform" ? "platform" : void 0),
1790
1985
  entries
1791
1986
  };
1792
1987
  }
@@ -1796,7 +1991,11 @@ function resolveAgentSwitchTarget(rawTarget, catalogEntries = []) {
1796
1991
  const aliasEntry = catalogEntries.find((entry) => entry.key === resolvedKey);
1797
1992
  return {
1798
1993
  name: aliasEntry?.name ?? asTrimmedLowercaseString(rawTarget),
1799
- key: resolvedKey
1994
+ key: resolvedKey,
1995
+ ...aliasEntry?.model ? { model: aliasEntry.model } : {},
1996
+ ...aliasEntry ? {
1997
+ ...aliasEntry.apiSource ? { apiSource: aliasEntry.apiSource } : aliasEntry.kind === "platform" ? { apiSource: "platform" } : {}
1998
+ } : {}
1800
1999
  };
1801
2000
  }
1802
2001
  return findAgentCatalogEntry(catalogEntries, rawTarget);
@@ -1809,10 +2008,10 @@ function formatDialogTimestamp(value, now = Date.now()) {
1809
2008
  const time = typeof value === "number" ? value : Date.parse(value);
1810
2009
  if (!Number.isFinite(time) || time <= 0) return "";
1811
2010
  const deltaSeconds = Math.max(0, Math.floor((now - time) / 1e3));
1812
- if (deltaSeconds < 60) return "just now";
1813
- if (deltaSeconds < 3600) return `${Math.floor(deltaSeconds / 60)}m ago`;
1814
- if (deltaSeconds < 86400) return `${Math.floor(deltaSeconds / 3600)}h ago`;
1815
- if (deltaSeconds < 30 * 86400) return `${Math.floor(deltaSeconds / 86400)}d ago`;
2011
+ if (deltaSeconds < 60) return t("timeJustNow");
2012
+ if (deltaSeconds < 3600) return t("timeMinutesAgo", String(Math.floor(deltaSeconds / 60)));
2013
+ if (deltaSeconds < 86400) return t("timeHoursAgo", String(Math.floor(deltaSeconds / 3600)));
2014
+ if (deltaSeconds < 30 * 86400) return t("timeDaysAgo", String(Math.floor(deltaSeconds / 86400)));
1816
2015
  const date = new Date(time);
1817
2016
  const pad = (part) => String(part).padStart(2, "0");
1818
2017
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
@@ -1829,7 +2028,7 @@ function toDialogPickerItems(dialogs) {
1829
2028
  }
1830
2029
  function renderDialogList(dialogs) {
1831
2030
  if (dialogs.length === 0) return t("noDialogsYet");
1832
- const lines = ["Recent dialogs:"];
2031
+ const lines = [t("recentDialogs")];
1833
2032
  for (const [index, dialog] of dialogs.entries()) {
1834
2033
  const stamp = formatDialogTimestamp(dialog.updatedAt ?? dialog.createdAt);
1835
2034
  lines.push(
@@ -1837,7 +2036,7 @@ function renderDialogList(dialogs) {
1837
2036
  ` id=${dialog.id}`
1838
2037
  );
1839
2038
  }
1840
- lines.push("", "Tip: run /history to pick one interactively, or paste an id after /resume.");
2039
+ lines.push("", t("dialogListTip"));
1841
2040
  return lines.join("\n");
1842
2041
  }
1843
2042
  async function fetchRecentDialogs(args) {
@@ -1918,7 +2117,7 @@ async function runDialogPicker(args) {
1918
2117
  }
1919
2118
 
1920
2119
  // packages/cli/tui/pasteImage.ts
1921
- import { existsSync } from "node:fs";
2120
+ import { existsSync as existsSync2 } from "node:fs";
1922
2121
  import { readFile, stat } from "node:fs/promises";
1923
2122
  import { homedir } from "node:os";
1924
2123
  import { isAbsolute, resolve } from "node:path";
@@ -1973,7 +2172,7 @@ function detectImagePaths(line, cwd) {
1973
2172
  if (!token.raw) continue;
1974
2173
  const resolved = resolveImageSource(token.decoded, cwd);
1975
2174
  if (!isImagePath(resolved)) continue;
1976
- if (!existsSync(resolved)) continue;
2175
+ if (!existsSync2(resolved)) continue;
1977
2176
  out.push({ raw: token.raw, resolvedPath: resolved });
1978
2177
  }
1979
2178
  return out;
@@ -2156,302 +2355,48 @@ async function readImagePaths(paths, options = {}) {
2156
2355
  }
2157
2356
 
2158
2357
  // packages/cli/tui/gitStatus.ts
2159
- import { execSync } from "node:child_process";
2160
- function detectGitStatus(cwd) {
2161
- try {
2162
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
2163
- cwd,
2164
- encoding: "utf8",
2165
- timeout: 2e3,
2166
- stdio: ["pipe", "pipe", "pipe"]
2167
- }).trim();
2168
- if (!branch) return void 0;
2169
- const status = execSync("git status --porcelain", {
2170
- cwd,
2171
- encoding: "utf8",
2172
- timeout: 2e3,
2173
- stdio: ["pipe", "pipe", "pipe"]
2174
- }).trim();
2175
- if (!status) {
2176
- return { branch, modified: 0, untracked: 0 };
2177
- }
2178
- let modified = 0;
2179
- let untracked = 0;
2180
- for (const line of status.split("\n")) {
2181
- if (!line) continue;
2182
- const flag = line[0];
2183
- if (flag === "?") untracked += 1;
2184
- else modified += 1;
2185
- }
2186
- return { branch, modified, untracked };
2187
- } catch {
2188
- return void 0;
2189
- }
2190
- }
2191
-
2192
- // packages/cli/tui/tuiAnsi.ts
2193
- var ANSI_ESCAPE_REGEX = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
2194
- var OSC_HYPERLINK_REGEX = /\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)\x1b\]8;;(?:\x07|\x1b\\)|\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
2195
- function stripAnsi(text) {
2196
- return text.replace(OSC_HYPERLINK_REGEX, "").replace(ANSI_ESCAPE_REGEX, "");
2197
- }
2198
- var SGR_SEQUENCE_REGEX = /^\x1b\[[0-9;]*m/;
2199
- var TRAILING_SGR_REGEX = /(?:\x1b\[[0-9;]*m)+$/;
2200
- function applyTerminalOutputToText(existing, chunk) {
2201
- if (!chunk) return existing;
2202
- let text = existing;
2203
- let index = 0;
2204
- while (index < chunk.length) {
2205
- if (chunk[index] === "\x1B") {
2206
- const sgr = SGR_SEQUENCE_REGEX.exec(chunk.slice(index));
2207
- if (sgr) {
2208
- text += sgr[0];
2209
- index += sgr[0].length;
2210
- continue;
2211
- }
2212
- const osc = chunk.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
2213
- if (osc) {
2214
- index += osc[0].length;
2215
- continue;
2216
- }
2217
- const csi = chunk.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
2218
- if (csi) {
2219
- index += csi[0].length;
2220
- continue;
2221
- }
2222
- index += 1;
2223
- continue;
2224
- }
2225
- const ch = chunk[index];
2226
- if (ch === "\r") {
2227
- const lastNl = text.lastIndexOf("\n");
2228
- text = lastNl === -1 ? "" : text.slice(0, lastNl + 1);
2229
- index += 1;
2230
- continue;
2231
- }
2232
- if (ch === "\n") {
2233
- text += "\n";
2234
- index += 1;
2235
- continue;
2236
- }
2237
- if (ch === "\b") {
2238
- const trailing = TRAILING_SGR_REGEX.exec(text);
2239
- const sgrTail = trailing ? trailing[0] : "";
2240
- const head = sgrTail ? text.slice(0, -sgrTail.length) : text;
2241
- if (head.length > 0 && head[head.length - 1] !== "\n") {
2242
- text = head.slice(0, -1) + sgrTail;
2243
- }
2244
- index += 1;
2245
- continue;
2246
- }
2247
- const code = ch.charCodeAt(0);
2248
- if (code < 32 && ch !== " " || code === 127) {
2249
- index += 1;
2250
- continue;
2251
- }
2252
- text += ch;
2253
- index += 1;
2254
- }
2255
- return text;
2256
- }
2257
- function displayWidth2(str) {
2258
- let width = 0;
2259
- for (const char of str) {
2260
- const code = char.codePointAt(0) ?? 0;
2261
- if (code < 32 || code === 127) continue;
2262
- if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
2263
- // render narrow in terminals; counting them wide drifts the cursor.
2264
- code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
2265
- // status-line agent icon) render double-wide in common monospace fonts;
2266
- // counting ⬢ as width 1 drifts the whole status line right of the icon.
2267
- code >= 11008 && code <= 11263 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141 || // East Asian Ambiguous CJK quotation marks (U+201C/D “ ”, U+2018/9 ‘ ’).
2268
- // In CJK locale + CJK fonts these render double-wide like 全角 punctuation;
2269
- // in English fonts they stay width 1. Follow the active locale so Chinese
2270
- // replies align correctly without breaking English terminal output.
2271
- getCliLocale() === "zh" && (code === 8220 || code === 8221 || code === 8216 || code === 8217)) {
2272
- width += 2;
2273
- } else {
2274
- width += 1;
2275
- }
2276
- }
2277
- return width;
2278
- }
2279
- function visibleWidth(str) {
2280
- return displayWidth2(stripAnsi(str));
2281
- }
2282
- function truncateAnsi(text, maxWidth) {
2283
- if (maxWidth <= 0) return "";
2284
- if (visibleWidth(text) <= maxWidth) return text;
2285
- let width = 0;
2286
- let out = "";
2287
- let i = 0;
2288
- let sawAnsi = false;
2289
- while (i < text.length) {
2290
- if (text[i] === "\x1B" && text[i + 1] === "[") {
2291
- sawAnsi = true;
2292
- let j = i + 2;
2293
- while (j < text.length) {
2294
- const code = text.charCodeAt(j);
2295
- j += 1;
2296
- if (code >= 64 && code <= 126) break;
2297
- }
2298
- out += text.slice(i, j);
2299
- i = j;
2300
- continue;
2301
- }
2302
- const codePoint = text.codePointAt(i) ?? 0;
2303
- const char = String.fromCodePoint(codePoint);
2304
- const charWidth = displayWidth2(char);
2305
- if (width + charWidth > maxWidth) break;
2306
- out += char;
2307
- width += charWidth;
2308
- i += char.length;
2309
- }
2310
- return sawAnsi ? `${out}\x1B[0m` : out;
2311
- }
2312
- function fitAnsiLine(text, width, ellipsis = "\u2026") {
2313
- if (width <= 0) return "";
2314
- if (visibleWidth(text) <= width) return text;
2315
- const ellipsisWidth = displayWidth2(ellipsis);
2316
- if (width < ellipsisWidth) return truncateAnsi(text, width);
2317
- if (width === ellipsisWidth) return truncateAnsi(ellipsis, width) || truncateAnsi(text, width);
2318
- return `${truncateAnsi(text, width - ellipsisWidth)}${ellipsis}`;
2319
- }
2320
- function countPhysicalLines(text, columns) {
2321
- const lines = text.split("\n");
2322
- let total = 0;
2323
- for (const line of lines) {
2324
- const width = displayWidth2(line);
2325
- total += Math.max(1, Math.ceil(width / columns));
2326
- }
2327
- return Math.max(total, 1);
2328
- }
2329
- function takeDisplayWidth(text, width) {
2330
- let used = 0;
2331
- let index = 0;
2332
- for (const char of text) {
2333
- const charWidth = displayWidth2(char);
2334
- if (used + charWidth > width && used > 0) break;
2335
- used += charWidth;
2336
- index += char.length;
2337
- }
2338
- return { prefix: text.slice(0, index), rest: text.slice(index) };
2339
- }
2340
- function padOrTruncateToWidth(text, width) {
2341
- const textWidth = visibleWidth(text);
2342
- if (textWidth > width) {
2343
- return truncateAnsi(text, width);
2344
- }
2345
- return `${text}${" ".repeat(width - textWidth)}`;
2346
- }
2347
- var SGR_RESET_REGEX = /^\x1b\[0?m$/;
2348
- function tokenizeAnsiLine(line) {
2349
- const tokens = [];
2350
- let index = 0;
2351
- while (index < line.length) {
2352
- if (line[index] === "\x1B") {
2353
- const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
2354
- if (osc) {
2355
- tokens.push({ kind: "sgr", value: osc[0], width: 0 });
2356
- index += osc[0].length;
2357
- continue;
2358
- }
2359
- const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
2360
- if (sgr) {
2361
- tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
2362
- index += sgr[0].length;
2363
- continue;
2364
- }
2365
- }
2366
- const codePoint = line.codePointAt(index) ?? 0;
2367
- const value = String.fromCodePoint(codePoint);
2368
- tokens.push({ kind: "char", value, width: displayWidth2(value) });
2369
- index += value.length;
2370
- }
2371
- return tokens;
2372
- }
2373
- function wrapTranscriptLine(line, columns) {
2374
- if (line === "") return [""];
2375
- const tokens = tokenizeAnsiLine(line);
2376
- const result = [];
2377
- let activeStyles = [];
2378
- const applyStyleToken = (value) => {
2379
- if (SGR_RESET_REGEX.test(value)) {
2380
- activeStyles = [];
2381
- } else {
2382
- activeStyles.push(value);
2383
- }
2384
- };
2385
- let start = 0;
2386
- while (start < tokens.length) {
2387
- if (tokens.slice(start).every((token) => token.kind === "sgr")) {
2388
- if (result.length > 0) break;
2389
- }
2390
- const openingStyles = [...activeStyles];
2391
- let width = 0;
2392
- let end = start;
2393
- let lastBreak = -1;
2394
- while (end < tokens.length) {
2395
- const token = tokens[end];
2396
- if (token.kind === "sgr") {
2397
- end += 1;
2398
- continue;
2399
- }
2400
- if (width + token.width > columns && width > 0) break;
2401
- width += token.width;
2402
- end += 1;
2403
- if (token.value === " " || token.value === " ") {
2404
- lastBreak = end;
2405
- }
2406
- }
2407
- let segmentEnd = end;
2408
- if (end < tokens.length && lastBreak > start) {
2409
- const overflowToken = tokens[end];
2410
- if (overflowToken.kind === "char" && overflowToken.value !== " " && overflowToken.width === 1) {
2411
- segmentEnd = lastBreak;
2412
- }
2413
- }
2414
- if (segmentEnd === start) segmentEnd = start + 1;
2415
- let segment = "";
2416
- let sawStyle = openingStyles.length > 0;
2417
- for (let i = start; i < segmentEnd; i += 1) {
2418
- const token = tokens[i];
2419
- segment += token.value;
2420
- if (token.kind === "sgr") {
2421
- sawStyle = true;
2422
- applyStyleToken(token.value);
2423
- }
2424
- }
2425
- const prefix = openingStyles.join("");
2426
- const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
2427
- result.push(`${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`);
2428
- start = segmentEnd;
2429
- while (start < tokens.length) {
2430
- const token = tokens[start];
2431
- if (token.kind === "char" && token.value === " ") {
2432
- start += 1;
2433
- continue;
2358
+ import { execFile, execSync } from "node:child_process";
2359
+ function parseStatusOutput(branch, status) {
2360
+ if (!status) {
2361
+ return { branch, modified: 0, untracked: 0 };
2362
+ }
2363
+ let modified = 0;
2364
+ let untracked = 0;
2365
+ for (const line of status.split("\n")) {
2366
+ if (!line) continue;
2367
+ const flag = line[0];
2368
+ if (flag === "?") untracked += 1;
2369
+ else modified += 1;
2370
+ }
2371
+ return { branch, modified, untracked };
2372
+ }
2373
+ function execFileUtf8(file, args, cwd) {
2374
+ return new Promise((resolve2, reject) => {
2375
+ execFile(
2376
+ file,
2377
+ args,
2378
+ { cwd, encoding: "utf8", timeout: 2e3 },
2379
+ (err, stdout) => {
2380
+ if (err) {
2381
+ reject(err);
2382
+ return;
2383
+ }
2384
+ resolve2(typeof stdout === "string" ? stdout : String(stdout ?? ""));
2434
2385
  }
2435
- break;
2436
- }
2437
- }
2438
- return result.length > 0 ? result : [""];
2386
+ );
2387
+ });
2439
2388
  }
2440
- function wrapTextToLines(text, columns) {
2441
- const result = [];
2442
- for (const logicalLine of text.split("\n")) {
2443
- if (logicalLine === "") {
2444
- result.push("");
2445
- continue;
2446
- }
2447
- let remaining = logicalLine;
2448
- while (remaining.length > 0) {
2449
- const { prefix, rest } = takeDisplayWidth(remaining, columns);
2450
- result.push(prefix);
2451
- remaining = rest;
2389
+ function detectGitStatusAsync(cwd) {
2390
+ return (async () => {
2391
+ try {
2392
+ const branch = (await execFileUtf8("git", ["rev-parse", "--abbrev-ref", "HEAD"], cwd)).trim();
2393
+ if (!branch) return void 0;
2394
+ const status = (await execFileUtf8("git", ["status", "--porcelain"], cwd)).trim();
2395
+ return parseStatusOutput(branch, status);
2396
+ } catch {
2397
+ return void 0;
2452
2398
  }
2453
- }
2454
- return result;
2399
+ })();
2455
2400
  }
2456
2401
 
2457
2402
  // packages/cli/tui/sessionRender.ts
@@ -2468,12 +2413,12 @@ function formatElapsedSeconds(totalSeconds) {
2468
2413
  const mins = minutes % 60;
2469
2414
  return `${hours}h ${mins}m`;
2470
2415
  }
2471
- function renderComposerTokenChip(tokens, fallbackContextWindow) {
2416
+ function renderComposerTokenChip(tokens, fallbackContextWindow, estimatedContextTokens) {
2472
2417
  const cw = tokens?.contextWindow ?? fallbackContextWindow;
2473
2418
  if (!cw) {
2474
2419
  return "context: \u2014";
2475
2420
  }
2476
- const used = tokens ? tokens.input + tokens.output : 0;
2421
+ const used = tokens ? tokens.input + tokens.output : Math.max(0, estimatedContextTokens ?? 0);
2477
2422
  const pct = Math.min(100, used / cw * 100);
2478
2423
  const pctText = pct > 0 && pct < 10 ? pct.toFixed(1) : Math.round(pct).toString();
2479
2424
  return `context: ${pctText}% (${formatTokenCount(used)}/${formatTokenCount(cw)})`;
@@ -2495,7 +2440,17 @@ function renderStatusLine(state) {
2495
2440
  const untrackedText = untracked > 0 ? ` ${themeText(`?${untracked}`, "muted", colorEnabled)}` : "";
2496
2441
  parts.push(`${branchText}${modifiedText}${untrackedText}`);
2497
2442
  }
2498
- const tokenSegment = themeText(renderComposerTokenChip(state.turnTokens, state.contextWindow), "muted", colorEnabled);
2443
+ const credits = state.apiSource === "platform" ? state.turnTokens?.credits : void 0;
2444
+ const creditsSuffix = credits !== void 0 && credits > 0 ? ` \xB7 \u26A1 ${credits.toFixed(2)} \u79EF\u5206` : "";
2445
+ const tokenSegment = themeText(
2446
+ renderComposerTokenChip(
2447
+ state.turnTokens,
2448
+ state.contextWindow,
2449
+ state.estimatedContextTokens
2450
+ ) + creditsSuffix,
2451
+ "muted",
2452
+ colorEnabled
2453
+ );
2499
2454
  parts.push(tokenSegment);
2500
2455
  const runningCount = getProcessRegistry().list().filter((p) => p.status === "running").length;
2501
2456
  if (runningCount > 0) {
@@ -2577,7 +2532,7 @@ function renderWelcome(state, frame = 0, maxFrames = 0, columns) {
2577
2532
  const isDark = brightness === "dark";
2578
2533
  let sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
2579
2534
  if (typeof columns === "number" && columns > 0) {
2580
- const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth2(stripAnsi(line))), 0);
2535
+ const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth(stripAnsi(line))), 0);
2581
2536
  if (columns < widestSceneCol) sceneArt = "";
2582
2537
  }
2583
2538
  const versionLine = colorEnabled ? `\x1B[1m${themeColorSequence("accent")}nolo\x1B[0m ${state.cliVersion ?? ""} | server ${state.serverUrl}`.replace(" |", " |") : `nolo ${state.cliVersion ?? ""} | server ${state.serverUrl}`.replace(" |", " |");
@@ -2627,19 +2582,19 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
2627
2582
  ];
2628
2583
  let maxW = 0;
2629
2584
  for (const l of labels) {
2630
- const w = displayWidth2(l);
2585
+ const w = displayWidth(l);
2631
2586
  if (w > maxW) maxW = w;
2632
2587
  }
2633
2588
  const targetWidth = maxW >= 9 ? maxW + 1 : 9;
2634
2589
  const padLabel = (raw) => {
2635
- const w = displayWidth2(raw);
2590
+ const w = displayWidth(raw);
2636
2591
  const padding = " ".repeat(Math.max(0, targetWidth - w));
2637
2592
  return raw + padding;
2638
2593
  };
2639
2594
  const field = (rawLabel, value) => `${themeText(padLabel(rawLabel), "muted", colorEnabled)}${value}`;
2640
2595
  const next = (command, description) => ` ${themeText(command, "accent", colorEnabled)}${themeText(description, "muted", colorEnabled)}`;
2641
2596
  const titleText = t("contextTitle");
2642
- const heading = colorEnabled ? [renderDialogTitle(titleText, true)] : [titleText, "\u2500".repeat(displayWidth2(titleText))];
2597
+ const heading = colorEnabled ? [renderDialogTitle(titleText, true)] : [titleText, "\u2500".repeat(displayWidth(titleText))];
2643
2598
  return [
2644
2599
  ...heading,
2645
2600
  field(labels[0], `${state.agentName} (${state.agentKey})`),
@@ -2684,14 +2639,21 @@ function renderKnownAgents(colorEnabled = resolveCliColorEnabled()) {
2684
2639
 
2685
2640
  // packages/cli/tui/sessionInput.ts
2686
2641
  var PASTE_TOKEN_PREFIX = "\0PASTE\0";
2687
- function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2642
+ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
2688
2643
  const seq = sequence ?? "";
2689
2644
  const curPos = Math.max(0, Math.min(buffer.length, cursorPos ?? buffer.length));
2645
+ const pasteStore = options?.pasteStore;
2690
2646
  if (seq.startsWith(PASTE_TOKEN_PREFIX)) {
2691
2647
  const rawPayload = seq.slice(PASTE_TOKEN_PREFIX.length);
2692
2648
  const normalized = rawPayload.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2693
- const nextBuf2 = buffer.slice(0, curPos) + normalized + buffer.slice(curPos);
2694
- return { buffer: nextBuf2, cursorPos: curPos + normalized.length };
2649
+ const insertAt2 = snapCursorOutsidePasteChip(buffer, curPos);
2650
+ if (pasteStore && shouldCollapsePaste(normalized)) {
2651
+ const { placeholder } = allocateCollapsedPaste(pasteStore, normalized);
2652
+ const nextBuf3 = buffer.slice(0, insertAt2) + placeholder + buffer.slice(insertAt2);
2653
+ return { buffer: nextBuf3, cursorPos: insertAt2 + placeholder.length };
2654
+ }
2655
+ const nextBuf2 = buffer.slice(0, insertAt2) + normalized + buffer.slice(insertAt2);
2656
+ return { buffer: nextBuf2, cursorPos: insertAt2 + normalized.length };
2695
2657
  }
2696
2658
  if (seq === "" || key.ctrl && key.name === "c") {
2697
2659
  return { buffer, cursorPos: curPos, abort: true };
@@ -2700,16 +2662,25 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2700
2662
  return { buffer, cursorPos: curPos, copyView: true };
2701
2663
  }
2702
2664
  if (seq === "\x1B[13;2~" || seq === "\x1B[27;2;13~" || seq === "\x1B\r" || key.shift && (key.name === "enter" || key.name === "return") || seq === "\n" || key.ctrl && key.name === "j") {
2703
- const nextBuf2 = buffer.slice(0, curPos) + "\n" + buffer.slice(curPos);
2704
- return { buffer: nextBuf2, cursorPos: curPos + 1 };
2665
+ const insertAt2 = snapCursorOutsidePasteChip(buffer, curPos);
2666
+ const nextBuf2 = buffer.slice(0, insertAt2) + "\n" + buffer.slice(insertAt2);
2667
+ return { buffer: nextBuf2, cursorPos: insertAt2 + 1 };
2705
2668
  }
2706
2669
  if (key.name === "enter" || key.name === "return" || seq === "\r") {
2707
2670
  return { buffer: "", cursorPos: 0, submit: buffer };
2708
2671
  }
2709
2672
  if (isLeftArrowSequence(seq, key)) {
2673
+ const span = findCollapsedPasteSpanAt(buffer, curPos, { preferLeft: true });
2674
+ if (span && curPos > span.start && curPos <= span.end) {
2675
+ return { buffer, cursorPos: span.start };
2676
+ }
2710
2677
  return { buffer, cursorPos: Math.max(0, curPos - 1) };
2711
2678
  }
2712
2679
  if (isRightArrowSequence(seq, key)) {
2680
+ const span = findCollapsedPasteSpanAt(buffer, curPos);
2681
+ if (span && curPos >= span.start && curPos < span.end) {
2682
+ return { buffer, cursorPos: span.end };
2683
+ }
2713
2684
  return { buffer, cursorPos: Math.min(buffer.length, curPos + 1) };
2714
2685
  }
2715
2686
  if (isHomeSequence(seq, key)) {
@@ -2720,24 +2691,33 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2720
2691
  }
2721
2692
  if (isDeleteWordSequence(seq, key)) {
2722
2693
  if (curPos === 0) return { buffer, cursorPos: curPos };
2694
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos, {
2695
+ preferLeft: true
2696
+ });
2697
+ if (pasteSpan && (curPos === pasteSpan.end || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2698
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2699
+ }
2723
2700
  const cutIdx = findWordStartLeft(buffer, curPos);
2724
- const nextBuf2 = buffer.slice(0, cutIdx) + buffer.slice(curPos);
2725
- return { buffer: nextBuf2, cursorPos: cutIdx };
2701
+ return deleteBufferRange(buffer, cutIdx, curPos, pasteStore);
2726
2702
  }
2727
2703
  if (isKillToLineStartSequence(seq, key)) {
2728
2704
  if (curPos === 0) return { buffer, cursorPos: curPos };
2729
2705
  const lineStart = buffer.lastIndexOf("\n", curPos - 1) + 1;
2730
- const nextBuf2 = buffer.slice(0, lineStart) + buffer.slice(curPos);
2731
- return { buffer: nextBuf2, cursorPos: lineStart };
2706
+ return deleteBufferRange(buffer, lineStart, curPos, pasteStore);
2732
2707
  }
2733
2708
  if (isKillToLineEndSequence(seq, key)) {
2734
2709
  const nlIdx = buffer.indexOf("\n", curPos);
2735
2710
  const lineEnd = nlIdx === -1 ? buffer.length : nlIdx;
2736
- const nextBuf2 = buffer.slice(0, curPos) + buffer.slice(lineEnd);
2737
- return { buffer: nextBuf2, cursorPos: curPos };
2711
+ return deleteBufferRange(buffer, curPos, lineEnd, pasteStore);
2738
2712
  }
2739
2713
  if (isBackspaceSequence(seq, key)) {
2740
2714
  if (curPos > 0) {
2715
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos, {
2716
+ preferLeft: true
2717
+ });
2718
+ if (pasteSpan && (curPos === pasteSpan.end || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2719
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2720
+ }
2741
2721
  const nextBuf2 = buffer.slice(0, curPos - 1) + buffer.slice(curPos);
2742
2722
  return { buffer: nextBuf2, cursorPos: curPos - 1 };
2743
2723
  }
@@ -2745,6 +2725,10 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2745
2725
  }
2746
2726
  if (isForwardDeleteSequence(seq, key)) {
2747
2727
  if (curPos < buffer.length) {
2728
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos);
2729
+ if (pasteSpan && (curPos === pasteSpan.start || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2730
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2731
+ }
2748
2732
  const nextBuf2 = buffer.slice(0, curPos) + buffer.slice(curPos + 1);
2749
2733
  return { buffer: nextBuf2, cursorPos: curPos };
2750
2734
  }
@@ -2761,8 +2745,14 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2761
2745
  if (!seq || key.ctrl || key.meta || seq.startsWith("\x1B")) {
2762
2746
  return { buffer, cursorPos: curPos };
2763
2747
  }
2764
- const nextBuf = buffer.slice(0, curPos) + seq + buffer.slice(curPos);
2765
- return { buffer: nextBuf, cursorPos: curPos + seq.length };
2748
+ const insertAt = snapCursorOutsidePasteChip(buffer, curPos);
2749
+ const nextBuf = buffer.slice(0, insertAt) + seq + buffer.slice(insertAt);
2750
+ return { buffer: nextBuf, cursorPos: insertAt + seq.length };
2751
+ }
2752
+ function snapCursorOutsidePasteChip(buffer, pos) {
2753
+ const span = findCollapsedPasteSpanAt(buffer, pos);
2754
+ if (span && pos > span.start && pos < span.end) return span.end;
2755
+ return pos;
2766
2756
  }
2767
2757
  function isLeftArrowSequence(seq, key) {
2768
2758
  if (key.name === "left") return true;
@@ -2786,13 +2776,44 @@ function isEndSequence(seq, key) {
2786
2776
  if (seq === "" || seq === "\x1B[F" || seq === "\x1B[4~" || seq === "\x1B[8~" || seq === "\x1BOF") return true;
2787
2777
  return false;
2788
2778
  }
2779
+ function removePasteSpan(buffer, span, pasteStore) {
2780
+ if (pasteStore) releaseCollapsedPaste(pasteStore, span.id);
2781
+ const nextBuf = buffer.slice(0, span.start) + buffer.slice(span.end);
2782
+ return { buffer: nextBuf, cursorPos: span.start };
2783
+ }
2784
+ function deleteBufferRange(buffer, start, end, pasteStore) {
2785
+ if (start >= end) return { buffer, cursorPos: start };
2786
+ const range = expandRangeToCollapsedPasteChips(buffer, start, end);
2787
+ releasePastesInRange(buffer, range.start, range.end, pasteStore);
2788
+ const nextBuf = buffer.slice(0, range.start) + buffer.slice(range.end);
2789
+ return { buffer: nextBuf, cursorPos: range.start };
2790
+ }
2791
+ function releasePastesInRange(buffer, start, end, pasteStore) {
2792
+ if (!pasteStore || start >= end) return;
2793
+ const slice = buffer.slice(start, end);
2794
+ const re = /\[paste #(\d+) · (\d+) lines\]/g;
2795
+ let match;
2796
+ while ((match = re.exec(slice)) !== null) {
2797
+ releaseCollapsedPaste(pasteStore, Number(match[1]));
2798
+ }
2799
+ }
2789
2800
  var CJK_CHAR = /[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
2790
2801
  function findWordStartLeft(buffer, curPos) {
2791
2802
  let i = curPos;
2792
2803
  while (i > 0 && /\s/.test(buffer[i - 1])) i--;
2793
2804
  if (i === 0) return 0;
2805
+ const spanAtCursor = findCollapsedPasteSpanAt(buffer, i, {
2806
+ preferLeft: true
2807
+ });
2808
+ if (spanAtCursor && (i === spanAtCursor.end || i > spanAtCursor.start && i < spanAtCursor.end)) {
2809
+ return spanAtCursor.start;
2810
+ }
2794
2811
  if (CJK_CHAR.test(buffer[i - 1])) return i - 1;
2795
- while (i > 0 && !/\s/.test(buffer[i - 1]) && !CJK_CHAR.test(buffer[i - 1])) i--;
2812
+ while (i > 0 && !/\s/.test(buffer[i - 1]) && !CJK_CHAR.test(buffer[i - 1])) {
2813
+ const span = findCollapsedPasteSpanAt(buffer, i, { preferLeft: true });
2814
+ if (span && i === span.end) break;
2815
+ i--;
2816
+ }
2796
2817
  return i;
2797
2818
  }
2798
2819
  function isDeleteWordSequence(seq, key) {
@@ -2903,6 +2924,7 @@ function createInitialTuiState(env = process.env) {
2903
2924
  ) || void 0;
2904
2925
  const dialogEnvValue = asOptionalTrimmedString(env.NOLO_DIALOG);
2905
2926
  const explicitDialogKey = asOptionalTrimmedString(env.NOLO_DIALOG_KEY) ?? (dialogEnvValue?.startsWith("dialog-") ? dialogEnvValue : void 0);
2927
+ const autoRouteDefault = env.NOLO_AUTO_ROUTE !== "0";
2906
2928
  return {
2907
2929
  agentKey,
2908
2930
  agentName,
@@ -2922,22 +2944,47 @@ function createInitialTuiState(env = process.env) {
2922
2944
  attachedImages: [],
2923
2945
  runtimeMode,
2924
2946
  modeLabel: asOptionalTrimmedString(env.NOLO_CLI_STATUS_MODE) ?? runtimeMode,
2925
- gitStatus: env.NOLO_CLI_GIT_STATUS === "0" ? void 0 : detectGitStatus(cwd),
2947
+ gitStatus: void 0,
2926
2948
  thinkingDisplay: normalizeThinkingDisplayMode(
2927
2949
  env.NOLO_CLI_THINKING ?? env.NOLO_THINKING,
2928
2950
  "hide"
2929
2951
  ),
2930
2952
  toolDisplay: normalizeToolDisplayMode(env.NOLO_CLI_TOOLS ?? env.NOLO_TOOLS, "compact"),
2931
- contextWindow: getModelContextWindow(agentName)
2953
+ contextWindow: resolveAgentContextWindow({
2954
+ agentKey,
2955
+ agentName,
2956
+ autoRouteDefault
2957
+ }),
2958
+ estimatedContextTokens: estimateDefaultCliContextTokens({
2959
+ cwd,
2960
+ agentKey,
2961
+ agentName: autoRouteDefault ? "DeepSeek V4 Flash" : agentName,
2962
+ model: autoRouteDefault ? "deepseek-v4-flash" : void 0
2963
+ }),
2964
+ apiSource: PLATFORM_AGENTS.some((entry) => entry.key === agentKey) ? "platform" : void 0
2932
2965
  };
2933
2966
  }
2934
- function applyAgentSwitch(state, target) {
2967
+ function applyAgentSwitch(state, target, opts) {
2968
+ const autoRouteDefault = opts?.autoRouteDefault ?? process.env.NOLO_AUTO_ROUTE !== "0";
2969
+ const contextWindow = resolveAgentContextWindow({
2970
+ agentKey: target.key,
2971
+ agentName: target.name,
2972
+ model: target.model,
2973
+ autoRouteDefault
2974
+ });
2935
2975
  return {
2936
2976
  nextState: {
2937
2977
  ...state,
2938
2978
  agentName: target.name,
2939
2979
  agentKey: target.key,
2940
- contextWindow: getModelContextWindow(target.name)
2980
+ contextWindow,
2981
+ estimatedContextTokens: estimateDefaultCliContextTokens({
2982
+ cwd: state.cwd,
2983
+ agentKey: target.key,
2984
+ agentName: target.name,
2985
+ model: target.model
2986
+ }),
2987
+ apiSource: target.apiSource
2941
2988
  },
2942
2989
  output: `Switched to ${target.name}. ${state.dialogId ? `Dialog kept: ${state.dialogId}` : "Dialog kept: new"}`
2943
2990
  };
@@ -2999,32 +3046,32 @@ function handleTuiInput(input, state) {
2999
3046
  return {
3000
3047
  nextState: state,
3001
3048
  output: [
3002
- `Current theme: ${getActiveThemeName()} \xB7 ${brightness}`,
3003
- `Usage: /theme <name> | /theme light | /theme dark`,
3004
- `Available themes: ${available}`
3049
+ t("themeCurrent", getActiveThemeName(), brightness),
3050
+ t("themeUsage"),
3051
+ t("themeAvailable", available)
3005
3052
  ].join("\n")
3006
3053
  };
3007
3054
  }
3008
3055
  if (sub === "light" || sub === "dark") {
3009
3056
  setActiveBrightness(sub);
3010
- return { nextState: state, output: `Switched to ${sub} background colors.` };
3057
+ return { nextState: state, output: t("themeBrightnessSwitched", sub) };
3011
3058
  }
3012
3059
  if (sub === "auto") {
3013
3060
  setActiveBrightness(null);
3014
3061
  return {
3015
3062
  nextState: state,
3016
- output: `Background colors follow terminal detection (now: ${resolveTuiBrightness()}).`
3063
+ output: t("themeBrightnessAuto", resolveTuiBrightness())
3017
3064
  };
3018
3065
  }
3019
3066
  if (setActiveThemeName(sub)) {
3020
3067
  return {
3021
3068
  nextState: state,
3022
- output: `Switched to theme: ${sub}`
3069
+ output: t("themeSwitched", sub)
3023
3070
  };
3024
3071
  } else {
3025
3072
  return {
3026
3073
  nextState: state,
3027
- output: `Unknown theme: ${sub}. Available themes: ${available}`
3074
+ output: t("themeUnknown", sub, available)
3028
3075
  };
3029
3076
  }
3030
3077
  }
@@ -3034,20 +3081,19 @@ function handleTuiInput(input, state) {
3034
3081
  if (!sub) {
3035
3082
  return {
3036
3083
  nextState: state,
3037
- output: `Current density: ${getActiveDensity()}
3038
- Usage: /density <cozy|spacious>`
3084
+ output: t("densityCurrent", getActiveDensity())
3039
3085
  };
3040
3086
  }
3041
3087
  if (sub === "cozy" || sub === "spacious") {
3042
3088
  setActiveDensity(sub);
3043
3089
  return {
3044
3090
  nextState: state,
3045
- output: `Switched to layout density: ${sub}`
3091
+ output: t("densitySwitched", sub)
3046
3092
  };
3047
3093
  } else {
3048
3094
  return {
3049
3095
  nextState: state,
3050
- output: `Unknown density: ${sub}. Use 'cozy' or 'spacious'.`
3096
+ output: t("densityUnknown", sub)
3051
3097
  };
3052
3098
  }
3053
3099
  }
@@ -3058,52 +3104,52 @@ Usage: /density <cozy|spacious>`
3058
3104
  if (argText !== "auto" && argText !== "local" && argText !== "server") {
3059
3105
  return {
3060
3106
  nextState: state,
3061
- output: "Usage: /runtime <auto|local|server>"
3107
+ output: t("runtimeUsage")
3062
3108
  };
3063
3109
  }
3064
3110
  return {
3065
3111
  nextState: { ...state, runtimeMode: argText },
3066
- output: `Runtime: ${argText}`
3112
+ output: t("runtimeSet", argText)
3067
3113
  };
3068
3114
  }
3069
3115
  case "/tools": {
3070
3116
  if (!argText) {
3071
3117
  return {
3072
3118
  nextState: state,
3073
- output: `Tool display: ${state.toolDisplay} (hide | compact | verbose)`
3119
+ output: t("toolsCurrent", state.toolDisplay)
3074
3120
  };
3075
3121
  }
3076
3122
  const normalizedArg = asTrimmedLowercaseString(argText);
3077
3123
  if (!["hide", "compact", "verbose", "on", "off"].includes(normalizedArg)) {
3078
3124
  return {
3079
3125
  nextState: state,
3080
- output: "Usage: /tools <hide|compact|verbose>"
3126
+ output: t("toolsUsage")
3081
3127
  };
3082
3128
  }
3083
3129
  const nextMode = normalizeToolDisplayMode(normalizedArg, state.toolDisplay);
3084
3130
  return {
3085
3131
  nextState: { ...state, toolDisplay: nextMode },
3086
- output: `Tool display: ${nextMode}`
3132
+ output: t("toolsSet", nextMode)
3087
3133
  };
3088
3134
  }
3089
3135
  case "/thinking": {
3090
3136
  if (!argText) {
3091
3137
  return {
3092
3138
  nextState: state,
3093
- output: `Thinking display: ${state.thinkingDisplay} (hide | marker | show)`
3139
+ output: t("thinkingCurrent", state.thinkingDisplay)
3094
3140
  };
3095
3141
  }
3096
3142
  const normalizedArg = asTrimmedLowercaseString(argText);
3097
3143
  if (!["hide", "marker", "show", "on", "off"].includes(normalizedArg)) {
3098
3144
  return {
3099
3145
  nextState: state,
3100
- output: "Usage: /thinking <hide|marker|show>"
3146
+ output: t("thinkingUsage")
3101
3147
  };
3102
3148
  }
3103
3149
  const nextMode = normalizeThinkingDisplayMode(normalizedArg, state.thinkingDisplay);
3104
3150
  return {
3105
3151
  nextState: { ...state, thinkingDisplay: nextMode },
3106
- output: `Thinking display: ${nextMode}`
3152
+ output: t("thinkingSet", nextMode)
3107
3153
  };
3108
3154
  }
3109
3155
  case "/tasks":
@@ -3115,14 +3161,14 @@ Usage: /density <cozy|spacious>`
3115
3161
  const stopped = all.filter((p) => p.status !== "running");
3116
3162
  const lines = [];
3117
3163
  if (running.length > 0) {
3118
- lines.push(`Running processes (${running.length}):`);
3164
+ lines.push(t("tasksRunning", String(running.length)));
3119
3165
  for (const p of running) {
3120
3166
  const elapsed = formatElapsedSeconds(Math.floor((Date.now() - p.startedAt) / 1e3));
3121
3167
  lines.push(` pid ${p.pid} ${p.label} running ${elapsed}`);
3122
3168
  }
3123
3169
  }
3124
3170
  if (stopped.length > 0) {
3125
- lines.push(`Stopped/exited (${stopped.length}):`);
3171
+ lines.push(t("tasksStopped", String(stopped.length)));
3126
3172
  for (const p of stopped) {
3127
3173
  const elapsed = formatElapsedSeconds(Math.floor((Date.now() - p.startedAt) / 1e3));
3128
3174
  const exitInfo = p.exitCode !== void 0 ? ` exit ${p.exitCode}` : "";
@@ -3130,38 +3176,38 @@ Usage: /density <cozy|spacious>`
3130
3176
  }
3131
3177
  }
3132
3178
  if (all.length === 0) {
3133
- lines.push("No processes.");
3179
+ lines.push(t("tasksNone"));
3134
3180
  }
3135
3181
  return { nextState: state, output: lines.join("\n") };
3136
3182
  }
3137
3183
  case "/stop": {
3138
3184
  const registry = getProcessRegistry();
3139
3185
  if (!argText) {
3140
- return { nextState: state, output: "Usage: /stop <pid|label|all>" };
3186
+ return { nextState: state, output: t("stopUsage") };
3141
3187
  }
3142
3188
  if (argText === "all") {
3143
3189
  const before = registry.list().filter((p) => p.status === "running").length;
3144
3190
  registry.stopAll();
3145
- return { nextState: state, output: `Stopped ${before} processes` };
3191
+ return { nextState: state, output: t("stopAllDone", String(before)) };
3146
3192
  }
3147
3193
  if (/^\d+$/.test(argText)) {
3148
3194
  const pid = parseInt(argText, 10);
3149
3195
  const proc = registry.get(pid);
3150
3196
  if (!proc || proc.status !== "running") {
3151
- return { nextState: state, output: `No running process with pid ${pid}` };
3197
+ return { nextState: state, output: t("stopNoPid", String(pid)) };
3152
3198
  }
3153
3199
  registry.kill(pid);
3154
- return { nextState: state, output: `Stopped pid ${pid} (${proc.label})` };
3200
+ return { nextState: state, output: t("stopPidDone", String(pid), proc.label) };
3155
3201
  }
3156
3202
  const matches = registry.list().filter((p) => p.status === "running" && p.label === argText);
3157
3203
  if (matches.length === 0) {
3158
- return { nextState: state, output: `No running process labeled '${argText}'` };
3204
+ return { nextState: state, output: t("stopNoLabel", argText) };
3159
3205
  }
3160
3206
  for (const p of matches) {
3161
3207
  registry.kill(p.pid);
3162
3208
  }
3163
3209
  const stoppedNames = matches.map((p) => `pid ${p.pid} (${p.label})`).join(", ");
3164
- return { nextState: state, output: `Stopped ${stoppedNames}` };
3210
+ return { nextState: state, output: t("stopLabelsDone", stoppedNames) };
3165
3211
  }
3166
3212
  case "/exit":
3167
3213
  case "/quit":
@@ -3185,15 +3231,13 @@ Usage: /density <cozy|spacious>`
3185
3231
  if (argText) {
3186
3232
  return {
3187
3233
  nextState: state,
3188
- output: `Unknown command: ${trimmed}
3189
-
3190
- ${renderTuiHelp()}`
3234
+ output: t("unknownCommand", trimmed) + renderTuiHelp()
3191
3235
  };
3192
3236
  }
3193
3237
  if (!state.dialogId) {
3194
3238
  return {
3195
3239
  nextState: state,
3196
- output: "Current dialog: new (nothing to compact yet)"
3240
+ output: t("compactNothing")
3197
3241
  };
3198
3242
  }
3199
3243
  return {
@@ -3220,15 +3264,14 @@ ${renderTuiHelp()}`
3220
3264
  if (argText === "current" || argText === "show") {
3221
3265
  return {
3222
3266
  nextState: state,
3223
- output: `Current agent: ${state.agentName} (${state.agentKey})`
3267
+ output: t("agentCurrent", state.agentName, state.agentKey)
3224
3268
  };
3225
3269
  }
3226
3270
  const resolvedTarget = resolveAgentSwitchTarget(argText, resolveCatalogPlatformAgents());
3227
3271
  if (!resolvedTarget) {
3228
3272
  return {
3229
3273
  nextState: state,
3230
- output: `I don't know agent "${argText}" yet.
3231
- Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3274
+ output: t("agentUnknown", argText)
3232
3275
  };
3233
3276
  }
3234
3277
  return applyAgentSwitch(state, resolvedTarget);
@@ -3282,6 +3325,16 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3282
3325
  action: { type: "set-mouse", enabled: argText === "on" }
3283
3326
  };
3284
3327
  }
3328
+ case "/altscreen": {
3329
+ if (argText !== "on" && argText !== "off") {
3330
+ return { nextState: state, output: t("altscreenUsage") };
3331
+ }
3332
+ return {
3333
+ nextState: state,
3334
+ output: argText === "on" ? t("altscreenOn") : t("altscreenOff"),
3335
+ action: { type: "set-altscreen", enabled: argText === "on" }
3336
+ };
3337
+ }
3285
3338
  case "/resume": {
3286
3339
  if (!argText) {
3287
3340
  return {
@@ -3311,7 +3364,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3311
3364
  if (rest[0] === "attach") {
3312
3365
  const docName = rest.slice(1).join(" ").trim();
3313
3366
  if (!docName) {
3314
- return { nextState: state, output: "Usage: /doc attach <doc>" };
3367
+ return { nextState: state, output: t("docAttachUsage") };
3315
3368
  }
3316
3369
  const attachedDocs = state.attachedDocs.includes(docName) ? state.attachedDocs : [...state.attachedDocs, docName];
3317
3370
  return {
@@ -3321,7 +3374,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3321
3374
  }
3322
3375
  return {
3323
3376
  nextState: state,
3324
- output: state.attachedDocs.length > 0 ? `Attached docs: ${state.attachedDocs.join(", ")}` : "No docs attached. Use /doc attach <doc>."
3377
+ output: state.attachedDocs.length > 0 ? t("docList", state.attachedDocs.join(", ")) : t("docNone")
3325
3378
  };
3326
3379
  }
3327
3380
  case "/skill": {
@@ -3329,7 +3382,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3329
3382
  if (sub === "attach") {
3330
3383
  const skillRef = rest.slice(1).join(" ").trim();
3331
3384
  if (!skillRef) {
3332
- return { nextState: state, output: "Usage: /skill attach <skill-ref>" };
3385
+ return { nextState: state, output: t("skillAttachUsage") };
3333
3386
  }
3334
3387
  const attachedSkills = state.attachedSkills.includes(skillRef) ? state.attachedSkills : [...state.attachedSkills, skillRef];
3335
3388
  return {
@@ -3340,38 +3393,37 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3340
3393
  if (sub === "detach") {
3341
3394
  const skillRef = rest.slice(1).join(" ").trim();
3342
3395
  if (!skillRef) {
3343
- return { nextState: state, output: "Usage: /skill detach <skill-ref>" };
3396
+ return { nextState: state, output: t("skillDetachUsage") };
3344
3397
  }
3345
3398
  const attachedSkills = state.attachedSkills.filter((s) => s !== skillRef);
3346
3399
  return {
3347
3400
  nextState: { ...state, attachedSkills },
3348
- output: state.attachedSkills.includes(skillRef) ? `Detached skill: ${skillRef}` : `Skill not attached: ${skillRef}`
3401
+ output: state.attachedSkills.includes(skillRef) ? t("skillDetached", skillRef) : t("skillNotAttached", skillRef)
3349
3402
  };
3350
3403
  }
3351
3404
  if (sub === "clear") {
3352
3405
  if (state.attachedSkills.length === 0) {
3353
- return { nextState: state, output: "No skills attached." };
3406
+ return { nextState: state, output: t("skillNone") };
3354
3407
  }
3355
3408
  return {
3356
3409
  nextState: { ...state, attachedSkills: [] },
3357
- output: `Cleared ${state.attachedSkills.length} skill(s).`
3410
+ output: t("skillCleared", String(state.attachedSkills.length))
3358
3411
  };
3359
3412
  }
3360
3413
  return {
3361
3414
  nextState: state,
3362
- output: state.attachedSkills.length > 0 ? `Attached skills: ${state.attachedSkills.join(", ")}
3363
- Usage: /skill attach <ref> | /skill detach <ref> | /skill clear` : "No skills attached. Use /skill attach <skill-ref> to attach a skill.\nSkill refs can be a dbKey (page-xxx), a skill name (searched in .agents/skills/ then docs/skills/), or a direct path."
3415
+ output: state.attachedSkills.length > 0 ? t("skillList", state.attachedSkills.join(", ")) : t("skillNoneHint")
3364
3416
  };
3365
3417
  }
3366
3418
  case "/customize":
3367
3419
  return {
3368
3420
  nextState: state,
3369
- output: "Tell nolo what to change, for example: /customize make my default agent more concise."
3421
+ output: t("customizeHint")
3370
3422
  };
3371
3423
  case "/login":
3372
3424
  return {
3373
3425
  nextState: state,
3374
- output: "MVP login uses profile/env auth. Set AUTH_TOKEN, NOLO_SERVER, or NOLO_PROFILE before starting nolo."
3426
+ output: t("loginHint")
3375
3427
  };
3376
3428
  case "/profile":
3377
3429
  return {
@@ -3387,16 +3439,12 @@ Usage: /skill attach <ref> | /skill detach <ref> | /skill clear` : "No skills at
3387
3439
  case "/version":
3388
3440
  return {
3389
3441
  nextState: state,
3390
- output: `nolo ${state.cliVersion || "unknown version"}
3391
- Update this install with: nolo update
3392
- If repo-local output differs, publish/install the latest npm package first.`
3442
+ output: t("versionInfo", state.cliVersion || t("versionUnknown"))
3393
3443
  };
3394
3444
  default:
3395
3445
  return {
3396
3446
  nextState: state,
3397
- output: `Unknown command: ${command}
3398
-
3399
- ${renderTuiHelp()}`
3447
+ output: t("unknownCommand", command) + renderTuiHelp()
3400
3448
  };
3401
3449
  }
3402
3450
  }
@@ -3414,19 +3462,23 @@ function brightnessFromRgb(r, g, b) {
3414
3462
  const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
3415
3463
  return luma >= 0.5 ? "light" : "dark";
3416
3464
  }
3417
- function parseOsc11Reply(reply) {
3465
+ function parseOsc11Background(reply) {
3418
3466
  const match = reply.match(OSC11_REPLY_RE);
3419
3467
  if (!match) return null;
3420
- return brightnessFromRgb(
3468
+ const channels = [
3421
3469
  componentToByte(match[1]),
3422
3470
  componentToByte(match[2]),
3423
3471
  componentToByte(match[3])
3424
- );
3472
+ ];
3473
+ return {
3474
+ brightness: brightnessFromRgb(channels[0], channels[1], channels[2]),
3475
+ hex: channels.map((v) => v.toString(16).padStart(2, "0").toUpperCase()).join("")
3476
+ };
3425
3477
  }
3426
3478
  function isTerminalDevice(stream) {
3427
3479
  return stream instanceof ReadStream;
3428
3480
  }
3429
- async function detectTerminalBrightness(args = {}) {
3481
+ async function detectTerminalBackground(args = {}) {
3430
3482
  const stdin = args.stdin ?? process.stdin;
3431
3483
  const stdout = args.stdout ?? process.stdout;
3432
3484
  const timeoutMs = args.timeoutMs ?? 100;
@@ -3453,7 +3505,7 @@ async function detectTerminalBrightness(args = {}) {
3453
3505
  };
3454
3506
  const onData = (chunk) => {
3455
3507
  buffer += chunk.toString("latin1");
3456
- const parsed = parseOsc11Reply(buffer);
3508
+ const parsed = parseOsc11Background(buffer);
3457
3509
  if (parsed) finish(parsed);
3458
3510
  else if (buffer.length > 256) finish(null);
3459
3511
  };
@@ -3747,6 +3799,7 @@ function createChatQueueTuiBinding(runTurn) {
3747
3799
 
3748
3800
  // packages/cli/tui/tuiHistory.ts
3749
3801
  var MAX_TUI_HISTORY_TURNS = 500;
3802
+ var turnLineCache = /* @__PURE__ */ new WeakMap();
3750
3803
  function createTurnHistory() {
3751
3804
  return {
3752
3805
  turns: [],
@@ -3788,45 +3841,81 @@ function applyOutputChunkToCurrentTurn(history, chunk) {
3788
3841
  history.currentContent = next;
3789
3842
  return true;
3790
3843
  }
3791
- function buildHistoryLines(history, contentWidth) {
3792
- const colorEnabled = resolveCliColorEnabled();
3793
- const styleTurn = (role, content) => {
3794
- if (role === "user") {
3795
- if (!colorEnabled) return `\u276F ${content}`;
3796
- const accent = themeColorSequence("accent");
3797
- const reset = "\x1B[39m";
3798
- return `${accent}\u276F${reset} ${content}`;
3799
- }
3800
- const highlighted = highlightMarkdown(content, colorEnabled);
3801
- const rawLines = highlighted.split("\n");
3802
- const styledLines = rawLines.map((line, idx) => {
3803
- if (idx === 0 && !line.startsWith("[nolo]")) {
3804
- const anchorPrefix = colorEnabled ? `${themeColorSequence("chrome")}\u25C8\x1B[39m ` : "\u25C8 ";
3805
- return `${anchorPrefix}${line}`;
3806
- }
3807
- return line.startsWith("[nolo]") && colorEnabled ? themeText(line, "chrome", true) : line;
3808
- });
3809
- return styledLines.join("\n");
3810
- };
3844
+ function styleAssistantTurn(content, colorEnabled) {
3845
+ const highlighted = colorEnabled ? formatAssistantDisplay(content) : stripAnsi(formatAssistantDisplay(content));
3846
+ const rawLines = highlighted.split("\n");
3847
+ const styledLines = rawLines.map((line, idx) => {
3848
+ if (idx === 0 && !line.startsWith("[nolo]")) {
3849
+ const anchorPrefix = colorEnabled ? `${themeColorSequence("chrome")}\u25C8\x1B[39m ` : "\u25C8 ";
3850
+ return `${anchorPrefix}${line}`;
3851
+ }
3852
+ return line.startsWith("[nolo]") && colorEnabled ? themeText(line, "chrome", true) : line;
3853
+ });
3854
+ return styledLines.join("\n");
3855
+ }
3856
+ function renderTurnBlock(role, content, contentWidth, colorEnabled) {
3811
3857
  const lines = [];
3812
- const pushTurn = (role, content) => {
3813
- if (getActiveDensity() === "spacious") {
3814
- if (lines.length > 0 || role === "user") lines.push("");
3858
+ if (role === "user") {
3859
+ const logicalLines = content.split("\n");
3860
+ const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
3861
+ const chromeSeq = colorEnabled ? themeColorSequence("chrome") : "";
3862
+ for (let i = 0; i < logicalLines.length; i++) {
3863
+ const line = logicalLines[i];
3864
+ let styledLine;
3865
+ if (colorEnabled) {
3866
+ const prefix = i === 0 ? `${accentSeq}\u276F\x1B[39m ` : `${chromeSeq}\u2502\x1B[39m `;
3867
+ styledLine = `${prefix}${line}`;
3868
+ } else {
3869
+ const prefix = i === 0 ? "\u276F " : " ";
3870
+ styledLine = `${prefix}${line}`;
3871
+ }
3872
+ lines.push(...wrapTranscriptLine(styledLine, contentWidth, " "));
3873
+ }
3874
+ } else {
3875
+ const styledEntry = styleAssistantTurn(content, colorEnabled);
3876
+ for (const logicalLine of styledEntry.split("\n")) {
3877
+ lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
3815
3878
  }
3816
- lines.push(styleTurn(role, content));
3817
- };
3818
- for (const turn of history.turns) {
3819
- pushTurn(turn.role, turn.content);
3820
- }
3821
- if (history.currentRole !== null && history.currentContent) {
3822
- pushTurn(history.currentRole, history.currentContent);
3823
3879
  }
3880
+ return lines;
3881
+ }
3882
+ function buildHistoryLines(history, contentWidth) {
3883
+ const colorEnabled = resolveCliColorEnabled();
3884
+ const density = getActiveDensity();
3824
3885
  const wrapped = [];
3825
- for (const entry of lines) {
3826
- for (const logicalLine of entry.split("\n")) {
3827
- wrapped.push(...wrapTranscriptLine(logicalLine, contentWidth));
3886
+ for (let i = 0; i < history.turns.length; i++) {
3887
+ const turn = history.turns[i];
3888
+ if (density === "spacious" && (i > 0 || turn.role === "user")) {
3889
+ wrapped.push("");
3890
+ }
3891
+ const cached = turnLineCache.get(turn);
3892
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density) {
3893
+ wrapped.push(...cached.lines);
3894
+ } else {
3895
+ const lines = renderTurnBlock(turn.role, turn.content, contentWidth, colorEnabled);
3896
+ turnLineCache.set(turn, {
3897
+ width: contentWidth,
3898
+ color: colorEnabled,
3899
+ density,
3900
+ lines
3901
+ });
3902
+ wrapped.push(...lines);
3828
3903
  }
3829
3904
  }
3905
+ if (history.currentRole !== null && history.currentContent) {
3906
+ const i = history.turns.length;
3907
+ if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
3908
+ wrapped.push("");
3909
+ }
3910
+ wrapped.push(
3911
+ ...renderTurnBlock(
3912
+ history.currentRole,
3913
+ history.currentContent,
3914
+ contentWidth,
3915
+ colorEnabled
3916
+ )
3917
+ );
3918
+ }
3830
3919
  return wrapped;
3831
3920
  }
3832
3921
  function renderHistory(output, history, inputLines) {
@@ -3924,6 +4013,38 @@ function applyScrollAction(history, action, output, inputLines) {
3924
4013
  }
3925
4014
 
3926
4015
  // packages/cli/tui/tuiRawInput.ts
4016
+ function normalizePasteNewlines(text) {
4017
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4018
+ }
4019
+ var altScreenOn = /* @__PURE__ */ new WeakMap();
4020
+ function outputIsTty2(output) {
4021
+ return Boolean(output.isTTY);
4022
+ }
4023
+ function enterAltScreen(output) {
4024
+ if (!outputIsTty2(output)) return false;
4025
+ if (altScreenOn.get(output)) return false;
4026
+ altScreenOn.set(output, true);
4027
+ output.write("\x1B[?1049h");
4028
+ return true;
4029
+ }
4030
+ function leaveAltScreen(output) {
4031
+ if (!outputIsTty2(output)) return false;
4032
+ if (!altScreenOn.get(output)) return false;
4033
+ altScreenOn.set(output, false);
4034
+ output.write("\x1B[?1049l");
4035
+ return true;
4036
+ }
4037
+ function isAltScreenOn(output) {
4038
+ return Boolean(altScreenOn.get(output));
4039
+ }
4040
+ function styleCollapsedPastePlaceholders(text, colorEnabled) {
4041
+ if (!colorEnabled || !text.includes("[paste #")) return text;
4042
+ COLLAPSED_PASTE_PLACEHOLDER_RE.lastIndex = 0;
4043
+ return text.replace(
4044
+ COLLAPSED_PASTE_PLACEHOLDER_RE,
4045
+ (match) => themeText(match, "accent", true)
4046
+ );
4047
+ }
3927
4048
  function createNoopFixedInput() {
3928
4049
  return {
3929
4050
  active: false,
@@ -3946,6 +4067,8 @@ function createNoopFixedInput() {
3946
4067
  getInputLines: () => 1,
3947
4068
  isPaused: () => false,
3948
4069
  setMouseEnabled() {
4070
+ },
4071
+ setAltScreenEnabled() {
3949
4072
  }
3950
4073
  };
3951
4074
  }
@@ -3989,7 +4112,7 @@ function createFixedInput(output, config) {
3989
4112
  sections.push(fitAnsiLine(line.replace(/\r?\n/g, " "), cols));
3990
4113
  }
3991
4114
  const promptRaw = t("promptLabel");
3992
- const promptWidth = displayWidth2(promptRaw);
4115
+ const promptWidth = displayWidth(promptRaw);
3993
4116
  const prompt = colorEnabled ? `${themeColorSequence("accent")}${promptRaw}\x1B[39m` : promptRaw;
3994
4117
  const contentWidth = Math.max(1, cols - promptWidth);
3995
4118
  const logicalLines = buffer.length === 0 ? [""] : buffer.split("\n");
@@ -4017,13 +4140,15 @@ function createFixedInput(output, config) {
4017
4140
  for (let j = 0; j < rows.length; j += 1) {
4018
4141
  const rowPrefix = j === 0 ? prefix : " ".repeat(promptWidth);
4019
4142
  const rowText = rows[j] ?? "";
4020
- sections.push(`${rowPrefix}${rowText}`);
4143
+ sections.push(
4144
+ `${rowPrefix}${styleCollapsedPastePlaceholders(rowText, colorEnabled)}`
4145
+ );
4021
4146
  const rowLen = rowText.length;
4022
4147
  if (!cursorFound) {
4023
4148
  if (targetPos <= charOffset + rowLen || j === rows.length - 1 && i === logicalLines.length - 1) {
4024
4149
  const subOffset = Math.max(0, Math.min(rowLen, targetPos - charOffset));
4025
4150
  const subStr = rowText.slice(0, subOffset);
4026
- cursorCol = promptWidth + displayWidth2(subStr);
4151
+ cursorCol = promptWidth + displayWidth(subStr);
4027
4152
  cursorRow = inputRows;
4028
4153
  cursorFound = true;
4029
4154
  }
@@ -4040,10 +4165,15 @@ function createFixedInput(output, config) {
4040
4165
  };
4041
4166
  const repaintAt = (buffer, cursorPos) => {
4042
4167
  const { text, lines, cursorCol, cursorRow } = renderInputArea(buffer, cursorPos);
4168
+ let linesChanged = false;
4043
4169
  if (lines !== inputLines) {
4044
4170
  inputLines = lines;
4171
+ linesChanged = true;
4045
4172
  }
4046
4173
  setScrollRegion(inputLines);
4174
+ if (linesChanged) {
4175
+ config.onInputLinesChange?.(inputLines);
4176
+ }
4047
4177
  const startRow = getRows() - inputLines + 1;
4048
4178
  write(`\x1B[${startRow};1H`);
4049
4179
  write("\x1B[J");
@@ -4091,8 +4221,7 @@ function createFixedInput(output, config) {
4091
4221
  disable() {
4092
4222
  disableMouse();
4093
4223
  resetScrollRegion();
4094
- const rows = getRows();
4095
- write(`\x1B[${rows};1H\x1B[2K\x1B[${Math.max(1, rows - 1)};1H`);
4224
+ leaveAltScreen(output);
4096
4225
  },
4097
4226
  getInputLines: () => inputLines,
4098
4227
  isPaused: () => paused,
@@ -4103,6 +4232,13 @@ function createFixedInput(output, config) {
4103
4232
  } else {
4104
4233
  write("\x1B[?1000l\x1B[?1006l");
4105
4234
  }
4235
+ },
4236
+ setAltScreenEnabled(enabled) {
4237
+ if (enabled) {
4238
+ enterAltScreen(output);
4239
+ } else {
4240
+ leaveAltScreen(output);
4241
+ }
4106
4242
  }
4107
4243
  };
4108
4244
  }
@@ -4152,9 +4288,27 @@ function isIncompleteTail(input, start) {
4152
4288
  }
4153
4289
  return false;
4154
4290
  }
4155
- var PASTE_START = "\x1B[?2004h";
4156
- var PASTE_END = "\x1B[?2004l";
4157
- function splitRawInputWithTail(input) {
4291
+ var PASTE_START = "\x1B[200~";
4292
+ var PASTE_END = "\x1B[201~";
4293
+ function findBracketedPasteEnd(input, contentStart) {
4294
+ return input.indexOf(PASTE_END, contentStart);
4295
+ }
4296
+ function emitPlainRunAsTokens(run) {
4297
+ if (run.length === 0) return [];
4298
+ if (shouldCollapsePaste(normalizePasteNewlines(run))) {
4299
+ return [`${PASTE_TOKEN_PREFIX}${run}`];
4300
+ }
4301
+ const tokens = [];
4302
+ for (let index = 0; index < run.length; ) {
4303
+ const codePoint = run.codePointAt(index);
4304
+ if (codePoint === void 0) break;
4305
+ const value = String.fromCodePoint(codePoint);
4306
+ tokens.push(value);
4307
+ index += value.length;
4308
+ }
4309
+ return tokens;
4310
+ }
4311
+ function splitRawInputWithTail(input, options) {
4158
4312
  const tokens = [];
4159
4313
  for (let index = 0; index < input.length; ) {
4160
4314
  if (isIncompleteTail(input, index)) {
@@ -4162,15 +4316,17 @@ function splitRawInputWithTail(input) {
4162
4316
  }
4163
4317
  if (input.startsWith(PASTE_START, index)) {
4164
4318
  const contentStart = index + PASTE_START.length;
4165
- const endPos = input.indexOf(PASTE_END, contentStart);
4319
+ const endPos = findBracketedPasteEnd(input, contentStart);
4166
4320
  if (endPos !== -1) {
4167
4321
  const payload = input.slice(contentStart, endPos);
4168
4322
  tokens.push(`${PASTE_TOKEN_PREFIX}${payload}`);
4169
4323
  index = endPos + PASTE_END.length;
4170
- } else {
4324
+ } else if (options?.forceCompleteOpenPaste) {
4171
4325
  const payload = input.slice(contentStart);
4172
4326
  tokens.push(`${PASTE_TOKEN_PREFIX}${payload}`);
4173
4327
  index = input.length;
4328
+ } else {
4329
+ return { tokens, tail: input.slice(index) };
4174
4330
  }
4175
4331
  continue;
4176
4332
  }
@@ -4195,63 +4351,121 @@ function splitRawInputWithTail(input) {
4195
4351
  index += csi.length;
4196
4352
  continue;
4197
4353
  }
4198
- const codePoint = input.codePointAt(index);
4199
- if (codePoint === void 0) break;
4200
- const value = String.fromCodePoint(codePoint);
4201
- tokens.push(value);
4202
- index += value.length;
4354
+ const nextEsc = input.indexOf("\x1B", index);
4355
+ const runEnd = nextEsc === -1 ? input.length : nextEsc;
4356
+ const run = input.slice(index, runEnd);
4357
+ if (run.length === 0) {
4358
+ const codePoint = input.codePointAt(index);
4359
+ if (codePoint === void 0) break;
4360
+ const value = String.fromCodePoint(codePoint);
4361
+ tokens.push(value);
4362
+ index += value.length;
4363
+ continue;
4364
+ }
4365
+ tokens.push(...emitPlainRunAsTokens(run));
4366
+ index = runEnd;
4203
4367
  }
4204
4368
  return { tokens, tail: "" };
4205
4369
  }
4206
4370
  function splitRawInput(input) {
4207
- const { tokens, tail } = splitRawInputWithTail(input);
4371
+ const { tokens, tail } = splitRawInputWithTail(input, {
4372
+ forceCompleteOpenPaste: true
4373
+ });
4208
4374
  if (!tail) return tokens;
4209
- const tailTokens = [];
4210
- for (let index = 0; index < tail.length; ) {
4211
- const codePoint = tail.codePointAt(index);
4212
- if (codePoint === void 0) break;
4213
- const value = String.fromCodePoint(codePoint);
4214
- tailTokens.push(value);
4215
- index += value.length;
4216
- }
4217
- return [...tokens, ...tailTokens];
4375
+ return [...tokens, ...emitPlainRunAsTokens(tail)];
4218
4376
  }
4219
4377
  function createRawInputDecoder(onToken, options) {
4220
4378
  const decoder = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
4221
4379
  let pendingBuffer = "";
4222
4380
  let timer = null;
4223
4381
  const timeoutMs = options?.escTimeoutMs ?? 15;
4382
+ const unmarkedPasteDebounceMs = options?.unmarkedPasteDebounceMs ?? 40;
4224
4383
  const clearTimer = () => {
4225
4384
  if (timer !== null) {
4226
4385
  clearTimeout(timer);
4227
4386
  timer = null;
4228
4387
  }
4229
4388
  };
4230
- const flush = () => {
4389
+ const emitCodePoints = (text) => {
4390
+ for (let index = 0; index < text.length; ) {
4391
+ const codePoint = text.codePointAt(index);
4392
+ if (codePoint === void 0) break;
4393
+ const value = String.fromCodePoint(codePoint);
4394
+ onToken(value);
4395
+ index += value.length;
4396
+ }
4397
+ };
4398
+ const flushPlainPending = () => {
4231
4399
  clearTimer();
4232
- if (pendingBuffer.length > 0) {
4233
- const textToFlush = pendingBuffer;
4234
- pendingBuffer = "";
4235
- for (const token of splitRawInput(textToFlush)) {
4236
- onToken(token);
4237
- }
4400
+ if (pendingBuffer.length === 0) return;
4401
+ const text = pendingBuffer;
4402
+ pendingBuffer = "";
4403
+ for (const token of emitPlainRunAsTokens(text)) {
4404
+ onToken(token);
4238
4405
  }
4239
4406
  };
4407
+ const flushEscPending = (forceCompleteOpenPaste) => {
4408
+ clearTimer();
4409
+ if (pendingBuffer.length === 0) return;
4410
+ const textToFlush = pendingBuffer;
4411
+ pendingBuffer = "";
4412
+ const { tokens, tail } = splitRawInputWithTail(textToFlush, {
4413
+ forceCompleteOpenPaste
4414
+ });
4415
+ for (const token of tokens) {
4416
+ onToken(token);
4417
+ }
4418
+ if (!tail) return;
4419
+ if (forceCompleteOpenPaste) {
4420
+ emitCodePoints(tail);
4421
+ return;
4422
+ }
4423
+ pendingBuffer = tail;
4424
+ };
4240
4425
  const decodeFn = (chunk) => {
4241
4426
  clearTimer();
4242
4427
  const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
4243
4428
  const text = decoder.decode(bytes, { stream: true });
4244
4429
  pendingBuffer += text;
4245
- const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4246
- pendingBuffer = tail;
4247
- for (const token of tokens) {
4248
- onToken(token);
4430
+ if (pendingBuffer.startsWith(PASTE_START)) {
4431
+ const endPos = findBracketedPasteEnd(
4432
+ pendingBuffer,
4433
+ PASTE_START.length
4434
+ );
4435
+ if (endPos === -1) return;
4436
+ const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4437
+ pendingBuffer = tail;
4438
+ for (const token of tokens) onToken(token);
4439
+ if (pendingBuffer.startsWith(PASTE_START)) return;
4440
+ }
4441
+ if (pendingBuffer.includes("\x1B")) {
4442
+ const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4443
+ pendingBuffer = tail;
4444
+ for (const token of tokens) onToken(token);
4445
+ if (pendingBuffer.startsWith(PASTE_START)) return;
4446
+ if (pendingBuffer.length > 0) {
4447
+ timer = setTimeout(() => flushEscPending(true), timeoutMs);
4448
+ }
4449
+ return;
4450
+ }
4451
+ if (shouldCollapsePaste(normalizePasteNewlines(pendingBuffer))) {
4452
+ timer = setTimeout(flushPlainPending, unmarkedPasteDebounceMs);
4453
+ return;
4454
+ }
4455
+ if (!pendingBuffer.includes("\n") && !pendingBuffer.includes("\r")) {
4456
+ emitCodePoints(pendingBuffer);
4457
+ pendingBuffer = "";
4458
+ return;
4249
4459
  }
4250
- if (pendingBuffer.length > 0) {
4251
- timer = setTimeout(flush, timeoutMs);
4460
+ timer = setTimeout(flushPlainPending, unmarkedPasteDebounceMs);
4461
+ };
4462
+ decodeFn.flush = () => {
4463
+ if (pendingBuffer.startsWith(PASTE_START) || pendingBuffer.includes("\x1B")) {
4464
+ flushEscPending(true);
4465
+ return;
4252
4466
  }
4467
+ flushPlainPending();
4253
4468
  };
4254
- decodeFn.flush = flush;
4255
4469
  decodeFn.destroy = () => {
4256
4470
  clearTimer();
4257
4471
  pendingBuffer = "";
@@ -4261,12 +4475,66 @@ function createRawInputDecoder(onToken, options) {
4261
4475
 
4262
4476
  // packages/cli/tui/readlineWorkspace.ts
4263
4477
  var AGENTS_MD_MAX_BYTES = 8192;
4478
+ var altScreenRestoreOutput = null;
4479
+ var altScreenHandlersInstalled = false;
4480
+ var preExistingSignalListeners = {};
4481
+ var SIGNAL_EXIT_CODE = {
4482
+ SIGINT: 130,
4483
+ SIGTERM: 143,
4484
+ SIGHUP: 129
4485
+ };
4486
+ var restoreAltScreen = () => {
4487
+ try {
4488
+ if (altScreenRestoreOutput) leaveAltScreen(altScreenRestoreOutput);
4489
+ } catch {
4490
+ }
4491
+ };
4492
+ function installAltScreenRestoreHandlers(output) {
4493
+ altScreenRestoreOutput = output;
4494
+ if (altScreenHandlersInstalled) return;
4495
+ altScreenHandlersInstalled = true;
4496
+ process.on("exit", restoreAltScreen);
4497
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
4498
+ const existing = process.listeners(sig);
4499
+ preExistingSignalListeners[sig] = [...existing];
4500
+ const handler = () => {
4501
+ restoreAltScreen();
4502
+ const others = process.listeners(sig).filter((fn) => fn !== handler);
4503
+ if (others.length === 0) {
4504
+ process.exit(SIGNAL_EXIT_CODE[sig] ?? 128 + 1);
4505
+ return;
4506
+ }
4507
+ };
4508
+ process.prependListener(sig, handler);
4509
+ }
4510
+ process.on("uncaughtException", (err) => {
4511
+ restoreAltScreen();
4512
+ try {
4513
+ process.stderr.write(
4514
+ `uncaughtException: ${err?.stack ?? String(err)}
4515
+ `
4516
+ );
4517
+ } catch {
4518
+ }
4519
+ process.exit(1);
4520
+ });
4521
+ process.on("unhandledRejection", (reason) => {
4522
+ restoreAltScreen();
4523
+ try {
4524
+ const text = reason instanceof Error ? reason.stack ?? String(reason) : String(reason);
4525
+ process.stderr.write(`unhandledRejection: ${text}
4526
+ `);
4527
+ } catch {
4528
+ }
4529
+ process.exit(1);
4530
+ });
4531
+ }
4264
4532
  function readAgentsMdBlock(cwd) {
4265
4533
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
4266
- const filePath = join(cwd, name);
4267
- if (existsSync2(filePath)) {
4534
+ const filePath = join2(cwd, name);
4535
+ if (existsSync3(filePath)) {
4268
4536
  try {
4269
- let content = readFileSync(filePath, "utf8").trim();
4537
+ let content = readFileSync2(filePath, "utf8").trim();
4270
4538
  if (!content) continue;
4271
4539
  if (Buffer.byteLength(content, "utf8") > AGENTS_MD_MAX_BYTES) {
4272
4540
  content = Buffer.from(content, "utf8").subarray(0, AGENTS_MD_MAX_BYTES).toString("utf8") + "\n\n<!-- AGENTS.md truncated -->";
@@ -4290,10 +4558,14 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4290
4558
  effectiveAgentName = cachedRoute.agentName;
4291
4559
  } else if (!continueId && env.NOLO_AUTO_ROUTE !== "0" && state.agentKey === DEFAULT_TUI_AGENT_KEY) {
4292
4560
  const authToken = env.AUTH_TOKEN ?? env.AUTH ?? env.BENCHMARK_AUTH_TOKEN ?? "";
4293
- const route = await classifyCliAutoRoute(message, {
4561
+ const route = authToken ? await classifyCliAutoRoute(message, {
4294
4562
  serverUrl: state.serverUrl,
4295
4563
  authToken
4296
- });
4564
+ }) : {
4565
+ agentKey: CLI_AUTO_TIER_AGENT_KEYS[resolveCliAutoFallbackTier(message)],
4566
+ tier: resolveCliAutoFallbackTier(message),
4567
+ classified: false
4568
+ };
4297
4569
  effectiveAgentKey = route.agentKey;
4298
4570
  effectiveAgentName = `auto\u2192${route.tier}`;
4299
4571
  if (effectiveAgentKey !== state.agentKey) {
@@ -4381,6 +4653,18 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4381
4653
  if (skillDiscoveryBlock) {
4382
4654
  extraContextBlocks.push(skillDiscoveryBlock);
4383
4655
  }
4656
+ const agentsMdMarker = "--- \u9879\u76EE\u6307\u4EE4\uFF08";
4657
+ const contextBlockScopes = [];
4658
+ for (const block of extraContextBlocks) {
4659
+ if (block.startsWith(agentsMdMarker)) {
4660
+ contextBlockScopes.push({ content: block, cacheScope: "session" });
4661
+ }
4662
+ }
4663
+ for (const block of extraContextBlocks) {
4664
+ if (!block.startsWith(agentsMdMarker)) {
4665
+ contextBlockScopes.push({ content: block, cacheScope: "turn" });
4666
+ }
4667
+ }
4384
4668
  const result = await agentRunner({
4385
4669
  agentName: effectiveAgentName,
4386
4670
  agentKey: effectiveAgentKey,
@@ -4401,9 +4685,17 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4401
4685
  ...options.confirmDestructiveAction ? { confirmDestructiveAction: options.confirmDestructiveAction } : {},
4402
4686
  ...options.requestUserChoice ? { requestUserChoice: options.requestUserChoice } : {},
4403
4687
  ...options.abortSignal ? { abortSignal: options.abortSignal } : {},
4688
+ ...options.pastedTextStore?.items.size ? {
4689
+ pastedTextStore: options.pastedTextStore,
4690
+ localRuntimeAdapterFactory: (factoryEnv, factoryOptions) => createCliLocalRuntimeAdapter({
4691
+ env: factoryEnv,
4692
+ cwd: factoryOptions?.cwd ?? state.cwd,
4693
+ pastedTextStore: options.pastedTextStore
4694
+ })
4695
+ } : {},
4404
4696
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {},
4405
4697
  ...skillAllowedTools !== void 0 ? { allowedToolNames: skillAllowedTools } : {},
4406
- ...extraContextBlocks.length > 0 ? { extraContextBlocks } : {}
4698
+ ...contextBlockScopes.length > 0 ? { contextBlockScopes } : {}
4407
4699
  });
4408
4700
  if (!continueId && result.dialogId && effectiveAgentKey !== state.agentKey) {
4409
4701
  autoRouteByDialog.set(result.dialogId, {
@@ -4411,19 +4703,32 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4411
4703
  agentName: effectiveAgentName
4412
4704
  });
4413
4705
  }
4414
- return result;
4706
+ return {
4707
+ ...result,
4708
+ contextWindow: resolveAgentContextWindow({
4709
+ agentKey: effectiveAgentKey,
4710
+ agentName: effectiveAgentName,
4711
+ autoRouteDefault: env.NOLO_AUTO_ROUTE !== "0"
4712
+ })
4713
+ };
4415
4714
  }
4416
4715
  function waitForActionGate(rl, input, output, gate, spawnRunner) {
4417
4716
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
4418
4717
  const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
4419
- output.write("\n[nolo] Action needed in your terminal\n");
4420
- output.write(`[nolo] ${gate.title}
4718
+ const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
4719
+ const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
4720
+ const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
4721
+ output.write(`
4722
+ [nolo] ${t("actionGateNeeded")}
4723
+ `);
4724
+ output.write(`[nolo] ${title}
4421
4725
  `);
4422
- if (gate.body) output.write(`[nolo] ${gate.body}
4726
+ if (body) output.write(`[nolo] ${body}
4423
4727
  `);
4424
4728
  output.write(` ${displayCommand}
4425
4729
  `);
4426
- output.write("[nolo] Press Enter to run it now. Follow any prompts below, or Ctrl+C to cancel.\n");
4730
+ output.write(`[nolo] ${t("actionGateEnterHint")}
4731
+ `);
4427
4732
  return new Promise((resolve2) => {
4428
4733
  let settled = false;
4429
4734
  const finish = (result) => {
@@ -4513,17 +4818,28 @@ function isInteractiveInput(input) {
4513
4818
  function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4514
4819
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
4515
4820
  const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
4516
- output.write("\n[nolo] Action needed in your terminal\n");
4517
- output.write(`[nolo] ${gate.title}
4821
+ const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
4822
+ const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
4823
+ const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
4824
+ output.write(`
4825
+ [nolo] ${t("actionGateNeeded")}
4518
4826
  `);
4519
- if (gate.body) output.write(`[nolo] ${gate.body}
4827
+ output.write(`[nolo] ${title}
4828
+ `);
4829
+ if (body) output.write(`[nolo] ${body}
4520
4830
  `);
4521
4831
  output.write(` ${displayCommand}
4522
4832
  `);
4523
- output.write("[nolo] Press Enter to run it now. Follow any prompts below, or Ctrl+C to cancel.\n");
4833
+ output.write(`[nolo] ${t("actionGateEnterHint")}
4834
+ `);
4524
4835
  return new Promise((resolve2) => {
4525
4836
  const rawInput = input;
4837
+ let settled = false;
4838
+ let commandRunning = false;
4526
4839
  const finish = (result) => {
4840
+ if (settled) return;
4841
+ settled = true;
4842
+ hooks?.registerToken?.(null);
4527
4843
  input.off("data", onData);
4528
4844
  resolve2(result);
4529
4845
  };
@@ -4541,13 +4857,9 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4541
4857
  actionGateResult: { gateId: gate.id, status: "failed", output: message }
4542
4858
  }
4543
4859
  });
4544
- const onData = async (chunk) => {
4545
- const text = String(chunk);
4546
- if (text.includes("")) {
4547
- cancel("interrupted");
4548
- return;
4549
- }
4550
- if (!text.includes("\r") && !text.includes("\n")) return;
4860
+ const runCommand = async () => {
4861
+ if (settled || commandRunning) return;
4862
+ commandRunning = true;
4551
4863
  if (!commandPayload) {
4552
4864
  fail("unsupported gate payload");
4553
4865
  return;
@@ -4585,7 +4897,25 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4585
4897
  }
4586
4898
  });
4587
4899
  };
4588
- input.on("data", onData);
4900
+ const handleToken = (text) => {
4901
+ if (settled || commandRunning) return;
4902
+ if (text.includes("")) {
4903
+ cancel("interrupted");
4904
+ return;
4905
+ }
4906
+ if (text.includes("\r") || text.includes("\n")) {
4907
+ void runCommand();
4908
+ }
4909
+ };
4910
+ const onData = (chunk) => {
4911
+ const text = String(chunk);
4912
+ handleToken(text);
4913
+ };
4914
+ if (hooks?.registerToken) {
4915
+ hooks.registerToken(handleToken);
4916
+ } else {
4917
+ input.on("data", onData);
4918
+ }
4589
4919
  });
4590
4920
  }
4591
4921
  async function startTuiWorkspace(options) {
@@ -4597,22 +4927,33 @@ async function startTuiWorkspace(options) {
4597
4927
  const spawnRunner = options.spawnRunner ?? spawnProcess;
4598
4928
  const selfUpdater = options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
4599
4929
  if (output.isTTY) {
4600
- output.write("\x1B[2J\x1B[3J\x1B[H");
4930
+ enterAltScreen(output);
4931
+ installAltScreenRestoreHandlers(output);
4932
+ output.write("\x1B[2J\x1B[H");
4601
4933
  }
4602
- const detected = await detectTerminalBrightness({
4934
+ const detected = await detectTerminalBackground({
4603
4935
  stdin: input,
4604
4936
  stdout: output
4605
4937
  });
4606
- if (detected) setActiveBrightness(detected);
4938
+ if (detected) {
4939
+ setActiveBrightness(detected.brightness);
4940
+ setActiveTerminalBaseHex(detected.hex);
4941
+ }
4607
4942
  const bannerColumns = output.columns;
4608
4943
  output.write(renderWelcome(state, 0, 0, bannerColumns));
4609
4944
  let fixedInput = createNoopFixedInput();
4610
4945
  let buffer = "";
4611
4946
  let cursorPos = 0;
4947
+ const pasteStore = createCollapsedPasteStore();
4612
4948
  let activeTurnAbort = null;
4949
+ let forcedStop = false;
4950
+ let forcedStopEpoch = 0;
4951
+ let turnEpoch = 0;
4952
+ let activeTurnEpoch = 0;
4613
4953
  const activityIndicator = createActivityIndicator({
4614
4954
  isTurnActive: () => activeTurnAbort !== null,
4615
4955
  fallbackLabel: () => `${state.agentName} -> working`,
4956
+ stoppingLabel: () => t("turnStopping"),
4616
4957
  onRepaint: () => {
4617
4958
  if (fixedInput.active && !fixedInput.isPaused()) {
4618
4959
  output.write("\x1B[?2026h\x1B[?25l");
@@ -4642,8 +4983,10 @@ async function startTuiWorkspace(options) {
4642
4983
  },
4643
4984
  output
4644
4985
  });
4986
+ const RENDER_THROTTLE_MS = 33;
4645
4987
  let renderScheduled = false;
4646
4988
  let renderTimer = null;
4989
+ let sessionEnded = false;
4647
4990
  const flushPendingRender = () => {
4648
4991
  if (renderTimer !== null) {
4649
4992
  clearTimeout(renderTimer);
@@ -4670,11 +5013,26 @@ async function startTuiWorkspace(options) {
4670
5013
  renderScheduled = false;
4671
5014
  renderTimer = null;
4672
5015
  paintSyncedFrame();
4673
- }, 0);
5016
+ }, RENDER_THROTTLE_MS);
4674
5017
  };
5018
+ const refreshGitStatus = () => {
5019
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) === "0") return;
5020
+ void detectGitStatusAsync(state.cwd).then((gitStatus) => {
5021
+ if (sessionEnded) return;
5022
+ state = { ...state, gitStatus };
5023
+ scheduleRender();
5024
+ });
5025
+ };
5026
+ let syncingLayout = false;
4675
5027
  const renderHistoryToOutput = () => {
4676
5028
  if (fixedInput.isPaused()) return;
4677
- renderHistory(output, history, fixedInput.getInputLines());
5029
+ if (syncingLayout) return;
5030
+ syncingLayout = true;
5031
+ try {
5032
+ renderHistory(output, history, fixedInput.getInputLines());
5033
+ } finally {
5034
+ syncingLayout = false;
5035
+ }
4678
5036
  };
4679
5037
  const readLatestAssistantReply = () => {
4680
5038
  const lastReply = [...history.turns].reverse().find((turn) => turn.role === "assistant")?.content;
@@ -4712,7 +5070,11 @@ ${t("copyViewHint")}
4712
5070
  return true;
4713
5071
  };
4714
5072
  let modalOwnsKeyboard = false;
5073
+ let rawActionGateTokenHandler = null;
4715
5074
  const runOneAgentTurn = async (message, imageUrls, actionGateHandler, confirmDestructiveAction) => {
5075
+ forcedStop = false;
5076
+ turnEpoch += 1;
5077
+ const myEpoch = turnEpoch;
4716
5078
  history.followBottom = true;
4717
5079
  startTurn(history, "user");
4718
5080
  appendToCurrentTurn(history, message);
@@ -4742,6 +5104,7 @@ ${t("copyViewHint")}
4742
5104
  } : void 0;
4743
5105
  try {
4744
5106
  activeTurnAbort = new AbortController();
5107
+ activeTurnEpoch = myEpoch;
4745
5108
  const runResult = await runAgentChat(
4746
5109
  options.scriptDir,
4747
5110
  state,
@@ -4755,10 +5118,27 @@ ${t("copyViewHint")}
4755
5118
  ...confirmDestructiveAction ? { confirmDestructiveAction } : {},
4756
5119
  ...requestUserChoice ? { requestUserChoice } : {},
4757
5120
  abortSignal: activeTurnAbort.signal,
5121
+ pastedTextStore: pasteStore,
4758
5122
  explicitAgentSwitch,
4759
5123
  activityReporter
4760
5124
  }
4761
5125
  );
5126
+ const wasForceStopped = forcedStopEpoch === myEpoch;
5127
+ if (wasForceStopped) {
5128
+ if (runResult.dialogId || runResult.turnTokens) {
5129
+ const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
5130
+ state = {
5131
+ ...state,
5132
+ ...runResult.dialogId ? {
5133
+ dialogId: runResult.dialogId,
5134
+ dialogKey: nextDialogKey,
5135
+ dialogLabel: runResult.dialogId
5136
+ } : {},
5137
+ ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {}
5138
+ };
5139
+ }
5140
+ return { ok: false, aborted: true };
5141
+ }
4762
5142
  const wasAborted = activeTurnAbort.signal.aborted;
4763
5143
  activeTurnAbort = null;
4764
5144
  explicitAgentSwitch = false;
@@ -4769,9 +5149,13 @@ ${t("copyViewHint")}
4769
5149
  if (fixedInput.active) fixedInput.repaint(buffer);
4770
5150
  }
4771
5151
  if (wasAborted) {
4772
- emitCommandOutput(t("turnStopped"));
5152
+ if (runResult.pendingToolName) {
5153
+ emitCommandOutput(t("turnStoppedToolPending", runResult.pendingToolName));
5154
+ } else {
5155
+ emitCommandOutput(t("turnStopped"));
5156
+ }
4773
5157
  }
4774
- if (runResult.dialogId || runResult.turnTokens) {
5158
+ if (runResult.dialogId || runResult.turnTokens || runResult.contextWindow) {
4775
5159
  const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
4776
5160
  state = {
4777
5161
  ...state,
@@ -4780,7 +5164,8 @@ ${t("copyViewHint")}
4780
5164
  dialogKey: nextDialogKey,
4781
5165
  dialogLabel: runResult.dialogId
4782
5166
  } : {},
4783
- ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {}
5167
+ ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
5168
+ ...runResult.contextWindow ? { contextWindow: runResult.contextWindow } : {}
4784
5169
  };
4785
5170
  }
4786
5171
  if (!wasAborted && runResult.exitCode !== 0) {
@@ -4847,6 +5232,7 @@ ${t("copyViewHint")}
4847
5232
  }
4848
5233
  if (result.action?.type === "exit") return true;
4849
5234
  if (result.action?.type === "clear") {
5235
+ clearCollapsedPasteStore(pasteStore);
4850
5236
  history.turns.length = 0;
4851
5237
  history.currentRole = null;
4852
5238
  history.currentContent = "";
@@ -4917,7 +5303,19 @@ ${t("copyViewHint")}
4917
5303
  ...state,
4918
5304
  agentName: pickResult.name,
4919
5305
  agentKey: pickResult.key,
4920
- contextWindow: getModelContextWindow(pickResult.name)
5306
+ contextWindow: resolveAgentContextWindow({
5307
+ agentKey: pickResult.key,
5308
+ agentName: pickResult.name,
5309
+ model: pickResult.model,
5310
+ autoRouteDefault: (options.env ?? process.env).NOLO_AUTO_ROUTE !== "0"
5311
+ }),
5312
+ estimatedContextTokens: estimateDefaultCliContextTokens({
5313
+ cwd: state.cwd,
5314
+ agentKey: pickResult.key,
5315
+ agentName: pickResult.name,
5316
+ model: pickResult.model
5317
+ }),
5318
+ ...pickResult.apiSource ? { apiSource: pickResult.apiSource } : {}
4921
5319
  };
4922
5320
  if (state.dialogId) autoRouteByDialog.delete(state.dialogId);
4923
5321
  explicitAgentSwitch = true;
@@ -4949,6 +5347,16 @@ ${t("copyViewHint")}
4949
5347
  if (result.action?.type === "set-mouse") {
4950
5348
  fixedInput.setMouseEnabled(result.action.enabled);
4951
5349
  }
5350
+ if (result.action?.type === "set-altscreen") {
5351
+ if (result.action.enabled) {
5352
+ enterAltScreen(output);
5353
+ installAltScreenRestoreHandlers(output);
5354
+ } else {
5355
+ leaveAltScreen(output);
5356
+ }
5357
+ renderHistoryToOutput();
5358
+ fixedInput.repaint(buffer);
5359
+ }
4952
5360
  if (result.action?.type === "copy-view") {
4953
5361
  const opened = await openCopyView();
4954
5362
  if (!opened) emitCommandOutput(t("copyNothing"));
@@ -4992,14 +5400,7 @@ ${t("copyViewHint")}
4992
5400
  dialog: pickResult.dialog,
4993
5401
  env: options.env ?? process.env
4994
5402
  });
4995
- const restored = loadedTurns.slice(-MAX_TUI_HISTORY_TURNS).map(
4996
- (turn) => turn.role === "assistant" ? {
4997
- ...turn,
4998
- content: formatAssistantDisplay(
4999
- turn.content
5000
- )
5001
- } : turn
5002
- );
5403
+ const restored = loadedTurns.slice(-MAX_TUI_HISTORY_TURNS);
5003
5404
  history.turns.length = 0;
5004
5405
  history.turns.push(...restored);
5005
5406
  history.currentRole = null;
@@ -5013,6 +5414,7 @@ ${t("copyViewHint")}
5013
5414
  dialogLabel: pickResult.dialog.id,
5014
5415
  turnTokens: void 0
5015
5416
  };
5417
+ clearCollapsedPasteStore(pasteStore);
5016
5418
  emitCommandOutput(
5017
5419
  `${t("resumedDialogPrefix")}: ${pickResult.dialog.title} (${pickResult.dialog.id})`
5018
5420
  );
@@ -5136,6 +5538,7 @@ ${err.message}` : ""}`
5136
5538
  output.write("\x1B[?2004h");
5137
5539
  let busy = false;
5138
5540
  let done = false;
5541
+ let resolveDone = null;
5139
5542
  let cursorPos2 = 0;
5140
5543
  fixedInput = createFixedInput(output, {
5141
5544
  getStatusLine: () => {
@@ -5172,6 +5575,9 @@ ${err.message}` : ""}`
5172
5575
  const oneLine = text.replace(/\r?\n/g, " \u23CE ");
5173
5576
  return dimCliText(` \u2937 ${i + 1}. ${oneLine}`, colorEnabled);
5174
5577
  });
5578
+ },
5579
+ onInputLinesChange: () => {
5580
+ renderHistoryToOutput();
5175
5581
  }
5176
5582
  });
5177
5583
  fixedInput.init();
@@ -5194,6 +5600,9 @@ ${err.message}` : ""}`
5194
5600
  const finish = () => {
5195
5601
  if (done) return;
5196
5602
  done = true;
5603
+ sessionEnded = true;
5604
+ resolveDone?.();
5605
+ clearCollapsedPasteStore(pasteStore);
5197
5606
  resizeTarget.off?.("resize", onResize);
5198
5607
  input.off("data", onData);
5199
5608
  onData.destroy();
@@ -5211,7 +5620,10 @@ ${err.message}` : ""}`
5211
5620
  }
5212
5621
  return;
5213
5622
  }
5214
- if (modalOwnsKeyboard) return;
5623
+ if (modalOwnsKeyboard) {
5624
+ rawActionGateTokenHandler?.(sequence);
5625
+ return;
5626
+ }
5215
5627
  const busyLock = busy;
5216
5628
  const scrollAction = parseScrollAction(sequence);
5217
5629
  if (scrollAction) {
@@ -5221,19 +5633,33 @@ ${err.message}` : ""}`
5221
5633
  return;
5222
5634
  }
5223
5635
  if (busyLock && sequence === "\x1B" && activeTurnAbort) {
5636
+ if (activityIndicator.isStopping()) {
5637
+ forcedStop = true;
5638
+ forcedStopEpoch = activeTurnEpoch;
5639
+ activityIndicator.stop();
5640
+ activeTurnAbort.abort();
5641
+ activeTurnAbort = null;
5642
+ activeTurnEpoch = 0;
5643
+ busy = false;
5644
+ emitCommandOutput(t("forceStopped"));
5645
+ flushPendingRender();
5646
+ renderHistoryToOutput();
5647
+ if (fixedInput.active) fixedInput.repaint(buffer);
5648
+ return;
5649
+ }
5224
5650
  const stopBinding = chatQueueBinding;
5225
5651
  if (stopBinding && stopBinding.queueLength() > 0) {
5226
5652
  stopBinding.preemptForStop();
5227
5653
  }
5654
+ activityIndicator.markStopping();
5228
5655
  activeTurnAbort.abort();
5229
5656
  return;
5230
5657
  }
5231
- const result = applyTuiInputKey(buffer, sequence, {}, cursorPos2);
5658
+ const result = applyTuiInputKey(buffer, sequence, {}, cursorPos2, {
5659
+ pasteStore
5660
+ });
5232
5661
  if (result.copyView) {
5233
- if (busyLock) return;
5234
- busy = true;
5235
5662
  const opened = await openCopyView();
5236
- busy = false;
5237
5663
  if (!opened) {
5238
5664
  history.followBottom = true;
5239
5665
  startTurn(history, "assistant");
@@ -5249,14 +5675,19 @@ ${err.message}` : ""}`
5249
5675
  return;
5250
5676
  }
5251
5677
  if (result.submit !== void 0) {
5678
+ const submittedText = replaceCollapsedPastesWithReferences(
5679
+ result.submit,
5680
+ pasteStore
5681
+ );
5682
+ const submittedTrimmed = submittedText.trim();
5683
+ const startsChatTurn = submittedTrimmed.length > 0 && !submittedTrimmed.startsWith("/") && !submittedTrimmed.startsWith("!");
5252
5684
  if (busyLock) {
5253
- const submittedText2 = result.submit;
5254
- const trimmedText = submittedText2.trim();
5685
+ const trimmedText = submittedText.trim();
5255
5686
  const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
5256
5687
  const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch";
5257
5688
  if (isBusyLocalSlash) {
5258
5689
  const beforeAgentKey = state.agentKey;
5259
- const res = handleTuiInput(submittedText2, state);
5690
+ const res = handleTuiInput(submittedText, state);
5260
5691
  if (res.action) {
5261
5692
  output.write(
5262
5693
  "Model picker isn't available while a reply is running. Use `/switch <name>` to switch now (takes effect on the next turn), or wait for the reply to finish.\n"
@@ -5274,6 +5705,7 @@ ${hint}` : hint;
5274
5705
  `);
5275
5706
  }
5276
5707
  }
5708
+ releaseCollapsedPasteReferences(submittedText, pasteStore);
5277
5709
  buffer = "";
5278
5710
  cursorPos2 = 0;
5279
5711
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos2);
@@ -5285,7 +5717,10 @@ ${hint}` : hint;
5285
5717
  try {
5286
5718
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5287
5719
  beforeSubprocess: () => fixedInput.pause(),
5288
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5720
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5721
+ registerToken: (handler) => {
5722
+ rawActionGateTokenHandler = handler;
5723
+ }
5289
5724
  });
5290
5725
  } finally {
5291
5726
  modalOwnsKeyboard = false;
@@ -5308,7 +5743,7 @@ ${hint}` : hint;
5308
5743
  }
5309
5744
  );
5310
5745
  const decision = binding.resolveSubmit({
5311
- text: submittedText2,
5746
+ text: submittedText,
5312
5747
  isRunning: true
5313
5748
  });
5314
5749
  if (decision.kind === "queue-text") {
@@ -5317,7 +5752,7 @@ ${hint}` : hint;
5317
5752
  cursorPos2 = 0;
5318
5753
  fixedInput.repaint(buffer, cursorPos2);
5319
5754
  } else if (decision.kind === "queue-blocked") {
5320
- } else if (decision.kind === "noop" && !submittedText2.trim() && binding.queueLength() > 0) {
5755
+ } else if (decision.kind === "noop" && !submittedText.trim() && binding.queueLength() > 0) {
5321
5756
  if (binding.preemptForDrain()) {
5322
5757
  if (activeTurnAbort) {
5323
5758
  activeTurnAbort.abort();
@@ -5326,13 +5761,16 @@ ${hint}` : hint;
5326
5761
  }
5327
5762
  return;
5328
5763
  }
5329
- if (!result.submit.trim() && chatQueueBinding && chatQueueBinding.queueLength() > 0) {
5764
+ if (!submittedText.trim() && chatQueueBinding && chatQueueBinding.queueLength() > 0) {
5330
5765
  const actionGateHandler = async (gate) => {
5331
5766
  modalOwnsKeyboard = true;
5332
5767
  try {
5333
5768
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5334
5769
  beforeSubprocess: () => fixedInput.pause(),
5335
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5770
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5771
+ registerToken: (handler) => {
5772
+ rawActionGateTokenHandler = handler;
5773
+ }
5336
5774
  });
5337
5775
  } finally {
5338
5776
  modalOwnsKeyboard = false;
@@ -5377,17 +5815,13 @@ ${err.message}` : ""}`
5377
5815
  } finally {
5378
5816
  busy = false;
5379
5817
  }
5380
- const drainEnv = options.env ?? process.env;
5381
- if (drainEnv.NOLO_CLI_GIT_STATUS !== "0") {
5382
- state = { ...state, gitStatus: detectGitStatus(state.cwd) };
5383
- }
5818
+ refreshGitStatus();
5384
5819
  flushPendingRender();
5385
5820
  fixedInput.exitOutputMode(buffer, cursorPos2);
5386
5821
  return;
5387
5822
  }
5388
5823
  }
5389
- busy = true;
5390
- const submittedText = result.submit;
5824
+ busy = startsChatTurn;
5391
5825
  buffer = "";
5392
5826
  cursorPos2 = 0;
5393
5827
  fixedInput.enterOutputMode(submittedText);
@@ -5400,7 +5834,10 @@ ${err.message}` : ""}`
5400
5834
  try {
5401
5835
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5402
5836
  beforeSubprocess: () => fixedInput.pause(),
5403
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5837
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5838
+ registerToken: (handler) => {
5839
+ rawActionGateTokenHandler = handler;
5840
+ }
5404
5841
  });
5405
5842
  } finally {
5406
5843
  modalOwnsKeyboard = false;
@@ -5430,10 +5867,7 @@ ${err.message}` : ""}`
5430
5867
  finish();
5431
5868
  return;
5432
5869
  }
5433
- const refreshEnv = options.env ?? process.env;
5434
- if (refreshEnv.NOLO_CLI_GIT_STATUS !== "0") {
5435
- state = { ...state, gitStatus: detectGitStatus(state.cwd) };
5436
- }
5870
+ refreshGitStatus();
5437
5871
  flushPendingRender();
5438
5872
  fixedInput.exitOutputMode(buffer, cursorPos2);
5439
5873
  return;
@@ -5448,13 +5882,10 @@ ${err.message}` : ""}`
5448
5882
  try {
5449
5883
  input.on("data", onData);
5450
5884
  fixedInput.repaint(buffer, cursorPos2);
5885
+ refreshGitStatus();
5451
5886
  await new Promise((resolve2) => {
5452
- const check = setInterval(() => {
5453
- if (done) {
5454
- clearInterval(check);
5455
- resolve2();
5456
- }
5457
- }, 10);
5887
+ resolveDone = resolve2;
5888
+ if (done) resolve2();
5458
5889
  });
5459
5890
  } finally {
5460
5891
  finish();
@@ -5509,9 +5940,13 @@ export {
5509
5940
  createNoopFixedInput,
5510
5941
  createRawInputDecoder,
5511
5942
  createTurnHistory,
5512
- displayWidth2 as displayWidth,
5943
+ displayWidth,
5944
+ enterAltScreen,
5513
5945
  finalizeCurrentTurn,
5514
5946
  fitAnsiLine,
5947
+ installAltScreenRestoreHandlers,
5948
+ isAltScreenOn,
5949
+ leaveAltScreen,
5515
5950
  padOrTruncateToWidth,
5516
5951
  parseScrollAction,
5517
5952
  renderHistory,