nolo-cli 0.11.0 → 0.12.0-alpha.10

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-4F7VZR5U.js} +52 -50
  2. package/{action-AA4IJ5K2.js → action-OTOWUMPW.js} +8 -9
  3. package/{addContentAction-QYVO4SYP.js → addContentAction-JGBUVR7K.js} +7 -9
  4. package/{addDialogAgentAction-WVVKT5WQ.js → addDialogAgentAction-V6CL2UCF.js} +9 -10
  5. package/{agentDeleteCommand-RDPGYIWF.js → agentDeleteCommand-MMRSG6AB.js} +39 -28
  6. package/{agentEmailCommands-2HJJKS5F.js → agentEmailCommands-YFVKOGM7.js} +65 -62
  7. package/{agentListCommands-HFEVV3ZW.js → agentListCommands-2PZHIQ3I.js} +128 -31
  8. package/{agentMachineCommands-25ZOENJU.js → agentMachineCommands-55UUZNHF.js} +38 -26
  9. package/{agentPullCommand-GOL2S4W2.js → agentPullCommand-KRBRIFIY.js} +7 -7
  10. package/{agentRecordCommands-GTCOAM3L.js → agentRecordCommands-BTP3OAPL.js} +57 -54
  11. package/{agentRunCommand-YBS3JDLD.js → agentRunCommand-AA4QLRQZ.js} +60 -59
  12. package/{agentRunControl-K4VKNEHF.js → agentRunControl-IX4F5W2C.js} +3 -3
  13. package/{agentRuntimeLocal-UQBMQI2M.js → agentRuntimeLocal-QDJCBXM2.js} +31 -15
  14. package/{agentSlice-6EKOBCCS.js → agentSlice-6JAJOU3X.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-E4HNJSHC.js → chunk-2BVGERJ2.js} +81 -19
  18. package/{chunk-LP65VRCI.js → chunk-3MI5LDDU.js} +6 -6
  19. package/{chunk-OPZYGHUC.js → chunk-3WGLLTK4.js} +59 -1
  20. package/{chunk-KJR5KAUQ.js → chunk-3XF6LCCN.js} +20 -7
  21. package/{chunk-PXZAAB4L.js → chunk-4LG74DRG.js} +4 -4
  22. package/{chunk-2FTTDCD4.js → chunk-4ON5AV72.js} +2 -1
  23. package/{chunk-IWX4UW2E.js → chunk-524BDL33.js} +89 -8
  24. package/{chunk-ISNCLGUZ.js → chunk-5NBHQD4D.js} +4 -3
  25. package/{chunk-ZPRK73XY.js → chunk-5S4SWV5G.js} +51 -38
  26. package/{chunk-EVZKZF32.js → chunk-5V524MHX.js} +195 -78
  27. package/{chunk-CKOJQDKR.js → chunk-7JJCGV7N.js} +146 -3
  28. package/{chunk-NEM67FX7.js → chunk-ARSC56DC.js} +1 -1
  29. package/{chunk-AFON4QUU.js → chunk-AS3ZQ3O2.js} +19 -28
  30. package/{chunk-4SOKXIW4.js → chunk-B4KT7RN2.js} +10 -13
  31. package/chunk-BYXBJQAS.js +0 -0
  32. package/{chunk-PXQN7IBQ.js → chunk-C3BFP57W.js} +95 -2
  33. package/{chunk-AEZAORLG.js → chunk-CGDQDXKZ.js} +5 -5
  34. package/{chunk-MQI77IRB.js → chunk-DPFWKRHG.js} +2 -2
  35. package/{chunk-TACHTH5D.js → chunk-EIMX5ER5.js} +18 -18
  36. package/{chunk-5MOUAAMK.js → chunk-FSUZIGIG.js} +197 -2578
  37. package/{chunk-FXXSGWAJ.js → chunk-H5Y2H2N6.js} +1 -1
  38. package/chunk-IKXL2HZ3.js +70 -0
  39. package/{chunk-QNMFBBZK.js → chunk-IVAOVLM7.js} +1 -1
  40. package/chunk-JFANOO7I.js +2274 -0
  41. package/{chunk-YPRZSPF3.js → chunk-JKWWVF2I.js} +1 -1
  42. package/{chunk-3NSIQZZ6.js → chunk-JU7J7EAV.js} +45 -45
  43. package/{chunk-X2S7JTIG.js → chunk-KWBH4RAL.js} +1280 -137
  44. package/{chunk-OUI3ZHYD.js → chunk-LUKQQJFL.js} +39 -2
  45. package/{chunk-BX6U3ETE.js → chunk-MG6BVV3T.js} +320 -102
  46. package/{chunk-L53FGHA4.js → chunk-MQRZ5QUH.js} +1 -1
  47. package/{chunk-2W2VYVI5.js → chunk-MZHSAPZP.js} +675 -177
  48. package/{chunk-YOCHZD7D.js → chunk-NCGFB7NN.js} +5 -3
  49. package/{chunk-DWGAEFJL.js → chunk-O4FOPRCP.js} +1 -1
  50. package/chunk-OGGHETUE.js +4900 -0
  51. package/{chunk-PNEW2Z3T.js → chunk-OHEKYYJF.js} +7 -7
  52. package/chunk-OOIN7HSO.js +146 -0
  53. package/{chunk-SV5MWASH.js → chunk-OPM2DE2J.js} +12 -12
  54. package/{chunk-5OIFWVX7.js → chunk-QJSTNKJY.js} +1 -1
  55. package/{chunk-C4FTUXVF.js → chunk-QLUFR47U.js} +11 -1
  56. package/{chunk-LAX2JSTX.js → chunk-QUHUVG6Y.js} +20 -2
  57. package/{chunk-D6OQHXVG.js → chunk-RQSOAMPJ.js} +22 -6
  58. package/{chunk-ZEYDOXHF.js → chunk-RUGEW4P7.js} +1515 -884
  59. package/{chunk-GBDSZATH.js → chunk-RWO5DGXI.js} +3 -3
  60. package/{chunk-S3GS2PRL.js → chunk-TA7VKOHU.js} +613 -70
  61. package/{chunk-XYZA7S7V.js → chunk-TQYYGE6G.js} +6 -6
  62. package/{chunk-YTBMA7NO.js → chunk-TYPHUVK5.js} +70 -8
  63. package/{chunk-4ZDM73SD.js → chunk-V2J635L5.js} +1 -1
  64. package/{chunk-C2MDQQ4O.js → chunk-VBSWHE5W.js} +1 -1
  65. package/{chunk-O26GI2JP.js → chunk-VKG4HXYV.js} +39 -8
  66. package/{chunk-IPYUUY4E.js → chunk-WPHL6ALY.js} +1 -1
  67. package/{chunk-7D3JY6C2.js → chunk-WVTBAALN.js} +64 -5
  68. package/{chunk-XFOLQLDN.js → chunk-XHQWJUW4.js} +2 -2
  69. package/{chunk-Q3UKJVU4.js → chunk-Y5P3ZRGC.js} +10 -10
  70. package/{chunk-KQQVGRA3.js → chunk-YUMYYXPI.js} +53 -183
  71. package/cliEnvHelpers-VYC2OPTB.js +30 -0
  72. package/{createAgentAutomationAction-LTI5QU35.js → createAgentAutomationAction-4NY4JLOK.js} +26 -26
  73. package/{createDialogAction-OJJ7L3L3.js → createDialogAction-I5WUWFJ6.js} +26 -26
  74. package/{createPageAction-5MXWVRGY.js → createPageAction-HFTIQ7TW.js} +26 -26
  75. package/{dbSlice-RQFQQVHT.js → dbSlice-J4PUZ6Y5.js} +6 -7
  76. package/{deleteFile-7XP5BGC7.js → deleteFile-M7UNIUOO.js} +4 -4
  77. package/{dialogCommands-JTJ3RPPW.js → dialogCommands-SVXRJFZS.js} +34 -23
  78. package/{dialogSlice-OWROKURJ.js → dialogSlice-35LA3ZRG.js} +26 -26
  79. package/{docCreateCommands-WJBEW2V7.js → docCreateCommands-Z7RIAYKQ.js} +34 -23
  80. package/{docDeleteCommands-BVEHJOY5.js → docDeleteCommands-NEYHUVZF.js} +35 -25
  81. package/{docReadCommands-NEWOZIJV.js → docReadCommands-I4US2SDE.js} +34 -23
  82. package/{docStore-3DXRYBMD.js → docStore-3CP2IO6M.js} +25 -25
  83. package/{docUpdateCommands-C7WHT7UU.js → docUpdateCommands-UU5HQBRP.js} +34 -23
  84. package/fetchMessages-KTNM7QAR.js +1 -1
  85. package/{getPricing-3E6UNPKL.js → getPricing-UGDJHI7M.js} +7 -3
  86. package/{handleSendMessageAction-B5PRR7BE.js → handleSendMessageAction-FWTLQGOZ.js} +30 -30
  87. package/index.js +77 -76
  88. package/{localDialogRead-YSKFFRTT.js → localDialogRead-2VRI24F4.js} +1 -1
  89. package/{machineCommands-IIIREAEK.js → machineCommands-STHZKBDJ.js} +64 -61
  90. package/{machineWsRunDispatch-3SIU6ILW.js → machineWsRunDispatch-7YHT4TPT.js} +56 -53
  91. package/{memoryCommands-O4MIPIWQ.js → memoryCommands-AFMES3LN.js} +5 -5
  92. package/{models-4OPOOLFS.js → models-LSCRJJ6P.js} +2 -2
  93. package/package.json +1 -1
  94. package/{read-QJNKRROM.js → read-2MIMRBZ2.js} +4 -4
  95. package/{readDocTool-H6X6KO6X.js → readDocTool-QHVGQHUJ.js} +1 -1
  96. package/{readlineWorkspace-HGP3I3OC.js → readlineWorkspace-OMRWYMB6.js} +1279 -716
  97. package/{removeDialogAgentAction-TCT35SOF.js → removeDialogAgentAction-XUZYQBV4.js} +13 -13
  98. package/{runAgentClientLoop-EFM4PTVI.js → runAgentClientLoop-FP2K33ZO.js} +47 -46
  99. package/{runtimeDoctorCommands-ID43Z7VX.js → runtimeDoctorCommands-QNF3J3UV.js} +6 -6
  100. package/{setDialogExtraReferencesAction-CGYLZMFH.js → setDialogExtraReferencesAction-PTAIFKGM.js} +9 -10
  101. package/{setPrimaryDialogAgentAction-F6BFQDJU.js → setPrimaryDialogAgentAction-HRFTDB44.js} +13 -13
  102. package/{settingSlice-2I5HMG47.js → settingSlice-VGRJUSE5.js} +7 -8
  103. package/{skillDiscovery-UGSQGX2U.js → skillDiscovery-6M2BZUFN.js} +3 -3
  104. package/{spaceCommands-MT7BWCIL.js → spaceCommands-GN73BCOB.js} +38 -29
  105. package/{spaceSlice-BS5TSW3W.js → spaceSlice-WTFNKOVA.js} +30 -26
  106. package/{streamAgentChatTurn-I3VBFNR3.js → streamAgentChatTurn-HBAQQVNK.js} +442 -130
  107. package/{toolApiClient-C7P7YSE4.js → toolApiClient-QI3YBNWB.js} +2 -2
  108. package/{tools-XSD2BOEN.js → tools-TPQYTLQZ.js} +34 -33
  109. package/{tuiSummaryLlmCaller-32NRHTT2.js → tuiSummaryLlmCaller-XDC7WG4K.js} +11 -7
  110. package/{updateCommands-VOSPKXFF.js → updateCommands-XZDOOYFH.js} +13 -5
  111. package/{updateDialogTitleAction-7FYNJOGP.js → updateDialogTitleAction-AWXNPUQJ.js} +30 -29
  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-OPM2DE2J.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-RUGEW4P7.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-QUHUVG6Y.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-O4FOPRCP.js";
73
+ import {
74
+ createCliLocalRuntimeAdapter,
75
+ initCliLocale,
76
+ parseCliLocale,
77
+ setCliLocale,
78
+ t
79
+ } from "./chunk-KWBH4RAL.js";
80
+ import "./chunk-TOILAPLH.js";
55
81
  import "./chunk-CMKUBIGW.js";
56
82
  import "./chunk-JBTJAPUF.js";
57
83
  import {
@@ -60,135 +86,150 @@ import {
60
86
  import {
61
87
  queryUserRecords,
62
88
  readDbRecord
63
- } from "./chunk-AEZAORLG.js";
64
- import "./chunk-C2MDQQ4O.js";
89
+ } from "./chunk-CGDQDXKZ.js";
90
+ import "./chunk-VBSWHE5W.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-QJSTNKJY.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-C3BFP57W.js";
101
+ import "./chunk-H5Y2H2N6.js";
102
+ import {
103
+ expandEnabledPacks
104
+ } from "./chunk-MZHSAPZP.js";
105
+ import "./chunk-RQSOAMPJ.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-TQYYGE6G.js";
114
+ import "./chunk-2BVGERJ2.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-FSUZIGIG.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-Y5P3ZRGC.js";
127
+ import "./chunk-RWO5DGXI.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-AS3ZQ3O2.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-MG6BVV3T.js";
136
139
  import "./chunk-ZNFJWEN4.js";
137
- import "./chunk-YOCHZD7D.js";
140
+ import "./chunk-5S4SWV5G.js";
141
+ import "./chunk-NCGFB7NN.js";
138
142
  import {
139
143
  readCommandActionGatePayload,
140
144
  resolveAgentImageInputSupport
141
- } from "./chunk-S3GS2PRL.js";
142
- import "./chunk-OPZYGHUC.js";
143
- import "./chunk-OUI3ZHYD.js";
144
- import "./chunk-YTBMA7NO.js";
145
- import "./chunk-4SOKXIW4.js";
145
+ } from "./chunk-TA7VKOHU.js";
146
+ import "./chunk-3WGLLTK4.js";
147
+ import {
148
+ buildCurrentTimeBlock,
149
+ buildIdentityBlock,
150
+ buildRuntimeGuidanceBlocks
151
+ } from "./chunk-LUKQQJFL.js";
152
+ import "./chunk-TYPHUVK5.js";
153
+ import {
154
+ canonicalizeToolNames
155
+ } from "./chunk-B4KT7RN2.js";
156
+ import {
157
+ DEFAULT_CONTEXT_WINDOW,
158
+ estimateTokenCount,
159
+ getModelContextWindow,
160
+ planCompression,
161
+ serializeMessageContent
162
+ } from "./chunk-JFANOO7I.js";
146
163
  import {
147
164
  clipCompactText
148
165
  } from "./chunk-5MMAK75Q.js";
149
- import "./chunk-BBYVOSU2.js";
150
- import "./chunk-5CS33IBL.js";
166
+ import "./chunk-OGGHETUE.js";
167
+ import "./chunk-PDL2AV3E.js";
151
168
  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";
169
+ import "./chunk-BYXBJQAS.js";
170
+ import "./chunk-QLUFR47U.js";
171
+ import "./chunk-KKY4B2Q2.js";
172
+ import "./chunk-5MIDPSSK.js";
158
173
  import {
159
174
  extractCustomId
160
175
  } from "./chunk-DFFIZ25G.js";
161
176
  import "./chunk-3ASDTTEI.js";
177
+ import "./chunk-4ON5AV72.js";
162
178
  import {
163
- asTrimmedLowercaseString
164
- } from "./chunk-QSJ3HFEF.js";
179
+ toErrorMessage
180
+ } from "./chunk-HNMXYYJR.js";
181
+ import "./chunk-H33743XH.js";
182
+ import "./chunk-LM6IYQ3F.js";
183
+ import "./chunk-SOTZ6RDN.js";
184
+ import "./chunk-HNW7IJSK.js";
185
+ import "./chunk-XENYHRUE.js";
165
186
  import {
166
187
  compactWhitespace
167
188
  } from "./chunk-DWLEOOGR.js";
168
- import "./chunk-2FTTDCD4.js";
189
+ import {
190
+ runSelfUpdate
191
+ } from "./chunk-7JJCGV7N.js";
192
+ import {
193
+ saveProfileAgentSelection,
194
+ saveProfileLocale
195
+ } from "./chunk-ROWQEJRP.js";
196
+ import {
197
+ readPipeText,
198
+ spawnProcess
199
+ } from "./chunk-IDZTNRP2.js";
200
+ import "./chunk-IKXL2HZ3.js";
201
+ import "./chunk-524BDL33.js";
202
+ import "./chunk-DPFWKRHG.js";
203
+ import "./chunk-JU7J7EAV.js";
204
+ import "./chunk-Y5GCAMGP.js";
205
+ import "./chunk-KX6YBIHG.js";
206
+ import "./chunk-WVTBAALN.js";
207
+ import "./chunk-VKG4HXYV.js";
208
+ import "./chunk-WPHL6ALY.js";
209
+ import "./chunk-O4BU24WZ.js";
210
+ import "./chunk-I42VC5KR.js";
211
+ import "./chunk-WWC7I6TA.js";
212
+ import {
213
+ asTrimmedLowercaseString
214
+ } from "./chunk-QSJ3HFEF.js";
215
+ import "./chunk-XHQWJUW4.js";
216
+ import "./chunk-TSUJOM6N.js";
217
+ import "./chunk-OOSFSFMA.js";
218
+ import "./chunk-GBRG75BQ.js";
219
+ import "./chunk-KUG52IAP.js";
220
+ import "./chunk-3EUW3WEO.js";
221
+ import "./chunk-U6VC4MU4.js";
169
222
  import {
170
223
  parseUserIdFromAuthToken,
171
224
  resolveAuthToken,
172
225
  resolveServerCandidates,
173
226
  resolveServerUrl
174
- } from "./chunk-ISNCLGUZ.js";
227
+ } from "./chunk-5NBHQD4D.js";
175
228
  import {
176
229
  DEFAULT_NOLO_SERVER_URL
177
230
  } 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
231
  import "./chunk-LCBAMY2M.js";
232
+ import "./chunk-4SN6J65J.js";
192
233
  import "./chunk-CF7PE763.js";
193
234
  import {
194
235
  asOptionalTrimmedString
@@ -198,8 +239,81 @@ import "./chunk-4VNS5WPM.js";
198
239
  // packages/cli/tui/readlineWorkspace.ts
199
240
  import { createInterface } from "node:readline";
200
241
  import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
201
- import { existsSync as existsSync2, readFileSync } from "node:fs";
242
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
243
+ import { join as join2 } from "node:path";
244
+
245
+ // packages/cli/client/estimateCliContext.ts
246
+ import { existsSync, readFileSync } from "node:fs";
202
247
  import { join } from "node:path";
248
+ var DEFAULT_CLI_PACKS = ["code", "agent-orchestration", "skills"];
249
+ var DEFAULT_CLI_PLATFORM_TOOLS = [
250
+ "ui_ask_choice",
251
+ "exa_search",
252
+ "fetchWebpage"
253
+ ];
254
+ 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";
255
+ function readAgentsMd(cwd) {
256
+ for (const name of ["AGENTS.md", "agents.md"]) {
257
+ const path = join(cwd, name);
258
+ if (!existsSync(path)) continue;
259
+ try {
260
+ return readFileSync(path, "utf8");
261
+ } catch {
262
+ return "";
263
+ }
264
+ }
265
+ return "";
266
+ }
267
+ function estimateToolSchemas(toolNames) {
268
+ const toolset = buildLocalWorkspaceToolset({
269
+ declaredToolNames: toolNames,
270
+ exposeShellTools: true
271
+ });
272
+ const workspaceTools = buildLocalWorkspaceOpenAiTools({
273
+ toolNames: toolset.toolNames,
274
+ exposeShellTools: true
275
+ });
276
+ const platformNames = DEFAULT_CLI_PLATFORM_TOOLS.filter(
277
+ (name) => toolNames.includes(name)
278
+ );
279
+ const platformTools = platformNames.length > 0 ? prepareTools([...platformNames]) : [];
280
+ return estimateTokenCount(JSON.stringify([...workspaceTools, ...platformTools]));
281
+ }
282
+ function estimateDefaultCliContextTokens(opts = {}) {
283
+ const cwd = opts.cwd?.trim() || process.cwd();
284
+ const model = opts.model?.trim() || "deepseek-v4-flash";
285
+ const agentName = opts.agentName?.trim() || "DeepSeek V4 Flash";
286
+ const agentKey = opts.agentKey?.trim() || "agent-pub-deepseek-v4-flash";
287
+ const prompt = opts.agentPrompt?.trim() || DEFAULT_FLASH_PROMPT;
288
+ const packTools = canonicalizeToolNames(
289
+ expandEnabledPacks([...DEFAULT_CLI_PACKS], [])
290
+ );
291
+ const toolNames = canonicalizeToolNames([
292
+ ...packTools,
293
+ ...DEFAULT_CLI_PLATFORM_TOOLS
294
+ ]);
295
+ const guidance = buildRuntimeGuidanceBlocks(toolNames);
296
+ const systemParts = [
297
+ prompt,
298
+ buildIdentityBlock({
299
+ agentName,
300
+ agentId: agentKey,
301
+ model
302
+ }),
303
+ guidance.startupProtocol,
304
+ guidance.contextLayerContract,
305
+ guidance.emailRegistrationWorkflow,
306
+ guidance.webResearchToolPolicy,
307
+ buildCurrentTimeBlock(/* @__PURE__ */ new Date()),
308
+ readAgentsMd(cwd),
309
+ buildSkillDiscoveryContextBlock(cwd) ?? ""
310
+ ];
311
+ const systemTokens = estimateTokenCount(
312
+ systemParts.filter((part) => part?.trim()).join("\n\n")
313
+ );
314
+ const toolTokens = estimateToolSchemas(toolNames);
315
+ return Math.max(0, systemTokens + toolTokens);
316
+ }
203
317
 
204
318
  // packages/cli/client/compactDialog.ts
205
319
  import { ulid } from "ulid";
@@ -480,6 +594,12 @@ function renderDialogRow(args, colorEnabled = resolveCliColorEnabled()) {
480
594
  function renderDialogOverflow(text, colorEnabled = resolveCliColorEnabled()) {
481
595
  return themeText(` ${text}`, "chrome", colorEnabled);
482
596
  }
597
+ function renderOverflowAbove(count, colorEnabled = resolveCliColorEnabled()) {
598
+ return renderDialogOverflow(t("dialogMoreAbove", String(count)), colorEnabled);
599
+ }
600
+ function renderOverflowBelow(count, colorEnabled = resolveCliColorEnabled()) {
601
+ return renderDialogOverflow(t("dialogMoreBelow", String(count)), colorEnabled);
602
+ }
483
603
  function renderDialogCommand(text, colorEnabled = resolveCliColorEnabled()) {
484
604
  return themeText(` ${text}`, "danger", colorEnabled);
485
605
  }
@@ -578,7 +698,7 @@ function renderSelectDialog(args) {
578
698
  ];
579
699
  const lines = [...titleLines, ""];
580
700
  if (window.start > 0) {
581
- lines.push(renderDialogOverflow(`\u2191 ${window.start} more`));
701
+ lines.push(renderOverflowAbove(window.start));
582
702
  }
583
703
  for (let index = window.start; index < window.end; index += 1) {
584
704
  const item = args.items[index];
@@ -591,7 +711,7 @@ function renderSelectDialog(args) {
591
711
  );
592
712
  }
593
713
  if (window.end < total) {
594
- lines.push("", renderDialogOverflow(`\u2193 ${total - window.end} more`));
714
+ lines.push("", renderOverflowBelow(total - window.end));
595
715
  }
596
716
  return lines.join("\n");
597
717
  }
@@ -820,7 +940,7 @@ async function runSelectDialog(args) {
820
940
  // packages/cli/tui/confirmDialog.ts
821
941
  var FALLBACK_TERMINAL_WIDTH = 80;
822
942
  var WIDE_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/;
823
- function displayWidth(text) {
943
+ function displayWidth2(text) {
824
944
  let width = 0;
825
945
  for (const char of text) width += WIDE_CHAR.test(char) ? 2 : 1;
826
946
  return width;
@@ -828,9 +948,9 @@ function displayWidth(text) {
828
948
  function truncateCommand(command, maxWidth) {
829
949
  const indent = 2;
830
950
  const budget = Math.max(10, maxWidth - indent);
831
- if (displayWidth(command) <= budget) return command;
951
+ if (displayWidth2(command) <= budget) return command;
832
952
  const marker = ` ${t("dialogConfirmCommandTruncated")}`;
833
- const keep = Math.max(1, budget - displayWidth(marker));
953
+ const keep = Math.max(1, budget - displayWidth2(marker));
834
954
  let kept = "";
835
955
  let width = 0;
836
956
  for (const char of command) {
@@ -860,9 +980,15 @@ async function runConfirmDialog(args) {
860
980
  value: false
861
981
  }
862
982
  ];
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;
983
+ let titleText = args.request.title;
984
+ let bodyText = args.request.body;
985
+ if (args.request.action === "destructive_shell_command") {
986
+ titleText = t("dialogConfirmTitle");
987
+ bodyText = t("dialogConfirmBody");
988
+ } else if (args.request.action === "external_file_access") {
989
+ titleText = t("dialogConfirmExternalFileTitle");
990
+ bodyText = t("dialogConfirmExternalFileBody");
991
+ }
866
992
  const terminalWidth = typeof output.columns === "number" && output.columns > 0 ? output.columns : FALLBACK_TERMINAL_WIDTH;
867
993
  const titleLines = [
868
994
  renderDialogTitle(`${titleText} ${t("dialogConfirmHint")}`)
@@ -946,6 +1072,27 @@ function createInitialAskChoiceState(questions) {
946
1072
  };
947
1073
  }
948
1074
  function askChoiceReducer(state, action) {
1075
+ if (action.type === "HYDRATE_QUESTIONS") {
1076
+ if (state.phase !== "active") return state;
1077
+ const newQuestions = action.questions;
1078
+ if (newQuestions.length <= state.questionStates.length) {
1079
+ if (newQuestions.length === state.questions.length) return state;
1080
+ return { ...state, questions: newQuestions };
1081
+ }
1082
+ const appended = newQuestions.slice(state.questionStates.length).map(() => ({
1083
+ cursorIndex: 0,
1084
+ selectedIds: [],
1085
+ pickedId: null,
1086
+ otherText: "",
1087
+ otherFocused: false
1088
+ }));
1089
+ return {
1090
+ ...state,
1091
+ questions: newQuestions,
1092
+ questionStates: [...state.questionStates, ...appended],
1093
+ activeIndex: Math.min(state.activeIndex, newQuestions.length - 1)
1094
+ };
1095
+ }
949
1096
  if (state.phase !== "active") return state;
950
1097
  switch (action.type) {
951
1098
  case "CANCEL":
@@ -1112,19 +1259,23 @@ function renderTabBar(questions, activeIndex, colorEnabled) {
1112
1259
  }
1113
1260
  return colorEnabled ? themeText(` ${label} `, "muted", colorEnabled) : ` ${label} `;
1114
1261
  });
1115
- const submitLabel = "Submit";
1262
+ const submitLabel = t("askChoiceSubmit");
1116
1263
  return tabs.join(" ") + " " + (colorEnabled ? themeText(submitLabel, "chrome", colorEnabled) : submitLabel);
1117
1264
  }
1118
- function renderFooter(colorEnabled) {
1119
- const hints = "\u21B5 pick/submit \xB7 space toggle \xB7 tab switch \xB7 esc cancel";
1265
+ function renderFooter(multiSelect, colorEnabled) {
1266
+ const hints = multiSelect ? t("askChoiceFooterMulti") : t("askChoiceFooterSingle");
1120
1267
  return colorEnabled ? themeText(` ${hints}`, "chrome", colorEnabled) : ` ${hints}`;
1121
1268
  }
1269
+ function otherRowPrefix(index) {
1270
+ return ` [${index + 1}] ${t("askChoiceOtherLabel")}: `;
1271
+ }
1122
1272
  function renderAskChoiceFrame(state) {
1123
1273
  const colorEnabled = resolveCliColorEnabled();
1124
1274
  const q = state.questions[state.activeIndex];
1125
1275
  const qs = state.questionStates[state.activeIndex];
1126
1276
  const lines = [];
1127
- lines.push(renderDialogTitle("question"));
1277
+ let otherCursor = null;
1278
+ lines.push(renderDialogTitle(t("askChoiceTitle")));
1128
1279
  lines.push("");
1129
1280
  if (state.questions.length > 1) {
1130
1281
  lines.push(renderTabBar(state.questions, state.activeIndex, colorEnabled));
@@ -1135,11 +1286,11 @@ function renderAskChoiceFrame(state) {
1135
1286
  );
1136
1287
  if (q.multiSelect) {
1137
1288
  lines.push(
1138
- colorEnabled ? themeText(" Space to toggle, Enter to confirm selection.", "muted", colorEnabled) : " Space to toggle, Enter to confirm selection."
1289
+ colorEnabled ? themeText(` ${t("askChoiceHintMulti")}`, "muted", colorEnabled) : ` ${t("askChoiceHintMulti")}`
1139
1290
  );
1140
1291
  } else {
1141
1292
  lines.push(
1142
- colorEnabled ? themeText(" Type your answer, then press Enter to save.", "muted", colorEnabled) : " Type your answer, then press Enter to save."
1293
+ colorEnabled ? themeText(` ${t("askChoiceHintSingle")}`, "muted", colorEnabled) : ` ${t("askChoiceHintSingle")}`
1143
1294
  );
1144
1295
  }
1145
1296
  lines.push("");
@@ -1149,9 +1300,7 @@ function renderAskChoiceFrame(state) {
1149
1300
  total: totalRows
1150
1301
  });
1151
1302
  if (window.start > 0) {
1152
- lines.push(
1153
- colorEnabled ? themeText(` \u2191 ${window.start} more`, "chrome", colorEnabled) : ` \u2191 ${window.start} more`
1154
- );
1303
+ lines.push(renderOverflowAbove(window.start));
1155
1304
  }
1156
1305
  for (let i = window.start; i < window.end; i++) {
1157
1306
  if (i < q.choices.length) {
@@ -1169,25 +1318,29 @@ function renderAskChoiceFrame(state) {
1169
1318
  } else {
1170
1319
  const focused = qs.cursorIndex === i;
1171
1320
  const marker = focused ? DIALOG_CURSOR : " ";
1172
- const otherContent = qs.otherFocused ? `${qs.otherText}\u2588` : qs.otherText || "";
1173
- const row = `${marker} [${i + 1}] Other: ${otherContent}`;
1321
+ const plainPrefix = `${marker}${otherRowPrefix(i)}`;
1322
+ const plainRow = `${plainPrefix}${qs.otherText}`;
1174
1323
  if (focused && colorEnabled) {
1175
1324
  lines.push(
1176
- `\x1B[1m${themeColorSequence("accent")}${row}\x1B[0m`
1325
+ `\x1B[1m${themeColorSequence("accent")}${plainRow}\x1B[0m`
1177
1326
  );
1178
1327
  } else {
1179
- lines.push(row);
1328
+ lines.push(plainRow);
1329
+ }
1330
+ if (qs.otherFocused) {
1331
+ otherCursor = {
1332
+ lineIndex: lines.length - 1,
1333
+ col: displayWidth(plainPrefix + qs.otherText)
1334
+ };
1180
1335
  }
1181
1336
  }
1182
1337
  }
1183
1338
  if (window.end < totalRows) {
1184
- lines.push(
1185
- colorEnabled ? themeText(` \u2193 ${totalRows - window.end} more`, "chrome", colorEnabled) : ` \u2193 ${totalRows - window.end} more`
1186
- );
1339
+ lines.push(renderOverflowBelow(totalRows - window.end));
1187
1340
  }
1188
1341
  lines.push("");
1189
- lines.push(renderFooter(colorEnabled));
1190
- return lines.join("\n");
1342
+ lines.push(renderFooter(q.multiSelect, colorEnabled));
1343
+ return { text: lines.join("\n"), otherCursor };
1191
1344
  }
1192
1345
  async function runAskChoiceDialog(args) {
1193
1346
  const { request } = args;
@@ -1200,9 +1353,6 @@ async function runAskChoiceDialog(args) {
1200
1353
  if (normalized.questions.length === 0) {
1201
1354
  return { kind: "cancelled" };
1202
1355
  }
1203
- const q0 = normalized.questions[0];
1204
- if (normalized.questions.length === 1 && !q0.multiSelect && !q0.allowOther) {
1205
- }
1206
1356
  let state = createInitialAskChoiceState(normalized.questions);
1207
1357
  const output = args.output ?? process.stdout;
1208
1358
  const input = args.input ?? process.stdin;
@@ -1216,13 +1366,28 @@ async function runAskChoiceDialog(args) {
1216
1366
  typeof args.bottomRow === "function" ? args.bottomRow() : args.bottomRow ?? 0
1217
1367
  );
1218
1368
  let lastBottomRow = 0;
1369
+ let scrolledHeadroom = false;
1370
+ let unanchoredCursorLift = 0;
1371
+ const restoreUnanchoredCursor = () => {
1372
+ if (unanchoredCursorLift <= 0) return;
1373
+ output.write(`\x1B[${unanchoredCursorLift}B`);
1374
+ unanchoredCursorLift = 0;
1375
+ };
1219
1376
  const paint = () => {
1220
1377
  const frame = renderAskChoiceFrame(state);
1221
- const lines = frame.split("\n");
1378
+ const lines = frame.text.split("\n");
1222
1379
  const lineCount = lines.length;
1223
1380
  const canPosition = outputIsTty(output) && typeof output.write === "function";
1224
1381
  if (bottomAnchored && canPosition) {
1225
1382
  const anchorRow = resolveBottomRow();
1383
+ if (!scrolledHeadroom) {
1384
+ const ttyRows = typeof output === "object" && output !== null && "rows" in output && typeof output.rows === "number" ? output.rows : 24;
1385
+ const scrollN = Math.min(lineCount, Math.max(0, ttyRows - 1));
1386
+ if (scrollN > 0) {
1387
+ output.write(`\x1B[${scrollN}S`);
1388
+ }
1389
+ scrolledHeadroom = true;
1390
+ }
1226
1391
  clearAnchoredLines(
1227
1392
  output,
1228
1393
  lastBottomRow > 0 ? lastBottomRow : anchorRow,
@@ -1235,22 +1400,37 @@ async function runAskChoiceDialog(args) {
1235
1400
  }
1236
1401
  lastBottomRow = anchorRow;
1237
1402
  renderedLineCount = lineCount;
1403
+ unanchoredCursorLift = 0;
1404
+ if (frame.otherCursor) {
1405
+ const cursorRow = anchorRow - (lineCount - 1 - frame.otherCursor.lineIndex);
1406
+ if (cursorRow >= 1) {
1407
+ output.write(`\x1B[${cursorRow};${frame.otherCursor.col + 1}H`);
1408
+ }
1409
+ }
1238
1410
  return;
1239
1411
  }
1240
1412
  if (canPosition) {
1413
+ restoreUnanchoredCursor();
1241
1414
  for (let i = 0; i < renderedLineCount; i++) {
1242
1415
  output.write("\x1B[1A\x1B[2K");
1243
1416
  }
1244
- output.write(`${frame}
1417
+ output.write(`${frame.text}
1245
1418
  `);
1246
1419
  renderedLineCount = lineCount;
1420
+ if (frame.otherCursor) {
1421
+ const linesFromBottom = lineCount - frame.otherCursor.lineIndex;
1422
+ output.write(`\x1B[${linesFromBottom}A`);
1423
+ output.write(`\x1B[${frame.otherCursor.col + 1}G`);
1424
+ unanchoredCursorLift = linesFromBottom;
1425
+ }
1247
1426
  return;
1248
1427
  }
1249
1428
  if (typeof output.write === "function") {
1250
- output.write(`${frame}
1429
+ output.write(`${frame.text}
1251
1430
  `);
1252
1431
  }
1253
1432
  renderedLineCount = lineCount;
1433
+ unanchoredCursorLift = 0;
1254
1434
  };
1255
1435
  const resizeTarget = output;
1256
1436
  const onOutputResize = () => paint();
@@ -1278,6 +1458,9 @@ async function runAskChoiceDialog(args) {
1278
1458
  continue;
1279
1459
  }
1280
1460
  let action = null;
1461
+ const qs = state.questionStates[state.activeIndex];
1462
+ const q = state.questions[state.activeIndex];
1463
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
1281
1464
  if (isCancel(sequence)) {
1282
1465
  action = { type: "CANCEL" };
1283
1466
  } else if (sequence === CSI_TAB) {
@@ -1289,11 +1472,17 @@ async function runAskChoiceDialog(args) {
1289
1472
  } else if (isArrowDown(sequence)) {
1290
1473
  action = { type: "MOVE_CURSOR", delta: 1 };
1291
1474
  } else if (sequence === CSI_SPACE) {
1292
- action = { type: "TOGGLE_AT_CURSOR" };
1475
+ if (qs.otherFocused) {
1476
+ action = {
1477
+ type: "SET_OTHER_TEXT",
1478
+ text: qs.otherText + " "
1479
+ };
1480
+ } else if (q.multiSelect) {
1481
+ action = { type: "TOGGLE_AT_CURSOR" };
1482
+ } else if (isOtherRow) {
1483
+ action = { type: "FOCUS_OTHER" };
1484
+ }
1293
1485
  } 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
1486
  if (qs.otherFocused) {
1298
1487
  action = { type: "BLUR_OTHER" };
1299
1488
  } else if (isOtherRow && !qs.otherFocused) {
@@ -1304,24 +1493,29 @@ async function runAskChoiceDialog(args) {
1304
1493
  action = { type: "SELECT_AT_CURSOR" };
1305
1494
  }
1306
1495
  } else if (sequence === CSI_BACKSPACE || sequence === CSI_BACKSPACE_ALT) {
1307
- const qs = state.questionStates[state.activeIndex];
1308
1496
  if (qs.otherFocused && qs.otherText.length > 0) {
1497
+ const chars = Array.from(qs.otherText);
1309
1498
  action = {
1310
1499
  type: "SET_OTHER_TEXT",
1311
- text: qs.otherText.slice(0, -1)
1500
+ text: chars.slice(0, -1).join("")
1312
1501
  };
1313
1502
  }
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
- };
1503
+ } else if (sequence.length >= 1 && sequence.charCodeAt(0) >= 32 && !sequence.startsWith("\x1B")) {
1504
+ if (qs.otherFocused || isOtherRow && q.allowOther) {
1505
+ const cleaned = sequence.replace(/[\x00-\x1f\x7f]/g, "");
1506
+ if (cleaned) {
1507
+ if (!qs.otherFocused) {
1508
+ state = askChoiceReducer(state, { type: "FOCUS_OTHER" });
1509
+ }
1510
+ const currentText = state.questionStates[state.activeIndex].otherText;
1511
+ action = {
1512
+ type: "SET_OTHER_TEXT",
1513
+ text: currentText + cleaned
1514
+ };
1515
+ }
1321
1516
  }
1322
1517
  }
1323
1518
  if (action) {
1324
- const prev = state;
1325
1519
  state = askChoiceReducer(state, action);
1326
1520
  if (state.phase !== "active") break;
1327
1521
  if (action.type === "BLUR_OTHER" && state.activeIndex >= state.questions.length - 1 && canSubmit(state)) {
@@ -1345,6 +1539,7 @@ async function runAskChoiceDialog(args) {
1345
1539
  renderedLineCount
1346
1540
  );
1347
1541
  } else {
1542
+ restoreUnanchoredCursor();
1348
1543
  for (let i = 0; i < renderedLineCount; i++) {
1349
1544
  output.write("\x1B[1A\x1B[2K");
1350
1545
  }
@@ -1358,13 +1553,13 @@ async function runAskChoiceDialog(args) {
1358
1553
  }
1359
1554
  if (result.answers.length === 1) {
1360
1555
  const a = result.answers[0];
1556
+ const q = normalized.questions[0];
1557
+ const labels = a.selectedIds.map((id) => q.choices.find((c) => c.id === id)?.label ?? "").filter(Boolean);
1558
+ if (a.otherText) labels.push(a.otherText);
1361
1559
  return {
1362
1560
  kind: "selected",
1363
1561
  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 || ""
1562
+ label: labels.join(", ") || a.otherText || ""
1368
1563
  };
1369
1564
  }
1370
1565
  return {
@@ -1422,8 +1617,13 @@ function createActivityIndicator(deps) {
1422
1617
  let lastActivityAt = 0;
1423
1618
  let frameIndex = 0;
1424
1619
  let timer = null;
1620
+ let stopping = false;
1425
1621
  const elapsedSecFrom = (startedAt) => startedAt > 0 ? Math.max(0, Math.floor((now() - startedAt) / 1e3)) : 0;
1426
1622
  const tick = () => {
1623
+ if (stopping) {
1624
+ deps.onRepaint();
1625
+ return;
1626
+ }
1427
1627
  frameIndex += 1;
1428
1628
  if (explicitLabel === null && !fallbackActive && deps.isTurnActive() && lastActivityAt > 0 && now() - lastActivityAt >= fallbackDelayMs) {
1429
1629
  fallbackActive = true;
@@ -1452,9 +1652,21 @@ function createActivityIndicator(deps) {
1452
1652
  ensureTimer();
1453
1653
  deps.onRepaint();
1454
1654
  };
1655
+ const markStopping = () => {
1656
+ stopping = true;
1657
+ deps.onRepaint();
1658
+ };
1659
+ const isStopping = () => stopping;
1455
1660
  const getView = () => {
1456
1661
  if (!deps.isTurnActive()) return null;
1457
1662
  const frame = ACTIVITY_FRAMES[frameIndex % ACTIVITY_FRAMES.length];
1663
+ if (stopping) {
1664
+ return {
1665
+ frame,
1666
+ label: deps.stoppingLabel(),
1667
+ elapsedSec: 0
1668
+ };
1669
+ }
1458
1670
  if (explicitLabel !== null) {
1459
1671
  return {
1460
1672
  frame,
@@ -1471,6 +1683,36 @@ function createActivityIndicator(deps) {
1471
1683
  }
1472
1684
  return null;
1473
1685
  };
1686
+ let agentRunSnapshot = null;
1687
+ const updateAgentRun = (snapshot) => {
1688
+ agentRunSnapshot = snapshot;
1689
+ deps.onRepaint();
1690
+ };
1691
+ const clearAgentRun = () => {
1692
+ agentRunSnapshot = null;
1693
+ deps.onRepaint();
1694
+ };
1695
+ const getAgentRun = () => agentRunSnapshot;
1696
+ const getActivityLines = (colorEnabled = resolveCliColorEnabled()) => {
1697
+ const lines = [];
1698
+ const baseView = getView();
1699
+ if (baseView) {
1700
+ const elapsed = Math.max(0, baseView.elapsedSec);
1701
+ const elapsedStr = `${elapsed}s`;
1702
+ const stopHint = t("stopHint");
1703
+ if (!colorEnabled) {
1704
+ lines.push(`${baseView.frame} ${baseView.label} (${elapsedStr}) \xB7 ${stopHint}`);
1705
+ } else {
1706
+ lines.push(
1707
+ themeText(baseView.frame, "accent") + " " + themeText(baseView.label, "muted") + themeText(` (${elapsedStr})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome")
1708
+ );
1709
+ }
1710
+ }
1711
+ if (agentRunSnapshot) {
1712
+ lines.push(...formatAgentRunPanelLines(agentRunSnapshot, colorEnabled));
1713
+ }
1714
+ return lines.length > 0 ? lines : null;
1715
+ };
1474
1716
  const stop = () => {
1475
1717
  if (timer !== null) {
1476
1718
  clearIntervalFn(timer);
@@ -1481,8 +1723,70 @@ function createActivityIndicator(deps) {
1481
1723
  fallbackActive = false;
1482
1724
  fallbackStartedAt = 0;
1483
1725
  lastActivityAt = 0;
1726
+ stopping = false;
1727
+ agentRunSnapshot = null;
1484
1728
  };
1485
- return { report, getView, stop };
1729
+ return {
1730
+ report,
1731
+ updateAgentRun,
1732
+ clearAgentRun,
1733
+ getAgentRun,
1734
+ getView,
1735
+ getActivityLines,
1736
+ markStopping,
1737
+ isStopping,
1738
+ stop
1739
+ };
1740
+ }
1741
+ function formatAgentRunPanelLines(snapshot, colorEnabled) {
1742
+ const lines = [];
1743
+ const name = snapshot.agentName || "sub-agent";
1744
+ const runId = snapshot.runId ? ` (${snapshot.runId.slice(0, 8)})` : "";
1745
+ const status = snapshot.status || "running";
1746
+ let statusSymbol = "\u231B";
1747
+ let statusColor = "warning";
1748
+ if (status === "completed" || status === "finished" || status === "success") {
1749
+ statusSymbol = "\u2713";
1750
+ statusColor = "success";
1751
+ } else if (status === "killed" || status === "cancelled" || status === "failed" || status === "stopped") {
1752
+ statusSymbol = "\u{1F534}";
1753
+ statusColor = "danger";
1754
+ }
1755
+ const pollPart = snapshot.polls !== void 0 ? ` (polls: ${snapshot.polls})` : "";
1756
+ const errPart = snapshot.errorMessage ? ` \xB7 ${snapshot.errorMessage}` : "";
1757
+ if (!colorEnabled) {
1758
+ lines.push(`\u{1F916} Sub-Agent: ${name}${runId} \xB7 ${statusSymbol} ${status}${pollPart}${errPart}`);
1759
+ } else {
1760
+ const botIcon = themeText("\u{1F916}", "accent", true);
1761
+ const labelText = themeText(" Sub-Agent: ", "muted", true);
1762
+ const nameText = themeText(`${name}${runId}`, "chrome", true);
1763
+ const statusText = themeText(` \xB7 ${statusSymbol} ${status}`, statusColor, true);
1764
+ const pollsText = pollPart ? themeText(pollPart, "chrome") : "";
1765
+ const errorText = errPart ? themeText(errPart, "danger") : "";
1766
+ lines.push(`${botIcon}${labelText}${nameText}${statusText}${pollsText}${errorText}`);
1767
+ }
1768
+ let logLine;
1769
+ if (snapshot.logTail) {
1770
+ const trimmed = snapshot.logTail.trim();
1771
+ if (trimmed) {
1772
+ const parts = trimmed.split(/\r?\n/);
1773
+ logLine = parts[parts.length - 1];
1774
+ }
1775
+ } else if (snapshot.logLines && snapshot.logLines.length > 0) {
1776
+ logLine = snapshot.logLines[snapshot.logLines.length - 1];
1777
+ }
1778
+ if (logLine && logLine.trim()) {
1779
+ const cleanLog = logLine.trim();
1780
+ if (!colorEnabled) {
1781
+ lines.push(` \u2514 Log: ${cleanLog}`);
1782
+ } else {
1783
+ const branchChar = themeText(" \u2514 ", "muted");
1784
+ const logTag = themeText("Log: ", "muted");
1785
+ const logContent = themeText(cleanLog, "chrome");
1786
+ lines.push(`${branchChar}${logTag}${logContent}`);
1787
+ }
1788
+ }
1789
+ return lines;
1486
1790
  }
1487
1791
 
1488
1792
  // packages/ai/agent/utils/sortUtils.ts
@@ -1533,12 +1837,12 @@ function sortAgentsFavoriteOwnedPublic(items) {
1533
1837
  // packages/cli/tui/agentCatalog.ts
1534
1838
  var DEFAULT_TUI_AGENT_KEY = "agent-pub-01NOLOAPPBLD000000019KCKT0";
1535
1839
  function formatAgentSourceLabel(entry) {
1536
- if (entry.kind === "platform" || entry.apiSource === "platform") return "\u5E73\u53F0";
1840
+ if (entry.kind === "platform" || entry.apiSource === "platform") return t("agentSourcePlatform");
1537
1841
  if (entry.apiSource === "cli") {
1538
- return entry.cliProvider ? `\u8BA2\u9605(${entry.cliProvider})` : "\u8BA2\u9605";
1842
+ return entry.cliProvider ? `${t("agentSourceSubscription")}(${entry.cliProvider})` : t("agentSourceSubscription");
1539
1843
  }
1540
- if (entry.apiSource === "custom") return "API";
1541
- return "\u5E73\u53F0";
1844
+ if (entry.apiSource === "custom") return t("agentSourceApi");
1845
+ return t("agentSourcePlatform");
1542
1846
  }
1543
1847
  var PLATFORM_AGENTS = [
1544
1848
  {
@@ -1722,13 +2026,25 @@ function findAgentCatalogEntry(entries, rawTarget) {
1722
2026
  if (!target) return null;
1723
2027
  if (/^\d+$/.test(target)) {
1724
2028
  const entry = entries[Number(target) - 1];
1725
- return entry ? { name: entry.name, key: entry.key } : null;
2029
+ return entry ? {
2030
+ name: entry.name,
2031
+ key: entry.key,
2032
+ model: entry.model,
2033
+ ...entry.apiSource ? { apiSource: entry.apiSource } : entry.kind === "platform" ? { apiSource: "platform" } : {}
2034
+ } : null;
1726
2035
  }
1727
2036
  const lower = target.toLowerCase();
1728
2037
  const byName = entries.find(
1729
2038
  (entry) => entry.name.toLowerCase() === lower || entry.key.toLowerCase() === lower || entry.key.toLowerCase().endsWith(`-${lower}`)
1730
2039
  );
1731
- if (byName) return { name: byName.name, key: byName.key };
2040
+ if (byName) {
2041
+ return {
2042
+ name: byName.name,
2043
+ key: byName.key,
2044
+ model: byName.model,
2045
+ ...byName.apiSource ? { apiSource: byName.apiSource } : byName.kind === "platform" ? { apiSource: "platform" } : {}
2046
+ };
2047
+ }
1732
2048
  if (target.startsWith("agent-") || target.startsWith("agent-pub-")) {
1733
2049
  return { name: target, key: target };
1734
2050
  }
@@ -1787,6 +2103,8 @@ async function runAgentPicker(args) {
1787
2103
  kind: "selected",
1788
2104
  name: selected.name,
1789
2105
  key: selected.key,
2106
+ model: selected.model,
2107
+ apiSource: selected.apiSource ?? (selected.kind === "platform" ? "platform" : void 0),
1790
2108
  entries
1791
2109
  };
1792
2110
  }
@@ -1796,7 +2114,11 @@ function resolveAgentSwitchTarget(rawTarget, catalogEntries = []) {
1796
2114
  const aliasEntry = catalogEntries.find((entry) => entry.key === resolvedKey);
1797
2115
  return {
1798
2116
  name: aliasEntry?.name ?? asTrimmedLowercaseString(rawTarget),
1799
- key: resolvedKey
2117
+ key: resolvedKey,
2118
+ ...aliasEntry?.model ? { model: aliasEntry.model } : {},
2119
+ ...aliasEntry ? {
2120
+ ...aliasEntry.apiSource ? { apiSource: aliasEntry.apiSource } : aliasEntry.kind === "platform" ? { apiSource: "platform" } : {}
2121
+ } : {}
1800
2122
  };
1801
2123
  }
1802
2124
  return findAgentCatalogEntry(catalogEntries, rawTarget);
@@ -1809,10 +2131,10 @@ function formatDialogTimestamp(value, now = Date.now()) {
1809
2131
  const time = typeof value === "number" ? value : Date.parse(value);
1810
2132
  if (!Number.isFinite(time) || time <= 0) return "";
1811
2133
  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`;
2134
+ if (deltaSeconds < 60) return t("timeJustNow");
2135
+ if (deltaSeconds < 3600) return t("timeMinutesAgo", String(Math.floor(deltaSeconds / 60)));
2136
+ if (deltaSeconds < 86400) return t("timeHoursAgo", String(Math.floor(deltaSeconds / 3600)));
2137
+ if (deltaSeconds < 30 * 86400) return t("timeDaysAgo", String(Math.floor(deltaSeconds / 86400)));
1816
2138
  const date = new Date(time);
1817
2139
  const pad = (part) => String(part).padStart(2, "0");
1818
2140
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
@@ -1829,7 +2151,7 @@ function toDialogPickerItems(dialogs) {
1829
2151
  }
1830
2152
  function renderDialogList(dialogs) {
1831
2153
  if (dialogs.length === 0) return t("noDialogsYet");
1832
- const lines = ["Recent dialogs:"];
2154
+ const lines = [t("recentDialogs")];
1833
2155
  for (const [index, dialog] of dialogs.entries()) {
1834
2156
  const stamp = formatDialogTimestamp(dialog.updatedAt ?? dialog.createdAt);
1835
2157
  lines.push(
@@ -1837,7 +2159,7 @@ function renderDialogList(dialogs) {
1837
2159
  ` id=${dialog.id}`
1838
2160
  );
1839
2161
  }
1840
- lines.push("", "Tip: run /history to pick one interactively, or paste an id after /resume.");
2162
+ lines.push("", t("dialogListTip"));
1841
2163
  return lines.join("\n");
1842
2164
  }
1843
2165
  async function fetchRecentDialogs(args) {
@@ -1918,7 +2240,7 @@ async function runDialogPicker(args) {
1918
2240
  }
1919
2241
 
1920
2242
  // packages/cli/tui/pasteImage.ts
1921
- import { existsSync } from "node:fs";
2243
+ import { existsSync as existsSync2 } from "node:fs";
1922
2244
  import { readFile, stat } from "node:fs/promises";
1923
2245
  import { homedir } from "node:os";
1924
2246
  import { isAbsolute, resolve } from "node:path";
@@ -1973,7 +2295,7 @@ function detectImagePaths(line, cwd) {
1973
2295
  if (!token.raw) continue;
1974
2296
  const resolved = resolveImageSource(token.decoded, cwd);
1975
2297
  if (!isImagePath(resolved)) continue;
1976
- if (!existsSync(resolved)) continue;
2298
+ if (!existsSync2(resolved)) continue;
1977
2299
  out.push({ raw: token.raw, resolvedPath: resolved });
1978
2300
  }
1979
2301
  return out;
@@ -2156,302 +2478,48 @@ async function readImagePaths(paths, options = {}) {
2156
2478
  }
2157
2479
 
2158
2480
  // 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;
2481
+ import { execFile, execSync } from "node:child_process";
2482
+ function parseStatusOutput(branch, status) {
2483
+ if (!status) {
2484
+ return { branch, modified: 0, untracked: 0 };
2485
+ }
2486
+ let modified = 0;
2487
+ let untracked = 0;
2488
+ for (const line of status.split("\n")) {
2489
+ if (!line) continue;
2490
+ const flag = line[0];
2491
+ if (flag === "?") untracked += 1;
2492
+ else modified += 1;
2493
+ }
2494
+ return { branch, modified, untracked };
2495
+ }
2496
+ function execFileUtf8(file, args, cwd) {
2497
+ return new Promise((resolve2, reject) => {
2498
+ execFile(
2499
+ file,
2500
+ args,
2501
+ { cwd, encoding: "utf8", timeout: 2e3 },
2502
+ (err, stdout) => {
2503
+ if (err) {
2504
+ reject(err);
2505
+ return;
2506
+ }
2507
+ resolve2(typeof stdout === "string" ? stdout : String(stdout ?? ""));
2434
2508
  }
2435
- break;
2436
- }
2437
- }
2438
- return result.length > 0 ? result : [""];
2509
+ );
2510
+ });
2439
2511
  }
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;
2512
+ function detectGitStatusAsync(cwd) {
2513
+ return (async () => {
2514
+ try {
2515
+ const branch = (await execFileUtf8("git", ["rev-parse", "--abbrev-ref", "HEAD"], cwd)).trim();
2516
+ if (!branch) return void 0;
2517
+ const status = (await execFileUtf8("git", ["status", "--porcelain"], cwd)).trim();
2518
+ return parseStatusOutput(branch, status);
2519
+ } catch {
2520
+ return void 0;
2452
2521
  }
2453
- }
2454
- return result;
2522
+ })();
2455
2523
  }
2456
2524
 
2457
2525
  // packages/cli/tui/sessionRender.ts
@@ -2468,12 +2536,12 @@ function formatElapsedSeconds(totalSeconds) {
2468
2536
  const mins = minutes % 60;
2469
2537
  return `${hours}h ${mins}m`;
2470
2538
  }
2471
- function renderComposerTokenChip(tokens, fallbackContextWindow) {
2539
+ function renderComposerTokenChip(tokens, fallbackContextWindow, estimatedContextTokens) {
2472
2540
  const cw = tokens?.contextWindow ?? fallbackContextWindow;
2473
2541
  if (!cw) {
2474
2542
  return "context: \u2014";
2475
2543
  }
2476
- const used = tokens ? tokens.input + tokens.output : 0;
2544
+ const used = tokens ? tokens.input + tokens.output : Math.max(0, estimatedContextTokens ?? 0);
2477
2545
  const pct = Math.min(100, used / cw * 100);
2478
2546
  const pctText = pct > 0 && pct < 10 ? pct.toFixed(1) : Math.round(pct).toString();
2479
2547
  return `context: ${pctText}% (${formatTokenCount(used)}/${formatTokenCount(cw)})`;
@@ -2495,7 +2563,17 @@ function renderStatusLine(state) {
2495
2563
  const untrackedText = untracked > 0 ? ` ${themeText(`?${untracked}`, "muted", colorEnabled)}` : "";
2496
2564
  parts.push(`${branchText}${modifiedText}${untrackedText}`);
2497
2565
  }
2498
- const tokenSegment = themeText(renderComposerTokenChip(state.turnTokens, state.contextWindow), "muted", colorEnabled);
2566
+ const credits = state.apiSource === "platform" ? state.turnTokens?.credits : void 0;
2567
+ const creditsSuffix = credits !== void 0 && credits > 0 ? ` \xB7 \u26A1 ${credits.toFixed(2)} \u79EF\u5206` : "";
2568
+ const tokenSegment = themeText(
2569
+ renderComposerTokenChip(
2570
+ state.turnTokens,
2571
+ state.contextWindow,
2572
+ state.estimatedContextTokens
2573
+ ) + creditsSuffix,
2574
+ "muted",
2575
+ colorEnabled
2576
+ );
2499
2577
  parts.push(tokenSegment);
2500
2578
  const runningCount = getProcessRegistry().list().filter((p) => p.status === "running").length;
2501
2579
  if (runningCount > 0) {
@@ -2577,7 +2655,7 @@ function renderWelcome(state, frame = 0, maxFrames = 0, columns) {
2577
2655
  const isDark = brightness === "dark";
2578
2656
  let sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
2579
2657
  if (typeof columns === "number" && columns > 0) {
2580
- const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth2(stripAnsi(line))), 0);
2658
+ const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth(stripAnsi(line))), 0);
2581
2659
  if (columns < widestSceneCol) sceneArt = "";
2582
2660
  }
2583
2661
  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 +2705,19 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
2627
2705
  ];
2628
2706
  let maxW = 0;
2629
2707
  for (const l of labels) {
2630
- const w = displayWidth2(l);
2708
+ const w = displayWidth(l);
2631
2709
  if (w > maxW) maxW = w;
2632
2710
  }
2633
2711
  const targetWidth = maxW >= 9 ? maxW + 1 : 9;
2634
2712
  const padLabel = (raw) => {
2635
- const w = displayWidth2(raw);
2713
+ const w = displayWidth(raw);
2636
2714
  const padding = " ".repeat(Math.max(0, targetWidth - w));
2637
2715
  return raw + padding;
2638
2716
  };
2639
2717
  const field = (rawLabel, value) => `${themeText(padLabel(rawLabel), "muted", colorEnabled)}${value}`;
2640
2718
  const next = (command, description) => ` ${themeText(command, "accent", colorEnabled)}${themeText(description, "muted", colorEnabled)}`;
2641
2719
  const titleText = t("contextTitle");
2642
- const heading = colorEnabled ? [renderDialogTitle(titleText, true)] : [titleText, "\u2500".repeat(displayWidth2(titleText))];
2720
+ const heading = colorEnabled ? [renderDialogTitle(titleText, true)] : [titleText, "\u2500".repeat(displayWidth(titleText))];
2643
2721
  return [
2644
2722
  ...heading,
2645
2723
  field(labels[0], `${state.agentName} (${state.agentKey})`),
@@ -2684,14 +2762,21 @@ function renderKnownAgents(colorEnabled = resolveCliColorEnabled()) {
2684
2762
 
2685
2763
  // packages/cli/tui/sessionInput.ts
2686
2764
  var PASTE_TOKEN_PREFIX = "\0PASTE\0";
2687
- function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2765
+ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
2688
2766
  const seq = sequence ?? "";
2689
2767
  const curPos = Math.max(0, Math.min(buffer.length, cursorPos ?? buffer.length));
2768
+ const pasteStore = options?.pasteStore;
2690
2769
  if (seq.startsWith(PASTE_TOKEN_PREFIX)) {
2691
2770
  const rawPayload = seq.slice(PASTE_TOKEN_PREFIX.length);
2692
2771
  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 };
2772
+ const insertAt2 = snapCursorOutsidePasteChip(buffer, curPos);
2773
+ if (pasteStore && shouldCollapsePaste(normalized)) {
2774
+ const { placeholder } = allocateCollapsedPaste(pasteStore, normalized);
2775
+ const nextBuf3 = buffer.slice(0, insertAt2) + placeholder + buffer.slice(insertAt2);
2776
+ return { buffer: nextBuf3, cursorPos: insertAt2 + placeholder.length };
2777
+ }
2778
+ const nextBuf2 = buffer.slice(0, insertAt2) + normalized + buffer.slice(insertAt2);
2779
+ return { buffer: nextBuf2, cursorPos: insertAt2 + normalized.length };
2695
2780
  }
2696
2781
  if (seq === "" || key.ctrl && key.name === "c") {
2697
2782
  return { buffer, cursorPos: curPos, abort: true };
@@ -2700,16 +2785,25 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2700
2785
  return { buffer, cursorPos: curPos, copyView: true };
2701
2786
  }
2702
2787
  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 };
2788
+ const insertAt2 = snapCursorOutsidePasteChip(buffer, curPos);
2789
+ const nextBuf2 = buffer.slice(0, insertAt2) + "\n" + buffer.slice(insertAt2);
2790
+ return { buffer: nextBuf2, cursorPos: insertAt2 + 1 };
2705
2791
  }
2706
2792
  if (key.name === "enter" || key.name === "return" || seq === "\r") {
2707
2793
  return { buffer: "", cursorPos: 0, submit: buffer };
2708
2794
  }
2709
2795
  if (isLeftArrowSequence(seq, key)) {
2796
+ const span = findCollapsedPasteSpanAt(buffer, curPos, { preferLeft: true });
2797
+ if (span && curPos > span.start && curPos <= span.end) {
2798
+ return { buffer, cursorPos: span.start };
2799
+ }
2710
2800
  return { buffer, cursorPos: Math.max(0, curPos - 1) };
2711
2801
  }
2712
2802
  if (isRightArrowSequence(seq, key)) {
2803
+ const span = findCollapsedPasteSpanAt(buffer, curPos);
2804
+ if (span && curPos >= span.start && curPos < span.end) {
2805
+ return { buffer, cursorPos: span.end };
2806
+ }
2713
2807
  return { buffer, cursorPos: Math.min(buffer.length, curPos + 1) };
2714
2808
  }
2715
2809
  if (isHomeSequence(seq, key)) {
@@ -2720,24 +2814,33 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2720
2814
  }
2721
2815
  if (isDeleteWordSequence(seq, key)) {
2722
2816
  if (curPos === 0) return { buffer, cursorPos: curPos };
2817
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos, {
2818
+ preferLeft: true
2819
+ });
2820
+ if (pasteSpan && (curPos === pasteSpan.end || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2821
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2822
+ }
2723
2823
  const cutIdx = findWordStartLeft(buffer, curPos);
2724
- const nextBuf2 = buffer.slice(0, cutIdx) + buffer.slice(curPos);
2725
- return { buffer: nextBuf2, cursorPos: cutIdx };
2824
+ return deleteBufferRange(buffer, cutIdx, curPos, pasteStore);
2726
2825
  }
2727
2826
  if (isKillToLineStartSequence(seq, key)) {
2728
2827
  if (curPos === 0) return { buffer, cursorPos: curPos };
2729
2828
  const lineStart = buffer.lastIndexOf("\n", curPos - 1) + 1;
2730
- const nextBuf2 = buffer.slice(0, lineStart) + buffer.slice(curPos);
2731
- return { buffer: nextBuf2, cursorPos: lineStart };
2829
+ return deleteBufferRange(buffer, lineStart, curPos, pasteStore);
2732
2830
  }
2733
2831
  if (isKillToLineEndSequence(seq, key)) {
2734
2832
  const nlIdx = buffer.indexOf("\n", curPos);
2735
2833
  const lineEnd = nlIdx === -1 ? buffer.length : nlIdx;
2736
- const nextBuf2 = buffer.slice(0, curPos) + buffer.slice(lineEnd);
2737
- return { buffer: nextBuf2, cursorPos: curPos };
2834
+ return deleteBufferRange(buffer, curPos, lineEnd, pasteStore);
2738
2835
  }
2739
2836
  if (isBackspaceSequence(seq, key)) {
2740
2837
  if (curPos > 0) {
2838
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos, {
2839
+ preferLeft: true
2840
+ });
2841
+ if (pasteSpan && (curPos === pasteSpan.end || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2842
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2843
+ }
2741
2844
  const nextBuf2 = buffer.slice(0, curPos - 1) + buffer.slice(curPos);
2742
2845
  return { buffer: nextBuf2, cursorPos: curPos - 1 };
2743
2846
  }
@@ -2745,6 +2848,10 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2745
2848
  }
2746
2849
  if (isForwardDeleteSequence(seq, key)) {
2747
2850
  if (curPos < buffer.length) {
2851
+ const pasteSpan = findCollapsedPasteSpanAt(buffer, curPos);
2852
+ if (pasteSpan && (curPos === pasteSpan.start || curPos > pasteSpan.start && curPos < pasteSpan.end)) {
2853
+ return removePasteSpan(buffer, pasteSpan, pasteStore);
2854
+ }
2748
2855
  const nextBuf2 = buffer.slice(0, curPos) + buffer.slice(curPos + 1);
2749
2856
  return { buffer: nextBuf2, cursorPos: curPos };
2750
2857
  }
@@ -2761,8 +2868,14 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos) {
2761
2868
  if (!seq || key.ctrl || key.meta || seq.startsWith("\x1B")) {
2762
2869
  return { buffer, cursorPos: curPos };
2763
2870
  }
2764
- const nextBuf = buffer.slice(0, curPos) + seq + buffer.slice(curPos);
2765
- return { buffer: nextBuf, cursorPos: curPos + seq.length };
2871
+ const insertAt = snapCursorOutsidePasteChip(buffer, curPos);
2872
+ const nextBuf = buffer.slice(0, insertAt) + seq + buffer.slice(insertAt);
2873
+ return { buffer: nextBuf, cursorPos: insertAt + seq.length };
2874
+ }
2875
+ function snapCursorOutsidePasteChip(buffer, pos) {
2876
+ const span = findCollapsedPasteSpanAt(buffer, pos);
2877
+ if (span && pos > span.start && pos < span.end) return span.end;
2878
+ return pos;
2766
2879
  }
2767
2880
  function isLeftArrowSequence(seq, key) {
2768
2881
  if (key.name === "left") return true;
@@ -2786,13 +2899,44 @@ function isEndSequence(seq, key) {
2786
2899
  if (seq === "" || seq === "\x1B[F" || seq === "\x1B[4~" || seq === "\x1B[8~" || seq === "\x1BOF") return true;
2787
2900
  return false;
2788
2901
  }
2902
+ function removePasteSpan(buffer, span, pasteStore) {
2903
+ if (pasteStore) releaseCollapsedPaste(pasteStore, span.id);
2904
+ const nextBuf = buffer.slice(0, span.start) + buffer.slice(span.end);
2905
+ return { buffer: nextBuf, cursorPos: span.start };
2906
+ }
2907
+ function deleteBufferRange(buffer, start, end, pasteStore) {
2908
+ if (start >= end) return { buffer, cursorPos: start };
2909
+ const range = expandRangeToCollapsedPasteChips(buffer, start, end);
2910
+ releasePastesInRange(buffer, range.start, range.end, pasteStore);
2911
+ const nextBuf = buffer.slice(0, range.start) + buffer.slice(range.end);
2912
+ return { buffer: nextBuf, cursorPos: range.start };
2913
+ }
2914
+ function releasePastesInRange(buffer, start, end, pasteStore) {
2915
+ if (!pasteStore || start >= end) return;
2916
+ const slice = buffer.slice(start, end);
2917
+ const re = /\[paste #(\d+) · (\d+) lines\]/g;
2918
+ let match;
2919
+ while ((match = re.exec(slice)) !== null) {
2920
+ releaseCollapsedPaste(pasteStore, Number(match[1]));
2921
+ }
2922
+ }
2789
2923
  var CJK_CHAR = /[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
2790
2924
  function findWordStartLeft(buffer, curPos) {
2791
2925
  let i = curPos;
2792
2926
  while (i > 0 && /\s/.test(buffer[i - 1])) i--;
2793
2927
  if (i === 0) return 0;
2928
+ const spanAtCursor = findCollapsedPasteSpanAt(buffer, i, {
2929
+ preferLeft: true
2930
+ });
2931
+ if (spanAtCursor && (i === spanAtCursor.end || i > spanAtCursor.start && i < spanAtCursor.end)) {
2932
+ return spanAtCursor.start;
2933
+ }
2794
2934
  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--;
2935
+ while (i > 0 && !/\s/.test(buffer[i - 1]) && !CJK_CHAR.test(buffer[i - 1])) {
2936
+ const span = findCollapsedPasteSpanAt(buffer, i, { preferLeft: true });
2937
+ if (span && i === span.end) break;
2938
+ i--;
2939
+ }
2796
2940
  return i;
2797
2941
  }
2798
2942
  function isDeleteWordSequence(seq, key) {
@@ -2903,6 +3047,7 @@ function createInitialTuiState(env = process.env) {
2903
3047
  ) || void 0;
2904
3048
  const dialogEnvValue = asOptionalTrimmedString(env.NOLO_DIALOG);
2905
3049
  const explicitDialogKey = asOptionalTrimmedString(env.NOLO_DIALOG_KEY) ?? (dialogEnvValue?.startsWith("dialog-") ? dialogEnvValue : void 0);
3050
+ const autoRouteDefault = env.NOLO_AUTO_ROUTE !== "0";
2906
3051
  return {
2907
3052
  agentKey,
2908
3053
  agentName,
@@ -2922,22 +3067,47 @@ function createInitialTuiState(env = process.env) {
2922
3067
  attachedImages: [],
2923
3068
  runtimeMode,
2924
3069
  modeLabel: asOptionalTrimmedString(env.NOLO_CLI_STATUS_MODE) ?? runtimeMode,
2925
- gitStatus: env.NOLO_CLI_GIT_STATUS === "0" ? void 0 : detectGitStatus(cwd),
3070
+ gitStatus: void 0,
2926
3071
  thinkingDisplay: normalizeThinkingDisplayMode(
2927
3072
  env.NOLO_CLI_THINKING ?? env.NOLO_THINKING,
2928
3073
  "hide"
2929
3074
  ),
2930
3075
  toolDisplay: normalizeToolDisplayMode(env.NOLO_CLI_TOOLS ?? env.NOLO_TOOLS, "compact"),
2931
- contextWindow: getModelContextWindow(agentName)
3076
+ contextWindow: resolveAgentContextWindow({
3077
+ agentKey,
3078
+ agentName,
3079
+ autoRouteDefault
3080
+ }),
3081
+ estimatedContextTokens: estimateDefaultCliContextTokens({
3082
+ cwd,
3083
+ agentKey,
3084
+ agentName: autoRouteDefault ? "DeepSeek V4 Flash" : agentName,
3085
+ model: autoRouteDefault ? "deepseek-v4-flash" : void 0
3086
+ }),
3087
+ apiSource: PLATFORM_AGENTS.some((entry) => entry.key === agentKey) ? "platform" : void 0
2932
3088
  };
2933
3089
  }
2934
- function applyAgentSwitch(state, target) {
3090
+ function applyAgentSwitch(state, target, opts) {
3091
+ const autoRouteDefault = opts?.autoRouteDefault ?? process.env.NOLO_AUTO_ROUTE !== "0";
3092
+ const contextWindow = resolveAgentContextWindow({
3093
+ agentKey: target.key,
3094
+ agentName: target.name,
3095
+ model: target.model,
3096
+ autoRouteDefault
3097
+ });
2935
3098
  return {
2936
3099
  nextState: {
2937
3100
  ...state,
2938
3101
  agentName: target.name,
2939
3102
  agentKey: target.key,
2940
- contextWindow: getModelContextWindow(target.name)
3103
+ contextWindow,
3104
+ estimatedContextTokens: estimateDefaultCliContextTokens({
3105
+ cwd: state.cwd,
3106
+ agentKey: target.key,
3107
+ agentName: target.name,
3108
+ model: target.model
3109
+ }),
3110
+ apiSource: target.apiSource
2941
3111
  },
2942
3112
  output: `Switched to ${target.name}. ${state.dialogId ? `Dialog kept: ${state.dialogId}` : "Dialog kept: new"}`
2943
3113
  };
@@ -2999,32 +3169,32 @@ function handleTuiInput(input, state) {
2999
3169
  return {
3000
3170
  nextState: state,
3001
3171
  output: [
3002
- `Current theme: ${getActiveThemeName()} \xB7 ${brightness}`,
3003
- `Usage: /theme <name> | /theme light | /theme dark`,
3004
- `Available themes: ${available}`
3172
+ t("themeCurrent", getActiveThemeName(), brightness),
3173
+ t("themeUsage"),
3174
+ t("themeAvailable", available)
3005
3175
  ].join("\n")
3006
3176
  };
3007
3177
  }
3008
3178
  if (sub === "light" || sub === "dark") {
3009
3179
  setActiveBrightness(sub);
3010
- return { nextState: state, output: `Switched to ${sub} background colors.` };
3180
+ return { nextState: state, output: t("themeBrightnessSwitched", sub) };
3011
3181
  }
3012
3182
  if (sub === "auto") {
3013
3183
  setActiveBrightness(null);
3014
3184
  return {
3015
3185
  nextState: state,
3016
- output: `Background colors follow terminal detection (now: ${resolveTuiBrightness()}).`
3186
+ output: t("themeBrightnessAuto", resolveTuiBrightness())
3017
3187
  };
3018
3188
  }
3019
3189
  if (setActiveThemeName(sub)) {
3020
3190
  return {
3021
3191
  nextState: state,
3022
- output: `Switched to theme: ${sub}`
3192
+ output: t("themeSwitched", sub)
3023
3193
  };
3024
3194
  } else {
3025
3195
  return {
3026
3196
  nextState: state,
3027
- output: `Unknown theme: ${sub}. Available themes: ${available}`
3197
+ output: t("themeUnknown", sub, available)
3028
3198
  };
3029
3199
  }
3030
3200
  }
@@ -3034,20 +3204,19 @@ function handleTuiInput(input, state) {
3034
3204
  if (!sub) {
3035
3205
  return {
3036
3206
  nextState: state,
3037
- output: `Current density: ${getActiveDensity()}
3038
- Usage: /density <cozy|spacious>`
3207
+ output: t("densityCurrent", getActiveDensity())
3039
3208
  };
3040
3209
  }
3041
3210
  if (sub === "cozy" || sub === "spacious") {
3042
3211
  setActiveDensity(sub);
3043
3212
  return {
3044
3213
  nextState: state,
3045
- output: `Switched to layout density: ${sub}`
3214
+ output: t("densitySwitched", sub)
3046
3215
  };
3047
3216
  } else {
3048
3217
  return {
3049
3218
  nextState: state,
3050
- output: `Unknown density: ${sub}. Use 'cozy' or 'spacious'.`
3219
+ output: t("densityUnknown", sub)
3051
3220
  };
3052
3221
  }
3053
3222
  }
@@ -3058,52 +3227,52 @@ Usage: /density <cozy|spacious>`
3058
3227
  if (argText !== "auto" && argText !== "local" && argText !== "server") {
3059
3228
  return {
3060
3229
  nextState: state,
3061
- output: "Usage: /runtime <auto|local|server>"
3230
+ output: t("runtimeUsage")
3062
3231
  };
3063
3232
  }
3064
3233
  return {
3065
3234
  nextState: { ...state, runtimeMode: argText },
3066
- output: `Runtime: ${argText}`
3235
+ output: t("runtimeSet", argText)
3067
3236
  };
3068
3237
  }
3069
3238
  case "/tools": {
3070
3239
  if (!argText) {
3071
3240
  return {
3072
3241
  nextState: state,
3073
- output: `Tool display: ${state.toolDisplay} (hide | compact | verbose)`
3242
+ output: t("toolsCurrent", state.toolDisplay)
3074
3243
  };
3075
3244
  }
3076
3245
  const normalizedArg = asTrimmedLowercaseString(argText);
3077
3246
  if (!["hide", "compact", "verbose", "on", "off"].includes(normalizedArg)) {
3078
3247
  return {
3079
3248
  nextState: state,
3080
- output: "Usage: /tools <hide|compact|verbose>"
3249
+ output: t("toolsUsage")
3081
3250
  };
3082
3251
  }
3083
3252
  const nextMode = normalizeToolDisplayMode(normalizedArg, state.toolDisplay);
3084
3253
  return {
3085
3254
  nextState: { ...state, toolDisplay: nextMode },
3086
- output: `Tool display: ${nextMode}`
3255
+ output: t("toolsSet", nextMode)
3087
3256
  };
3088
3257
  }
3089
3258
  case "/thinking": {
3090
3259
  if (!argText) {
3091
3260
  return {
3092
3261
  nextState: state,
3093
- output: `Thinking display: ${state.thinkingDisplay} (hide | marker | show)`
3262
+ output: t("thinkingCurrent", state.thinkingDisplay)
3094
3263
  };
3095
3264
  }
3096
3265
  const normalizedArg = asTrimmedLowercaseString(argText);
3097
3266
  if (!["hide", "marker", "show", "on", "off"].includes(normalizedArg)) {
3098
3267
  return {
3099
3268
  nextState: state,
3100
- output: "Usage: /thinking <hide|marker|show>"
3269
+ output: t("thinkingUsage")
3101
3270
  };
3102
3271
  }
3103
3272
  const nextMode = normalizeThinkingDisplayMode(normalizedArg, state.thinkingDisplay);
3104
3273
  return {
3105
3274
  nextState: { ...state, thinkingDisplay: nextMode },
3106
- output: `Thinking display: ${nextMode}`
3275
+ output: t("thinkingSet", nextMode)
3107
3276
  };
3108
3277
  }
3109
3278
  case "/tasks":
@@ -3115,14 +3284,14 @@ Usage: /density <cozy|spacious>`
3115
3284
  const stopped = all.filter((p) => p.status !== "running");
3116
3285
  const lines = [];
3117
3286
  if (running.length > 0) {
3118
- lines.push(`Running processes (${running.length}):`);
3287
+ lines.push(t("tasksRunning", String(running.length)));
3119
3288
  for (const p of running) {
3120
3289
  const elapsed = formatElapsedSeconds(Math.floor((Date.now() - p.startedAt) / 1e3));
3121
3290
  lines.push(` pid ${p.pid} ${p.label} running ${elapsed}`);
3122
3291
  }
3123
3292
  }
3124
3293
  if (stopped.length > 0) {
3125
- lines.push(`Stopped/exited (${stopped.length}):`);
3294
+ lines.push(t("tasksStopped", String(stopped.length)));
3126
3295
  for (const p of stopped) {
3127
3296
  const elapsed = formatElapsedSeconds(Math.floor((Date.now() - p.startedAt) / 1e3));
3128
3297
  const exitInfo = p.exitCode !== void 0 ? ` exit ${p.exitCode}` : "";
@@ -3130,38 +3299,38 @@ Usage: /density <cozy|spacious>`
3130
3299
  }
3131
3300
  }
3132
3301
  if (all.length === 0) {
3133
- lines.push("No processes.");
3302
+ lines.push(t("tasksNone"));
3134
3303
  }
3135
3304
  return { nextState: state, output: lines.join("\n") };
3136
3305
  }
3137
3306
  case "/stop": {
3138
3307
  const registry = getProcessRegistry();
3139
3308
  if (!argText) {
3140
- return { nextState: state, output: "Usage: /stop <pid|label|all>" };
3309
+ return { nextState: state, output: t("stopUsage") };
3141
3310
  }
3142
3311
  if (argText === "all") {
3143
3312
  const before = registry.list().filter((p) => p.status === "running").length;
3144
3313
  registry.stopAll();
3145
- return { nextState: state, output: `Stopped ${before} processes` };
3314
+ return { nextState: state, output: t("stopAllDone", String(before)) };
3146
3315
  }
3147
3316
  if (/^\d+$/.test(argText)) {
3148
3317
  const pid = parseInt(argText, 10);
3149
3318
  const proc = registry.get(pid);
3150
3319
  if (!proc || proc.status !== "running") {
3151
- return { nextState: state, output: `No running process with pid ${pid}` };
3320
+ return { nextState: state, output: t("stopNoPid", String(pid)) };
3152
3321
  }
3153
3322
  registry.kill(pid);
3154
- return { nextState: state, output: `Stopped pid ${pid} (${proc.label})` };
3323
+ return { nextState: state, output: t("stopPidDone", String(pid), proc.label) };
3155
3324
  }
3156
3325
  const matches = registry.list().filter((p) => p.status === "running" && p.label === argText);
3157
3326
  if (matches.length === 0) {
3158
- return { nextState: state, output: `No running process labeled '${argText}'` };
3327
+ return { nextState: state, output: t("stopNoLabel", argText) };
3159
3328
  }
3160
3329
  for (const p of matches) {
3161
3330
  registry.kill(p.pid);
3162
3331
  }
3163
3332
  const stoppedNames = matches.map((p) => `pid ${p.pid} (${p.label})`).join(", ");
3164
- return { nextState: state, output: `Stopped ${stoppedNames}` };
3333
+ return { nextState: state, output: t("stopLabelsDone", stoppedNames) };
3165
3334
  }
3166
3335
  case "/exit":
3167
3336
  case "/quit":
@@ -3185,15 +3354,13 @@ Usage: /density <cozy|spacious>`
3185
3354
  if (argText) {
3186
3355
  return {
3187
3356
  nextState: state,
3188
- output: `Unknown command: ${trimmed}
3189
-
3190
- ${renderTuiHelp()}`
3357
+ output: t("unknownCommand", trimmed) + renderTuiHelp()
3191
3358
  };
3192
3359
  }
3193
3360
  if (!state.dialogId) {
3194
3361
  return {
3195
3362
  nextState: state,
3196
- output: "Current dialog: new (nothing to compact yet)"
3363
+ output: t("compactNothing")
3197
3364
  };
3198
3365
  }
3199
3366
  return {
@@ -3220,15 +3387,14 @@ ${renderTuiHelp()}`
3220
3387
  if (argText === "current" || argText === "show") {
3221
3388
  return {
3222
3389
  nextState: state,
3223
- output: `Current agent: ${state.agentName} (${state.agentKey})`
3390
+ output: t("agentCurrent", state.agentName, state.agentKey)
3224
3391
  };
3225
3392
  }
3226
3393
  const resolvedTarget = resolveAgentSwitchTarget(argText, resolveCatalogPlatformAgents());
3227
3394
  if (!resolvedTarget) {
3228
3395
  return {
3229
3396
  nextState: state,
3230
- output: `I don't know agent "${argText}" yet.
3231
- Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3397
+ output: t("agentUnknown", argText)
3232
3398
  };
3233
3399
  }
3234
3400
  return applyAgentSwitch(state, resolvedTarget);
@@ -3282,6 +3448,16 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3282
3448
  action: { type: "set-mouse", enabled: argText === "on" }
3283
3449
  };
3284
3450
  }
3451
+ case "/altscreen": {
3452
+ if (argText !== "on" && argText !== "off") {
3453
+ return { nextState: state, output: t("altscreenUsage") };
3454
+ }
3455
+ return {
3456
+ nextState: state,
3457
+ output: argText === "on" ? t("altscreenOn") : t("altscreenOff"),
3458
+ action: { type: "set-altscreen", enabled: argText === "on" }
3459
+ };
3460
+ }
3285
3461
  case "/resume": {
3286
3462
  if (!argText) {
3287
3463
  return {
@@ -3311,7 +3487,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3311
3487
  if (rest[0] === "attach") {
3312
3488
  const docName = rest.slice(1).join(" ").trim();
3313
3489
  if (!docName) {
3314
- return { nextState: state, output: "Usage: /doc attach <doc>" };
3490
+ return { nextState: state, output: t("docAttachUsage") };
3315
3491
  }
3316
3492
  const attachedDocs = state.attachedDocs.includes(docName) ? state.attachedDocs : [...state.attachedDocs, docName];
3317
3493
  return {
@@ -3321,7 +3497,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3321
3497
  }
3322
3498
  return {
3323
3499
  nextState: state,
3324
- output: state.attachedDocs.length > 0 ? `Attached docs: ${state.attachedDocs.join(", ")}` : "No docs attached. Use /doc attach <doc>."
3500
+ output: state.attachedDocs.length > 0 ? t("docList", state.attachedDocs.join(", ")) : t("docNone")
3325
3501
  };
3326
3502
  }
3327
3503
  case "/skill": {
@@ -3329,7 +3505,7 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3329
3505
  if (sub === "attach") {
3330
3506
  const skillRef = rest.slice(1).join(" ").trim();
3331
3507
  if (!skillRef) {
3332
- return { nextState: state, output: "Usage: /skill attach <skill-ref>" };
3508
+ return { nextState: state, output: t("skillAttachUsage") };
3333
3509
  }
3334
3510
  const attachedSkills = state.attachedSkills.includes(skillRef) ? state.attachedSkills : [...state.attachedSkills, skillRef];
3335
3511
  return {
@@ -3340,38 +3516,37 @@ Use /switch, /switch list, /switch minimax-m3, or a full agent key.`
3340
3516
  if (sub === "detach") {
3341
3517
  const skillRef = rest.slice(1).join(" ").trim();
3342
3518
  if (!skillRef) {
3343
- return { nextState: state, output: "Usage: /skill detach <skill-ref>" };
3519
+ return { nextState: state, output: t("skillDetachUsage") };
3344
3520
  }
3345
3521
  const attachedSkills = state.attachedSkills.filter((s) => s !== skillRef);
3346
3522
  return {
3347
3523
  nextState: { ...state, attachedSkills },
3348
- output: state.attachedSkills.includes(skillRef) ? `Detached skill: ${skillRef}` : `Skill not attached: ${skillRef}`
3524
+ output: state.attachedSkills.includes(skillRef) ? t("skillDetached", skillRef) : t("skillNotAttached", skillRef)
3349
3525
  };
3350
3526
  }
3351
3527
  if (sub === "clear") {
3352
3528
  if (state.attachedSkills.length === 0) {
3353
- return { nextState: state, output: "No skills attached." };
3529
+ return { nextState: state, output: t("skillNone") };
3354
3530
  }
3355
3531
  return {
3356
3532
  nextState: { ...state, attachedSkills: [] },
3357
- output: `Cleared ${state.attachedSkills.length} skill(s).`
3533
+ output: t("skillCleared", String(state.attachedSkills.length))
3358
3534
  };
3359
3535
  }
3360
3536
  return {
3361
3537
  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."
3538
+ output: state.attachedSkills.length > 0 ? t("skillList", state.attachedSkills.join(", ")) : t("skillNoneHint")
3364
3539
  };
3365
3540
  }
3366
3541
  case "/customize":
3367
3542
  return {
3368
3543
  nextState: state,
3369
- output: "Tell nolo what to change, for example: /customize make my default agent more concise."
3544
+ output: t("customizeHint")
3370
3545
  };
3371
3546
  case "/login":
3372
3547
  return {
3373
3548
  nextState: state,
3374
- output: "MVP login uses profile/env auth. Set AUTH_TOKEN, NOLO_SERVER, or NOLO_PROFILE before starting nolo."
3549
+ output: t("loginHint")
3375
3550
  };
3376
3551
  case "/profile":
3377
3552
  return {
@@ -3387,16 +3562,12 @@ Usage: /skill attach <ref> | /skill detach <ref> | /skill clear` : "No skills at
3387
3562
  case "/version":
3388
3563
  return {
3389
3564
  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.`
3565
+ output: t("versionInfo", state.cliVersion || t("versionUnknown"))
3393
3566
  };
3394
3567
  default:
3395
3568
  return {
3396
3569
  nextState: state,
3397
- output: `Unknown command: ${command}
3398
-
3399
- ${renderTuiHelp()}`
3570
+ output: t("unknownCommand", command) + renderTuiHelp()
3400
3571
  };
3401
3572
  }
3402
3573
  }
@@ -3414,19 +3585,23 @@ function brightnessFromRgb(r, g, b) {
3414
3585
  const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
3415
3586
  return luma >= 0.5 ? "light" : "dark";
3416
3587
  }
3417
- function parseOsc11Reply(reply) {
3588
+ function parseOsc11Background(reply) {
3418
3589
  const match = reply.match(OSC11_REPLY_RE);
3419
3590
  if (!match) return null;
3420
- return brightnessFromRgb(
3591
+ const channels = [
3421
3592
  componentToByte(match[1]),
3422
3593
  componentToByte(match[2]),
3423
3594
  componentToByte(match[3])
3424
- );
3595
+ ];
3596
+ return {
3597
+ brightness: brightnessFromRgb(channels[0], channels[1], channels[2]),
3598
+ hex: channels.map((v) => v.toString(16).padStart(2, "0").toUpperCase()).join("")
3599
+ };
3425
3600
  }
3426
3601
  function isTerminalDevice(stream) {
3427
3602
  return stream instanceof ReadStream;
3428
3603
  }
3429
- async function detectTerminalBrightness(args = {}) {
3604
+ async function detectTerminalBackground(args = {}) {
3430
3605
  const stdin = args.stdin ?? process.stdin;
3431
3606
  const stdout = args.stdout ?? process.stdout;
3432
3607
  const timeoutMs = args.timeoutMs ?? 100;
@@ -3453,7 +3628,7 @@ async function detectTerminalBrightness(args = {}) {
3453
3628
  };
3454
3629
  const onData = (chunk) => {
3455
3630
  buffer += chunk.toString("latin1");
3456
- const parsed = parseOsc11Reply(buffer);
3631
+ const parsed = parseOsc11Background(buffer);
3457
3632
  if (parsed) finish(parsed);
3458
3633
  else if (buffer.length > 256) finish(null);
3459
3634
  };
@@ -3747,6 +3922,7 @@ function createChatQueueTuiBinding(runTurn) {
3747
3922
 
3748
3923
  // packages/cli/tui/tuiHistory.ts
3749
3924
  var MAX_TUI_HISTORY_TURNS = 500;
3925
+ var turnLineCache = /* @__PURE__ */ new WeakMap();
3750
3926
  function createTurnHistory() {
3751
3927
  return {
3752
3928
  turns: [],
@@ -3788,45 +3964,81 @@ function applyOutputChunkToCurrentTurn(history, chunk) {
3788
3964
  history.currentContent = next;
3789
3965
  return true;
3790
3966
  }
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
- };
3967
+ function styleAssistantTurn(content, colorEnabled) {
3968
+ const highlighted = colorEnabled ? formatAssistantDisplay(content) : stripAnsi(formatAssistantDisplay(content));
3969
+ const rawLines = highlighted.split("\n");
3970
+ const styledLines = rawLines.map((line, idx) => {
3971
+ if (idx === 0 && !line.startsWith("[nolo]")) {
3972
+ const anchorPrefix = colorEnabled ? `${themeColorSequence("chrome")}\u25C8\x1B[39m ` : "\u25C8 ";
3973
+ return `${anchorPrefix}${line}`;
3974
+ }
3975
+ return line.startsWith("[nolo]") && colorEnabled ? themeText(line, "chrome", true) : line;
3976
+ });
3977
+ return styledLines.join("\n");
3978
+ }
3979
+ function renderTurnBlock(role, content, contentWidth, colorEnabled) {
3811
3980
  const lines = [];
3812
- const pushTurn = (role, content) => {
3813
- if (getActiveDensity() === "spacious") {
3814
- if (lines.length > 0 || role === "user") lines.push("");
3981
+ if (role === "user") {
3982
+ const logicalLines = content.split("\n");
3983
+ const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
3984
+ const chromeSeq = colorEnabled ? themeColorSequence("chrome") : "";
3985
+ for (let i = 0; i < logicalLines.length; i++) {
3986
+ const line = logicalLines[i];
3987
+ let styledLine;
3988
+ if (colorEnabled) {
3989
+ const prefix = i === 0 ? `${accentSeq}\u276F\x1B[39m ` : `${chromeSeq}\u2502\x1B[39m `;
3990
+ styledLine = `${prefix}${line}`;
3991
+ } else {
3992
+ const prefix = i === 0 ? "\u276F " : " ";
3993
+ styledLine = `${prefix}${line}`;
3994
+ }
3995
+ lines.push(...wrapTranscriptLine(styledLine, contentWidth, " "));
3996
+ }
3997
+ } else {
3998
+ const styledEntry = styleAssistantTurn(content, colorEnabled);
3999
+ for (const logicalLine of styledEntry.split("\n")) {
4000
+ lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
3815
4001
  }
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
4002
  }
4003
+ return lines;
4004
+ }
4005
+ function buildHistoryLines(history, contentWidth) {
4006
+ const colorEnabled = resolveCliColorEnabled();
4007
+ const density = getActiveDensity();
3824
4008
  const wrapped = [];
3825
- for (const entry of lines) {
3826
- for (const logicalLine of entry.split("\n")) {
3827
- wrapped.push(...wrapTranscriptLine(logicalLine, contentWidth));
4009
+ for (let i = 0; i < history.turns.length; i++) {
4010
+ const turn = history.turns[i];
4011
+ if (density === "spacious" && (i > 0 || turn.role === "user")) {
4012
+ wrapped.push("");
4013
+ }
4014
+ const cached = turnLineCache.get(turn);
4015
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density) {
4016
+ wrapped.push(...cached.lines);
4017
+ } else {
4018
+ const lines = renderTurnBlock(turn.role, turn.content, contentWidth, colorEnabled);
4019
+ turnLineCache.set(turn, {
4020
+ width: contentWidth,
4021
+ color: colorEnabled,
4022
+ density,
4023
+ lines
4024
+ });
4025
+ wrapped.push(...lines);
3828
4026
  }
3829
4027
  }
4028
+ if (history.currentRole !== null && history.currentContent) {
4029
+ const i = history.turns.length;
4030
+ if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
4031
+ wrapped.push("");
4032
+ }
4033
+ wrapped.push(
4034
+ ...renderTurnBlock(
4035
+ history.currentRole,
4036
+ history.currentContent,
4037
+ contentWidth,
4038
+ colorEnabled
4039
+ )
4040
+ );
4041
+ }
3830
4042
  return wrapped;
3831
4043
  }
3832
4044
  function renderHistory(output, history, inputLines) {
@@ -3924,6 +4136,38 @@ function applyScrollAction(history, action, output, inputLines) {
3924
4136
  }
3925
4137
 
3926
4138
  // packages/cli/tui/tuiRawInput.ts
4139
+ function normalizePasteNewlines(text) {
4140
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4141
+ }
4142
+ var altScreenOn = /* @__PURE__ */ new WeakMap();
4143
+ function outputIsTty2(output) {
4144
+ return Boolean(output.isTTY);
4145
+ }
4146
+ function enterAltScreen(output) {
4147
+ if (!outputIsTty2(output)) return false;
4148
+ if (altScreenOn.get(output)) return false;
4149
+ altScreenOn.set(output, true);
4150
+ output.write("\x1B[?1049h");
4151
+ return true;
4152
+ }
4153
+ function leaveAltScreen(output) {
4154
+ if (!outputIsTty2(output)) return false;
4155
+ if (!altScreenOn.get(output)) return false;
4156
+ altScreenOn.set(output, false);
4157
+ output.write("\x1B[?1049l");
4158
+ return true;
4159
+ }
4160
+ function isAltScreenOn(output) {
4161
+ return Boolean(altScreenOn.get(output));
4162
+ }
4163
+ function styleCollapsedPastePlaceholders(text, colorEnabled) {
4164
+ if (!colorEnabled || !text.includes("[paste #")) return text;
4165
+ COLLAPSED_PASTE_PLACEHOLDER_RE.lastIndex = 0;
4166
+ return text.replace(
4167
+ COLLAPSED_PASTE_PLACEHOLDER_RE,
4168
+ (match) => themeText(match, "accent", true)
4169
+ );
4170
+ }
3927
4171
  function createNoopFixedInput() {
3928
4172
  return {
3929
4173
  active: false,
@@ -3946,6 +4190,8 @@ function createNoopFixedInput() {
3946
4190
  getInputLines: () => 1,
3947
4191
  isPaused: () => false,
3948
4192
  setMouseEnabled() {
4193
+ },
4194
+ setAltScreenEnabled() {
3949
4195
  }
3950
4196
  };
3951
4197
  }
@@ -3977,9 +4223,10 @@ function createFixedInput(output, config) {
3977
4223
  if (completions.length > 0) {
3978
4224
  sections.push(fitAnsiLine(dimCliText(completions.join(" "), colorEnabled), cols));
3979
4225
  }
3980
- const activityLine = config.getActivityLine?.() ?? null;
3981
- if (activityLine) {
3982
- sections.push(fitAnsiLine(activityLine.replace(/\r?\n/g, " "), cols));
4226
+ const rawActivity = config.getActivityLines ? config.getActivityLines() : config.getActivityLine ? config.getActivityLine() : null;
4227
+ const activityLines = Array.isArray(rawActivity) ? rawActivity.filter((line) => line !== null && line !== void 0) : typeof rawActivity === "string" && rawActivity.length > 0 ? [rawActivity] : [];
4228
+ for (const actLine of activityLines) {
4229
+ sections.push(fitAnsiLine(actLine.replace(/\r?\n/g, " "), cols));
3983
4230
  }
3984
4231
  const rule = colorEnabled ? `\x1B[2m${themeColorSequence("chrome")}${"\u2500".repeat(cols)}\x1B[0m` : "\u2500".repeat(cols);
3985
4232
  sections.push(rule);
@@ -3989,7 +4236,7 @@ function createFixedInput(output, config) {
3989
4236
  sections.push(fitAnsiLine(line.replace(/\r?\n/g, " "), cols));
3990
4237
  }
3991
4238
  const promptRaw = t("promptLabel");
3992
- const promptWidth = displayWidth2(promptRaw);
4239
+ const promptWidth = displayWidth(promptRaw);
3993
4240
  const prompt = colorEnabled ? `${themeColorSequence("accent")}${promptRaw}\x1B[39m` : promptRaw;
3994
4241
  const contentWidth = Math.max(1, cols - promptWidth);
3995
4242
  const logicalLines = buffer.length === 0 ? [""] : buffer.split("\n");
@@ -4017,13 +4264,15 @@ function createFixedInput(output, config) {
4017
4264
  for (let j = 0; j < rows.length; j += 1) {
4018
4265
  const rowPrefix = j === 0 ? prefix : " ".repeat(promptWidth);
4019
4266
  const rowText = rows[j] ?? "";
4020
- sections.push(`${rowPrefix}${rowText}`);
4267
+ sections.push(
4268
+ `${rowPrefix}${styleCollapsedPastePlaceholders(rowText, colorEnabled)}`
4269
+ );
4021
4270
  const rowLen = rowText.length;
4022
4271
  if (!cursorFound) {
4023
4272
  if (targetPos <= charOffset + rowLen || j === rows.length - 1 && i === logicalLines.length - 1) {
4024
4273
  const subOffset = Math.max(0, Math.min(rowLen, targetPos - charOffset));
4025
4274
  const subStr = rowText.slice(0, subOffset);
4026
- cursorCol = promptWidth + displayWidth2(subStr);
4275
+ cursorCol = promptWidth + displayWidth(subStr);
4027
4276
  cursorRow = inputRows;
4028
4277
  cursorFound = true;
4029
4278
  }
@@ -4035,15 +4284,20 @@ function createFixedInput(output, config) {
4035
4284
  }
4036
4285
  const text = sections.join("\n");
4037
4286
  const lines = sections.length;
4038
- const headerRows = (completions.length > 0 ? 1 : 0) + 2 + (activityLine ? 1 : 0) + queueLines.length;
4287
+ const headerRows = (completions.length > 0 ? 1 : 0) + 2 + activityLines.length + queueLines.length;
4039
4288
  return { text, lines, cursorCol, cursorRow: headerRows + cursorRow };
4040
4289
  };
4041
4290
  const repaintAt = (buffer, cursorPos) => {
4042
4291
  const { text, lines, cursorCol, cursorRow } = renderInputArea(buffer, cursorPos);
4292
+ let linesChanged = false;
4043
4293
  if (lines !== inputLines) {
4044
4294
  inputLines = lines;
4295
+ linesChanged = true;
4045
4296
  }
4046
4297
  setScrollRegion(inputLines);
4298
+ if (linesChanged) {
4299
+ config.onInputLinesChange?.(inputLines);
4300
+ }
4047
4301
  const startRow = getRows() - inputLines + 1;
4048
4302
  write(`\x1B[${startRow};1H`);
4049
4303
  write("\x1B[J");
@@ -4091,8 +4345,7 @@ function createFixedInput(output, config) {
4091
4345
  disable() {
4092
4346
  disableMouse();
4093
4347
  resetScrollRegion();
4094
- const rows = getRows();
4095
- write(`\x1B[${rows};1H\x1B[2K\x1B[${Math.max(1, rows - 1)};1H`);
4348
+ leaveAltScreen(output);
4096
4349
  },
4097
4350
  getInputLines: () => inputLines,
4098
4351
  isPaused: () => paused,
@@ -4103,6 +4356,13 @@ function createFixedInput(output, config) {
4103
4356
  } else {
4104
4357
  write("\x1B[?1000l\x1B[?1006l");
4105
4358
  }
4359
+ },
4360
+ setAltScreenEnabled(enabled) {
4361
+ if (enabled) {
4362
+ enterAltScreen(output);
4363
+ } else {
4364
+ leaveAltScreen(output);
4365
+ }
4106
4366
  }
4107
4367
  };
4108
4368
  }
@@ -4152,9 +4412,27 @@ function isIncompleteTail(input, start) {
4152
4412
  }
4153
4413
  return false;
4154
4414
  }
4155
- var PASTE_START = "\x1B[?2004h";
4156
- var PASTE_END = "\x1B[?2004l";
4157
- function splitRawInputWithTail(input) {
4415
+ var PASTE_START = "\x1B[200~";
4416
+ var PASTE_END = "\x1B[201~";
4417
+ function findBracketedPasteEnd(input, contentStart) {
4418
+ return input.indexOf(PASTE_END, contentStart);
4419
+ }
4420
+ function emitPlainRunAsTokens(run) {
4421
+ if (run.length === 0) return [];
4422
+ if (shouldCollapsePaste(normalizePasteNewlines(run))) {
4423
+ return [`${PASTE_TOKEN_PREFIX}${run}`];
4424
+ }
4425
+ const tokens = [];
4426
+ for (let index = 0; index < run.length; ) {
4427
+ const codePoint = run.codePointAt(index);
4428
+ if (codePoint === void 0) break;
4429
+ const value = String.fromCodePoint(codePoint);
4430
+ tokens.push(value);
4431
+ index += value.length;
4432
+ }
4433
+ return tokens;
4434
+ }
4435
+ function splitRawInputWithTail(input, options) {
4158
4436
  const tokens = [];
4159
4437
  for (let index = 0; index < input.length; ) {
4160
4438
  if (isIncompleteTail(input, index)) {
@@ -4162,15 +4440,17 @@ function splitRawInputWithTail(input) {
4162
4440
  }
4163
4441
  if (input.startsWith(PASTE_START, index)) {
4164
4442
  const contentStart = index + PASTE_START.length;
4165
- const endPos = input.indexOf(PASTE_END, contentStart);
4443
+ const endPos = findBracketedPasteEnd(input, contentStart);
4166
4444
  if (endPos !== -1) {
4167
4445
  const payload = input.slice(contentStart, endPos);
4168
4446
  tokens.push(`${PASTE_TOKEN_PREFIX}${payload}`);
4169
4447
  index = endPos + PASTE_END.length;
4170
- } else {
4448
+ } else if (options?.forceCompleteOpenPaste) {
4171
4449
  const payload = input.slice(contentStart);
4172
4450
  tokens.push(`${PASTE_TOKEN_PREFIX}${payload}`);
4173
4451
  index = input.length;
4452
+ } else {
4453
+ return { tokens, tail: input.slice(index) };
4174
4454
  }
4175
4455
  continue;
4176
4456
  }
@@ -4195,63 +4475,121 @@ function splitRawInputWithTail(input) {
4195
4475
  index += csi.length;
4196
4476
  continue;
4197
4477
  }
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;
4478
+ const nextEsc = input.indexOf("\x1B", index);
4479
+ const runEnd = nextEsc === -1 ? input.length : nextEsc;
4480
+ const run = input.slice(index, runEnd);
4481
+ if (run.length === 0) {
4482
+ const codePoint = input.codePointAt(index);
4483
+ if (codePoint === void 0) break;
4484
+ const value = String.fromCodePoint(codePoint);
4485
+ tokens.push(value);
4486
+ index += value.length;
4487
+ continue;
4488
+ }
4489
+ tokens.push(...emitPlainRunAsTokens(run));
4490
+ index = runEnd;
4203
4491
  }
4204
4492
  return { tokens, tail: "" };
4205
4493
  }
4206
4494
  function splitRawInput(input) {
4207
- const { tokens, tail } = splitRawInputWithTail(input);
4495
+ const { tokens, tail } = splitRawInputWithTail(input, {
4496
+ forceCompleteOpenPaste: true
4497
+ });
4208
4498
  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];
4499
+ return [...tokens, ...emitPlainRunAsTokens(tail)];
4218
4500
  }
4219
4501
  function createRawInputDecoder(onToken, options) {
4220
4502
  const decoder = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
4221
4503
  let pendingBuffer = "";
4222
4504
  let timer = null;
4223
4505
  const timeoutMs = options?.escTimeoutMs ?? 15;
4506
+ const unmarkedPasteDebounceMs = options?.unmarkedPasteDebounceMs ?? 40;
4224
4507
  const clearTimer = () => {
4225
4508
  if (timer !== null) {
4226
4509
  clearTimeout(timer);
4227
4510
  timer = null;
4228
4511
  }
4229
4512
  };
4230
- const flush = () => {
4513
+ const emitCodePoints = (text) => {
4514
+ for (let index = 0; index < text.length; ) {
4515
+ const codePoint = text.codePointAt(index);
4516
+ if (codePoint === void 0) break;
4517
+ const value = String.fromCodePoint(codePoint);
4518
+ onToken(value);
4519
+ index += value.length;
4520
+ }
4521
+ };
4522
+ const flushPlainPending = () => {
4231
4523
  clearTimer();
4232
- if (pendingBuffer.length > 0) {
4233
- const textToFlush = pendingBuffer;
4234
- pendingBuffer = "";
4235
- for (const token of splitRawInput(textToFlush)) {
4236
- onToken(token);
4237
- }
4524
+ if (pendingBuffer.length === 0) return;
4525
+ const text = pendingBuffer;
4526
+ pendingBuffer = "";
4527
+ for (const token of emitPlainRunAsTokens(text)) {
4528
+ onToken(token);
4529
+ }
4530
+ };
4531
+ const flushEscPending = (forceCompleteOpenPaste) => {
4532
+ clearTimer();
4533
+ if (pendingBuffer.length === 0) return;
4534
+ const textToFlush = pendingBuffer;
4535
+ pendingBuffer = "";
4536
+ const { tokens, tail } = splitRawInputWithTail(textToFlush, {
4537
+ forceCompleteOpenPaste
4538
+ });
4539
+ for (const token of tokens) {
4540
+ onToken(token);
4541
+ }
4542
+ if (!tail) return;
4543
+ if (forceCompleteOpenPaste) {
4544
+ emitCodePoints(tail);
4545
+ return;
4238
4546
  }
4547
+ pendingBuffer = tail;
4239
4548
  };
4240
4549
  const decodeFn = (chunk) => {
4241
4550
  clearTimer();
4242
4551
  const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
4243
4552
  const text = decoder.decode(bytes, { stream: true });
4244
4553
  pendingBuffer += text;
4245
- const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4246
- pendingBuffer = tail;
4247
- for (const token of tokens) {
4248
- onToken(token);
4554
+ if (pendingBuffer.startsWith(PASTE_START)) {
4555
+ const endPos = findBracketedPasteEnd(
4556
+ pendingBuffer,
4557
+ PASTE_START.length
4558
+ );
4559
+ if (endPos === -1) return;
4560
+ const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4561
+ pendingBuffer = tail;
4562
+ for (const token of tokens) onToken(token);
4563
+ if (pendingBuffer.startsWith(PASTE_START)) return;
4564
+ }
4565
+ if (pendingBuffer.includes("\x1B")) {
4566
+ const { tokens, tail } = splitRawInputWithTail(pendingBuffer);
4567
+ pendingBuffer = tail;
4568
+ for (const token of tokens) onToken(token);
4569
+ if (pendingBuffer.startsWith(PASTE_START)) return;
4570
+ if (pendingBuffer.length > 0) {
4571
+ timer = setTimeout(() => flushEscPending(true), timeoutMs);
4572
+ }
4573
+ return;
4574
+ }
4575
+ if (shouldCollapsePaste(normalizePasteNewlines(pendingBuffer))) {
4576
+ timer = setTimeout(flushPlainPending, unmarkedPasteDebounceMs);
4577
+ return;
4249
4578
  }
4250
- if (pendingBuffer.length > 0) {
4251
- timer = setTimeout(flush, timeoutMs);
4579
+ if (!pendingBuffer.includes("\n") && !pendingBuffer.includes("\r")) {
4580
+ emitCodePoints(pendingBuffer);
4581
+ pendingBuffer = "";
4582
+ return;
4252
4583
  }
4584
+ timer = setTimeout(flushPlainPending, unmarkedPasteDebounceMs);
4585
+ };
4586
+ decodeFn.flush = () => {
4587
+ if (pendingBuffer.startsWith(PASTE_START) || pendingBuffer.includes("\x1B")) {
4588
+ flushEscPending(true);
4589
+ return;
4590
+ }
4591
+ flushPlainPending();
4253
4592
  };
4254
- decodeFn.flush = flush;
4255
4593
  decodeFn.destroy = () => {
4256
4594
  clearTimer();
4257
4595
  pendingBuffer = "";
@@ -4261,12 +4599,66 @@ function createRawInputDecoder(onToken, options) {
4261
4599
 
4262
4600
  // packages/cli/tui/readlineWorkspace.ts
4263
4601
  var AGENTS_MD_MAX_BYTES = 8192;
4602
+ var altScreenRestoreOutput = null;
4603
+ var altScreenHandlersInstalled = false;
4604
+ var preExistingSignalListeners = {};
4605
+ var SIGNAL_EXIT_CODE = {
4606
+ SIGINT: 130,
4607
+ SIGTERM: 143,
4608
+ SIGHUP: 129
4609
+ };
4610
+ var restoreAltScreen = () => {
4611
+ try {
4612
+ if (altScreenRestoreOutput) leaveAltScreen(altScreenRestoreOutput);
4613
+ } catch {
4614
+ }
4615
+ };
4616
+ function installAltScreenRestoreHandlers(output) {
4617
+ altScreenRestoreOutput = output;
4618
+ if (altScreenHandlersInstalled) return;
4619
+ altScreenHandlersInstalled = true;
4620
+ process.on("exit", restoreAltScreen);
4621
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
4622
+ const existing = process.listeners(sig);
4623
+ preExistingSignalListeners[sig] = [...existing];
4624
+ const handler = () => {
4625
+ restoreAltScreen();
4626
+ const others = process.listeners(sig).filter((fn) => fn !== handler);
4627
+ if (others.length === 0) {
4628
+ process.exit(SIGNAL_EXIT_CODE[sig] ?? 128 + 1);
4629
+ return;
4630
+ }
4631
+ };
4632
+ process.prependListener(sig, handler);
4633
+ }
4634
+ process.on("uncaughtException", (err) => {
4635
+ restoreAltScreen();
4636
+ try {
4637
+ process.stderr.write(
4638
+ `uncaughtException: ${err?.stack ?? String(err)}
4639
+ `
4640
+ );
4641
+ } catch {
4642
+ }
4643
+ process.exit(1);
4644
+ });
4645
+ process.on("unhandledRejection", (reason) => {
4646
+ restoreAltScreen();
4647
+ try {
4648
+ const text = reason instanceof Error ? reason.stack ?? String(reason) : String(reason);
4649
+ process.stderr.write(`unhandledRejection: ${text}
4650
+ `);
4651
+ } catch {
4652
+ }
4653
+ process.exit(1);
4654
+ });
4655
+ }
4264
4656
  function readAgentsMdBlock(cwd) {
4265
4657
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
4266
- const filePath = join(cwd, name);
4267
- if (existsSync2(filePath)) {
4658
+ const filePath = join2(cwd, name);
4659
+ if (existsSync3(filePath)) {
4268
4660
  try {
4269
- let content = readFileSync(filePath, "utf8").trim();
4661
+ let content = readFileSync2(filePath, "utf8").trim();
4270
4662
  if (!content) continue;
4271
4663
  if (Buffer.byteLength(content, "utf8") > AGENTS_MD_MAX_BYTES) {
4272
4664
  content = Buffer.from(content, "utf8").subarray(0, AGENTS_MD_MAX_BYTES).toString("utf8") + "\n\n<!-- AGENTS.md truncated -->";
@@ -4290,10 +4682,14 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4290
4682
  effectiveAgentName = cachedRoute.agentName;
4291
4683
  } else if (!continueId && env.NOLO_AUTO_ROUTE !== "0" && state.agentKey === DEFAULT_TUI_AGENT_KEY) {
4292
4684
  const authToken = env.AUTH_TOKEN ?? env.AUTH ?? env.BENCHMARK_AUTH_TOKEN ?? "";
4293
- const route = await classifyCliAutoRoute(message, {
4685
+ const route = authToken ? await classifyCliAutoRoute(message, {
4294
4686
  serverUrl: state.serverUrl,
4295
4687
  authToken
4296
- });
4688
+ }) : {
4689
+ agentKey: CLI_AUTO_TIER_AGENT_KEYS[resolveCliAutoFallbackTier(message)],
4690
+ tier: resolveCliAutoFallbackTier(message),
4691
+ classified: false
4692
+ };
4297
4693
  effectiveAgentKey = route.agentKey;
4298
4694
  effectiveAgentName = `auto\u2192${route.tier}`;
4299
4695
  if (effectiveAgentKey !== state.agentKey) {
@@ -4381,6 +4777,18 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4381
4777
  if (skillDiscoveryBlock) {
4382
4778
  extraContextBlocks.push(skillDiscoveryBlock);
4383
4779
  }
4780
+ const agentsMdMarker = "--- \u9879\u76EE\u6307\u4EE4\uFF08";
4781
+ const contextBlockScopes = [];
4782
+ for (const block of extraContextBlocks) {
4783
+ if (block.startsWith(agentsMdMarker)) {
4784
+ contextBlockScopes.push({ content: block, cacheScope: "session" });
4785
+ }
4786
+ }
4787
+ for (const block of extraContextBlocks) {
4788
+ if (!block.startsWith(agentsMdMarker)) {
4789
+ contextBlockScopes.push({ content: block, cacheScope: "turn" });
4790
+ }
4791
+ }
4384
4792
  const result = await agentRunner({
4385
4793
  agentName: effectiveAgentName,
4386
4794
  agentKey: effectiveAgentKey,
@@ -4401,9 +4809,17 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4401
4809
  ...options.confirmDestructiveAction ? { confirmDestructiveAction: options.confirmDestructiveAction } : {},
4402
4810
  ...options.requestUserChoice ? { requestUserChoice: options.requestUserChoice } : {},
4403
4811
  ...options.abortSignal ? { abortSignal: options.abortSignal } : {},
4812
+ ...options.pastedTextStore?.items.size ? {
4813
+ pastedTextStore: options.pastedTextStore,
4814
+ localRuntimeAdapterFactory: (factoryEnv, factoryOptions) => createCliLocalRuntimeAdapter({
4815
+ env: factoryEnv,
4816
+ cwd: factoryOptions?.cwd ?? state.cwd,
4817
+ pastedTextStore: options.pastedTextStore
4818
+ })
4819
+ } : {},
4404
4820
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {},
4405
4821
  ...skillAllowedTools !== void 0 ? { allowedToolNames: skillAllowedTools } : {},
4406
- ...extraContextBlocks.length > 0 ? { extraContextBlocks } : {}
4822
+ ...contextBlockScopes.length > 0 ? { contextBlockScopes } : {}
4407
4823
  });
4408
4824
  if (!continueId && result.dialogId && effectiveAgentKey !== state.agentKey) {
4409
4825
  autoRouteByDialog.set(result.dialogId, {
@@ -4411,19 +4827,32 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
4411
4827
  agentName: effectiveAgentName
4412
4828
  });
4413
4829
  }
4414
- return result;
4830
+ return {
4831
+ ...result,
4832
+ contextWindow: resolveAgentContextWindow({
4833
+ agentKey: effectiveAgentKey,
4834
+ agentName: effectiveAgentName,
4835
+ autoRouteDefault: env.NOLO_AUTO_ROUTE !== "0"
4836
+ })
4837
+ };
4415
4838
  }
4416
4839
  function waitForActionGate(rl, input, output, gate, spawnRunner) {
4417
4840
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
4418
4841
  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}
4842
+ const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
4843
+ const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
4844
+ const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
4845
+ output.write(`
4846
+ [nolo] ${t("actionGateNeeded")}
4421
4847
  `);
4422
- if (gate.body) output.write(`[nolo] ${gate.body}
4848
+ output.write(`[nolo] ${title}
4849
+ `);
4850
+ if (body) output.write(`[nolo] ${body}
4423
4851
  `);
4424
4852
  output.write(` ${displayCommand}
4425
4853
  `);
4426
- output.write("[nolo] Press Enter to run it now. Follow any prompts below, or Ctrl+C to cancel.\n");
4854
+ output.write(`[nolo] ${t("actionGateEnterHint")}
4855
+ `);
4427
4856
  return new Promise((resolve2) => {
4428
4857
  let settled = false;
4429
4858
  const finish = (result) => {
@@ -4513,17 +4942,28 @@ function isInteractiveInput(input) {
4513
4942
  function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4514
4943
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
4515
4944
  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}
4945
+ const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
4946
+ const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
4947
+ const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
4948
+ output.write(`
4949
+ [nolo] ${t("actionGateNeeded")}
4518
4950
  `);
4519
- if (gate.body) output.write(`[nolo] ${gate.body}
4951
+ output.write(`[nolo] ${title}
4952
+ `);
4953
+ if (body) output.write(`[nolo] ${body}
4520
4954
  `);
4521
4955
  output.write(` ${displayCommand}
4522
4956
  `);
4523
- output.write("[nolo] Press Enter to run it now. Follow any prompts below, or Ctrl+C to cancel.\n");
4957
+ output.write(`[nolo] ${t("actionGateEnterHint")}
4958
+ `);
4524
4959
  return new Promise((resolve2) => {
4525
4960
  const rawInput = input;
4961
+ let settled = false;
4962
+ let commandRunning = false;
4526
4963
  const finish = (result) => {
4964
+ if (settled) return;
4965
+ settled = true;
4966
+ hooks?.registerToken?.(null);
4527
4967
  input.off("data", onData);
4528
4968
  resolve2(result);
4529
4969
  };
@@ -4541,13 +4981,9 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4541
4981
  actionGateResult: { gateId: gate.id, status: "failed", output: message }
4542
4982
  }
4543
4983
  });
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;
4984
+ const runCommand = async () => {
4985
+ if (settled || commandRunning) return;
4986
+ commandRunning = true;
4551
4987
  if (!commandPayload) {
4552
4988
  fail("unsupported gate payload");
4553
4989
  return;
@@ -4585,7 +5021,25 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
4585
5021
  }
4586
5022
  });
4587
5023
  };
4588
- input.on("data", onData);
5024
+ const handleToken = (text) => {
5025
+ if (settled || commandRunning) return;
5026
+ if (text.includes("")) {
5027
+ cancel("interrupted");
5028
+ return;
5029
+ }
5030
+ if (text.includes("\r") || text.includes("\n")) {
5031
+ void runCommand();
5032
+ }
5033
+ };
5034
+ const onData = (chunk) => {
5035
+ const text = String(chunk);
5036
+ handleToken(text);
5037
+ };
5038
+ if (hooks?.registerToken) {
5039
+ hooks.registerToken(handleToken);
5040
+ } else {
5041
+ input.on("data", onData);
5042
+ }
4589
5043
  });
4590
5044
  }
4591
5045
  async function startTuiWorkspace(options) {
@@ -4597,27 +5051,38 @@ async function startTuiWorkspace(options) {
4597
5051
  const spawnRunner = options.spawnRunner ?? spawnProcess;
4598
5052
  const selfUpdater = options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
4599
5053
  if (output.isTTY) {
4600
- output.write("\x1B[2J\x1B[3J\x1B[H");
5054
+ enterAltScreen(output);
5055
+ installAltScreenRestoreHandlers(output);
5056
+ output.write("\x1B[2J\x1B[H");
4601
5057
  }
4602
- const detected = await detectTerminalBrightness({
5058
+ const detected = await detectTerminalBackground({
4603
5059
  stdin: input,
4604
5060
  stdout: output
4605
5061
  });
4606
- if (detected) setActiveBrightness(detected);
5062
+ if (detected) {
5063
+ setActiveBrightness(detected.brightness);
5064
+ setActiveTerminalBaseHex(detected.hex);
5065
+ }
4607
5066
  const bannerColumns = output.columns;
4608
5067
  output.write(renderWelcome(state, 0, 0, bannerColumns));
4609
5068
  let fixedInput = createNoopFixedInput();
4610
5069
  let buffer = "";
4611
5070
  let cursorPos = 0;
5071
+ const pasteStore = createCollapsedPasteStore();
4612
5072
  let activeTurnAbort = null;
5073
+ let forcedStop = false;
5074
+ let forcedStopEpoch = 0;
5075
+ let turnEpoch = 0;
5076
+ let activeTurnEpoch = 0;
4613
5077
  const activityIndicator = createActivityIndicator({
4614
5078
  isTurnActive: () => activeTurnAbort !== null,
4615
5079
  fallbackLabel: () => `${state.agentName} -> working`,
5080
+ stoppingLabel: () => t("turnStopping"),
4616
5081
  onRepaint: () => {
4617
5082
  if (fixedInput.active && !fixedInput.isPaused()) {
4618
5083
  output.write("\x1B[?2026h\x1B[?25l");
4619
5084
  try {
4620
- fixedInput.repaint(buffer);
5085
+ fixedInput.repaint(buffer, cursorPos);
4621
5086
  } finally {
4622
5087
  output.write("\x1B[?25h\x1B[?2026l");
4623
5088
  }
@@ -4635,15 +5100,17 @@ async function startTuiWorkspace(options) {
4635
5100
  fixedInput.resumeFromDialog();
4636
5101
  flushPendingRender();
4637
5102
  renderHistoryToOutput();
4638
- fixedInput.repaint(buffer);
5103
+ fixedInput.repaint(buffer, cursorPos);
4639
5104
  },
4640
5105
  getInputLines: () => fixedInput.getInputLines(),
4641
5106
  isPaused: () => fixedInput.isPaused()
4642
5107
  },
4643
5108
  output
4644
5109
  });
5110
+ const RENDER_THROTTLE_MS = 33;
4645
5111
  let renderScheduled = false;
4646
5112
  let renderTimer = null;
5113
+ let sessionEnded = false;
4647
5114
  const flushPendingRender = () => {
4648
5115
  if (renderTimer !== null) {
4649
5116
  clearTimeout(renderTimer);
@@ -4658,7 +5125,7 @@ async function startTuiWorkspace(options) {
4658
5125
  output.write("\x1B[?2026h\x1B[?25l");
4659
5126
  try {
4660
5127
  renderHistory(output, history, fixedInput.getInputLines());
4661
- if (fixedInput.active) fixedInput.repaint(buffer);
5128
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
4662
5129
  } finally {
4663
5130
  output.write("\x1B[?25h\x1B[?2026l");
4664
5131
  }
@@ -4670,11 +5137,26 @@ async function startTuiWorkspace(options) {
4670
5137
  renderScheduled = false;
4671
5138
  renderTimer = null;
4672
5139
  paintSyncedFrame();
4673
- }, 0);
5140
+ }, RENDER_THROTTLE_MS);
5141
+ };
5142
+ const refreshGitStatus = () => {
5143
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) === "0") return;
5144
+ void detectGitStatusAsync(state.cwd).then((gitStatus) => {
5145
+ if (sessionEnded) return;
5146
+ state = { ...state, gitStatus };
5147
+ scheduleRender();
5148
+ });
4674
5149
  };
5150
+ let syncingLayout = false;
4675
5151
  const renderHistoryToOutput = () => {
4676
5152
  if (fixedInput.isPaused()) return;
4677
- renderHistory(output, history, fixedInput.getInputLines());
5153
+ if (syncingLayout) return;
5154
+ syncingLayout = true;
5155
+ try {
5156
+ renderHistory(output, history, fixedInput.getInputLines());
5157
+ } finally {
5158
+ syncingLayout = false;
5159
+ }
4678
5160
  };
4679
5161
  const readLatestAssistantReply = () => {
4680
5162
  const lastReply = [...history.turns].reverse().find((turn) => turn.role === "assistant")?.content;
@@ -4707,18 +5189,22 @@ ${t("copyViewHint")}
4707
5189
  fixedInput.resumeFromDialog();
4708
5190
  flushPendingRender();
4709
5191
  renderHistoryToOutput();
4710
- fixedInput.repaint(buffer);
5192
+ fixedInput.repaint(buffer, cursorPos);
4711
5193
  }
4712
5194
  return true;
4713
5195
  };
4714
5196
  let modalOwnsKeyboard = false;
5197
+ let rawActionGateTokenHandler = null;
4715
5198
  const runOneAgentTurn = async (message, imageUrls, actionGateHandler, confirmDestructiveAction) => {
5199
+ forcedStop = false;
5200
+ turnEpoch += 1;
5201
+ const myEpoch = turnEpoch;
4716
5202
  history.followBottom = true;
4717
5203
  startTurn(history, "user");
4718
5204
  appendToCurrentTurn(history, message);
4719
5205
  finalizeCurrentTurn(history);
4720
5206
  renderHistoryToOutput();
4721
- if (fixedInput.active) fixedInput.repaint(buffer);
5207
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
4722
5208
  startTurn(history, "assistant");
4723
5209
  const agentOutput = isInteractiveInput(input) ? createHistoryOutputStream(history, () => {
4724
5210
  scheduleRender();
@@ -4742,6 +5228,7 @@ ${t("copyViewHint")}
4742
5228
  } : void 0;
4743
5229
  try {
4744
5230
  activeTurnAbort = new AbortController();
5231
+ activeTurnEpoch = myEpoch;
4745
5232
  const runResult = await runAgentChat(
4746
5233
  options.scriptDir,
4747
5234
  state,
@@ -4755,10 +5242,34 @@ ${t("copyViewHint")}
4755
5242
  ...confirmDestructiveAction ? { confirmDestructiveAction } : {},
4756
5243
  ...requestUserChoice ? { requestUserChoice } : {},
4757
5244
  abortSignal: activeTurnAbort.signal,
5245
+ pastedTextStore: pasteStore,
4758
5246
  explicitAgentSwitch,
4759
- activityReporter
5247
+ activityReporter,
5248
+ onAgentRunStatus: (snapshot) => {
5249
+ if (snapshot) {
5250
+ activityIndicator.updateAgentRun(snapshot);
5251
+ } else {
5252
+ activityIndicator.clearAgentRun();
5253
+ }
5254
+ }
4760
5255
  }
4761
5256
  );
5257
+ const wasForceStopped = forcedStopEpoch === myEpoch;
5258
+ if (wasForceStopped) {
5259
+ if (runResult.dialogId || runResult.turnTokens) {
5260
+ const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
5261
+ state = {
5262
+ ...state,
5263
+ ...runResult.dialogId ? {
5264
+ dialogId: runResult.dialogId,
5265
+ dialogKey: nextDialogKey,
5266
+ dialogLabel: runResult.dialogId
5267
+ } : {},
5268
+ ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {}
5269
+ };
5270
+ }
5271
+ return { ok: false, aborted: true };
5272
+ }
4762
5273
  const wasAborted = activeTurnAbort.signal.aborted;
4763
5274
  activeTurnAbort = null;
4764
5275
  explicitAgentSwitch = false;
@@ -4766,12 +5277,16 @@ ${t("copyViewHint")}
4766
5277
  finalizeCurrentTurn(history);
4767
5278
  flushPendingRender();
4768
5279
  renderHistoryToOutput();
4769
- if (fixedInput.active) fixedInput.repaint(buffer);
5280
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
4770
5281
  }
4771
5282
  if (wasAborted) {
4772
- emitCommandOutput(t("turnStopped"));
5283
+ if (runResult.pendingToolName) {
5284
+ emitCommandOutput(t("turnStoppedToolPending", runResult.pendingToolName));
5285
+ } else {
5286
+ emitCommandOutput(t("turnStopped"));
5287
+ }
4773
5288
  }
4774
- if (runResult.dialogId || runResult.turnTokens) {
5289
+ if (runResult.dialogId || runResult.turnTokens || runResult.contextWindow) {
4775
5290
  const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
4776
5291
  state = {
4777
5292
  ...state,
@@ -4780,7 +5295,8 @@ ${t("copyViewHint")}
4780
5295
  dialogKey: nextDialogKey,
4781
5296
  dialogLabel: runResult.dialogId
4782
5297
  } : {},
4783
- ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {}
5298
+ ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
5299
+ ...runResult.contextWindow ? { contextWindow: runResult.contextWindow } : {}
4784
5300
  };
4785
5301
  }
4786
5302
  if (!wasAborted && runResult.exitCode !== 0) {
@@ -4821,7 +5337,7 @@ ${t("copyViewHint")}
4821
5337
  appendToCurrentTurn(history, text);
4822
5338
  finalizeCurrentTurn(history);
4823
5339
  renderHistoryToOutput();
4824
- if (fixedInput.active) fixedInput.repaint(buffer);
5340
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
4825
5341
  };
4826
5342
  const runSubmittedLine = async (line, actionGateHandler, confirmDestructiveAction) => {
4827
5343
  if (!line.trim()) return false;
@@ -4835,7 +5351,7 @@ ${t("copyViewHint")}
4835
5351
  appendToCurrentTurn(history, line.trim());
4836
5352
  finalizeCurrentTurn(history);
4837
5353
  renderHistoryToOutput();
4838
- if (fixedInput.active) fixedInput.repaint(buffer);
5354
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
4839
5355
  }
4840
5356
  if (result.output) {
4841
5357
  emitCommandOutput(result.output);
@@ -4847,6 +5363,7 @@ ${t("copyViewHint")}
4847
5363
  }
4848
5364
  if (result.action?.type === "exit") return true;
4849
5365
  if (result.action?.type === "clear") {
5366
+ clearCollapsedPasteStore(pasteStore);
4850
5367
  history.turns.length = 0;
4851
5368
  history.currentRole = null;
4852
5369
  history.currentContent = "";
@@ -4877,6 +5394,14 @@ ${t("copyViewHint")}
4877
5394
  dialogKey: compactResult.dialogKey,
4878
5395
  dialogLabel: compactResult.dialogId
4879
5396
  };
5397
+ output.write(
5398
+ `${t(
5399
+ compactResult.summaryGenerated ? "compactDone" : "compactDoneNoSummary",
5400
+ result.action.dialogId,
5401
+ compactResult.dialogId
5402
+ )}
5403
+ `
5404
+ );
4880
5405
  } catch (error) {
4881
5406
  output.write(
4882
5407
  `[nolo] Compact failed: ${toErrorMessage(error)}
@@ -4917,7 +5442,19 @@ ${t("copyViewHint")}
4917
5442
  ...state,
4918
5443
  agentName: pickResult.name,
4919
5444
  agentKey: pickResult.key,
4920
- contextWindow: getModelContextWindow(pickResult.name)
5445
+ contextWindow: resolveAgentContextWindow({
5446
+ agentKey: pickResult.key,
5447
+ agentName: pickResult.name,
5448
+ model: pickResult.model,
5449
+ autoRouteDefault: (options.env ?? process.env).NOLO_AUTO_ROUTE !== "0"
5450
+ }),
5451
+ estimatedContextTokens: estimateDefaultCliContextTokens({
5452
+ cwd: state.cwd,
5453
+ agentKey: pickResult.key,
5454
+ agentName: pickResult.name,
5455
+ model: pickResult.model
5456
+ }),
5457
+ ...pickResult.apiSource ? { apiSource: pickResult.apiSource } : {}
4921
5458
  };
4922
5459
  if (state.dialogId) autoRouteByDialog.delete(state.dialogId);
4923
5460
  explicitAgentSwitch = true;
@@ -4949,6 +5486,16 @@ ${t("copyViewHint")}
4949
5486
  if (result.action?.type === "set-mouse") {
4950
5487
  fixedInput.setMouseEnabled(result.action.enabled);
4951
5488
  }
5489
+ if (result.action?.type === "set-altscreen") {
5490
+ if (result.action.enabled) {
5491
+ enterAltScreen(output);
5492
+ installAltScreenRestoreHandlers(output);
5493
+ } else {
5494
+ leaveAltScreen(output);
5495
+ }
5496
+ renderHistoryToOutput();
5497
+ fixedInput.repaint(buffer, cursorPos);
5498
+ }
4952
5499
  if (result.action?.type === "copy-view") {
4953
5500
  const opened = await openCopyView();
4954
5501
  if (!opened) emitCommandOutput(t("copyNothing"));
@@ -4992,14 +5539,7 @@ ${t("copyViewHint")}
4992
5539
  dialog: pickResult.dialog,
4993
5540
  env: options.env ?? process.env
4994
5541
  });
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
- );
5542
+ const restored = loadedTurns.slice(-MAX_TUI_HISTORY_TURNS);
5003
5543
  history.turns.length = 0;
5004
5544
  history.turns.push(...restored);
5005
5545
  history.currentRole = null;
@@ -5013,6 +5553,7 @@ ${t("copyViewHint")}
5013
5553
  dialogLabel: pickResult.dialog.id,
5014
5554
  turnTokens: void 0
5015
5555
  };
5556
+ clearCollapsedPasteStore(pasteStore);
5016
5557
  emitCommandOutput(
5017
5558
  `${t("resumedDialogPrefix")}: ${pickResult.dialog.title} (${pickResult.dialog.id})`
5018
5559
  );
@@ -5127,7 +5668,7 @@ ${stderrText.trim()}
5127
5668
  ${err.message}` : ""}`
5128
5669
  );
5129
5670
  }
5130
- if (fixedInput.active) fixedInput.repaint(buffer);
5671
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
5131
5672
  }
5132
5673
  return false;
5133
5674
  };
@@ -5136,7 +5677,7 @@ ${err.message}` : ""}`
5136
5677
  output.write("\x1B[?2004h");
5137
5678
  let busy = false;
5138
5679
  let done = false;
5139
- let cursorPos2 = 0;
5680
+ let resolveDone = null;
5140
5681
  fixedInput = createFixedInput(output, {
5141
5682
  getStatusLine: () => {
5142
5683
  let base = renderStatusLine(state);
@@ -5154,17 +5695,7 @@ ${err.message}` : ""}`
5154
5695
  }
5155
5696
  return base;
5156
5697
  },
5157
- getActivityLine: () => {
5158
- const view = activityIndicator.getView();
5159
- if (view === null) return null;
5160
- const colorEnabled = resolveCliColorEnabled();
5161
- const elapsed = formatElapsedSeconds(view.elapsedSec);
5162
- const stopHint = t("stopHint");
5163
- if (!colorEnabled) {
5164
- return `${view.frame} ${view.label} (${elapsed}) \xB7 ${stopHint}`;
5165
- }
5166
- return themeText(view.frame, "accent") + " " + themeText(view.label, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
5167
- },
5698
+ getActivityLines: () => activityIndicator.getActivityLines(resolveCliColorEnabled()),
5168
5699
  getQueueLines: () => {
5169
5700
  if (!chatQueueBinding || chatQueueBinding.queueLength() === 0) return [];
5170
5701
  const colorEnabled = resolveCliColorEnabled();
@@ -5172,18 +5703,21 @@ ${err.message}` : ""}`
5172
5703
  const oneLine = text.replace(/\r?\n/g, " \u23CE ");
5173
5704
  return dimCliText(` \u2937 ${i + 1}. ${oneLine}`, colorEnabled);
5174
5705
  });
5706
+ },
5707
+ onInputLinesChange: () => {
5708
+ renderHistoryToOutput();
5175
5709
  }
5176
5710
  });
5177
5711
  fixedInput.init();
5178
5712
  const paintFrame = (draft) => {
5179
5713
  renderHistoryToOutput();
5180
- fixedInput.repaint(draft, cursorPos2);
5714
+ fixedInput.repaint(draft, cursorPos);
5181
5715
  };
5182
5716
  const onResize = () => {
5183
5717
  if (done || copyViewExitResolver) return;
5184
5718
  if (fixedInput.isPaused()) {
5185
5719
  renderHistory(output, history, fixedInput.getInputLines());
5186
- fixedInput.repaint(buffer, cursorPos2);
5720
+ fixedInput.repaint(buffer, cursorPos);
5187
5721
  return;
5188
5722
  }
5189
5723
  flushPendingRender();
@@ -5194,6 +5728,9 @@ ${err.message}` : ""}`
5194
5728
  const finish = () => {
5195
5729
  if (done) return;
5196
5730
  done = true;
5731
+ sessionEnded = true;
5732
+ resolveDone?.();
5733
+ clearCollapsedPasteStore(pasteStore);
5197
5734
  resizeTarget.off?.("resize", onResize);
5198
5735
  input.off("data", onData);
5199
5736
  onData.destroy();
@@ -5211,7 +5748,10 @@ ${err.message}` : ""}`
5211
5748
  }
5212
5749
  return;
5213
5750
  }
5214
- if (modalOwnsKeyboard) return;
5751
+ if (modalOwnsKeyboard) {
5752
+ rawActionGateTokenHandler?.(sequence);
5753
+ return;
5754
+ }
5215
5755
  const busyLock = busy;
5216
5756
  const scrollAction = parseScrollAction(sequence);
5217
5757
  if (scrollAction) {
@@ -5221,19 +5761,33 @@ ${err.message}` : ""}`
5221
5761
  return;
5222
5762
  }
5223
5763
  if (busyLock && sequence === "\x1B" && activeTurnAbort) {
5764
+ if (activityIndicator.isStopping()) {
5765
+ forcedStop = true;
5766
+ forcedStopEpoch = activeTurnEpoch;
5767
+ activityIndicator.stop();
5768
+ activeTurnAbort.abort();
5769
+ activeTurnAbort = null;
5770
+ activeTurnEpoch = 0;
5771
+ busy = false;
5772
+ emitCommandOutput(t("forceStopped"));
5773
+ flushPendingRender();
5774
+ renderHistoryToOutput();
5775
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
5776
+ return;
5777
+ }
5224
5778
  const stopBinding = chatQueueBinding;
5225
5779
  if (stopBinding && stopBinding.queueLength() > 0) {
5226
5780
  stopBinding.preemptForStop();
5227
5781
  }
5782
+ activityIndicator.markStopping();
5228
5783
  activeTurnAbort.abort();
5229
5784
  return;
5230
5785
  }
5231
- const result = applyTuiInputKey(buffer, sequence, {}, cursorPos2);
5786
+ const result = applyTuiInputKey(buffer, sequence, {}, cursorPos, {
5787
+ pasteStore
5788
+ });
5232
5789
  if (result.copyView) {
5233
- if (busyLock) return;
5234
- busy = true;
5235
5790
  const opened = await openCopyView();
5236
- busy = false;
5237
5791
  if (!opened) {
5238
5792
  history.followBottom = true;
5239
5793
  startTurn(history, "assistant");
@@ -5249,14 +5803,19 @@ ${err.message}` : ""}`
5249
5803
  return;
5250
5804
  }
5251
5805
  if (result.submit !== void 0) {
5806
+ const submittedText = replaceCollapsedPastesWithReferences(
5807
+ result.submit,
5808
+ pasteStore
5809
+ );
5810
+ const submittedTrimmed = submittedText.trim();
5811
+ const startsChatTurn = submittedTrimmed.length > 0 && !submittedTrimmed.startsWith("/") && !submittedTrimmed.startsWith("!");
5252
5812
  if (busyLock) {
5253
- const submittedText2 = result.submit;
5254
- const trimmedText = submittedText2.trim();
5813
+ const trimmedText = submittedText.trim();
5255
5814
  const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
5256
5815
  const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch";
5257
5816
  if (isBusyLocalSlash) {
5258
5817
  const beforeAgentKey = state.agentKey;
5259
- const res = handleTuiInput(submittedText2, state);
5818
+ const res = handleTuiInput(submittedText, state);
5260
5819
  if (res.action) {
5261
5820
  output.write(
5262
5821
  "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,9 +5833,10 @@ ${hint}` : hint;
5274
5833
  `);
5275
5834
  }
5276
5835
  }
5836
+ releaseCollapsedPasteReferences(submittedText, pasteStore);
5277
5837
  buffer = "";
5278
- cursorPos2 = 0;
5279
- if (fixedInput.active) fixedInput.repaint(buffer, cursorPos2);
5838
+ cursorPos = 0;
5839
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
5280
5840
  return;
5281
5841
  }
5282
5842
  const binding = ensureChatQueueBinding(
@@ -5285,7 +5845,10 @@ ${hint}` : hint;
5285
5845
  try {
5286
5846
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5287
5847
  beforeSubprocess: () => fixedInput.pause(),
5288
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5848
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5849
+ registerToken: (handler) => {
5850
+ rawActionGateTokenHandler = handler;
5851
+ }
5289
5852
  });
5290
5853
  } finally {
5291
5854
  modalOwnsKeyboard = false;
@@ -5308,16 +5871,16 @@ ${hint}` : hint;
5308
5871
  }
5309
5872
  );
5310
5873
  const decision = binding.resolveSubmit({
5311
- text: submittedText2,
5874
+ text: submittedText,
5312
5875
  isRunning: true
5313
5876
  });
5314
5877
  if (decision.kind === "queue-text") {
5315
5878
  binding.enqueue(decision.text);
5316
5879
  buffer = "";
5317
- cursorPos2 = 0;
5318
- fixedInput.repaint(buffer, cursorPos2);
5880
+ cursorPos = 0;
5881
+ fixedInput.repaint(buffer, cursorPos);
5319
5882
  } else if (decision.kind === "queue-blocked") {
5320
- } else if (decision.kind === "noop" && !submittedText2.trim() && binding.queueLength() > 0) {
5883
+ } else if (decision.kind === "noop" && !submittedText.trim() && binding.queueLength() > 0) {
5321
5884
  if (binding.preemptForDrain()) {
5322
5885
  if (activeTurnAbort) {
5323
5886
  activeTurnAbort.abort();
@@ -5326,13 +5889,16 @@ ${hint}` : hint;
5326
5889
  }
5327
5890
  return;
5328
5891
  }
5329
- if (!result.submit.trim() && chatQueueBinding && chatQueueBinding.queueLength() > 0) {
5892
+ if (!submittedText.trim() && chatQueueBinding && chatQueueBinding.queueLength() > 0) {
5330
5893
  const actionGateHandler = async (gate) => {
5331
5894
  modalOwnsKeyboard = true;
5332
5895
  try {
5333
5896
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5334
5897
  beforeSubprocess: () => fixedInput.pause(),
5335
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5898
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5899
+ registerToken: (handler) => {
5900
+ rawActionGateTokenHandler = handler;
5901
+ }
5336
5902
  });
5337
5903
  } finally {
5338
5904
  modalOwnsKeyboard = false;
@@ -5358,7 +5924,7 @@ ${hint}` : hint;
5358
5924
  if (drainedText !== null) {
5359
5925
  busy = true;
5360
5926
  buffer = "";
5361
- cursorPos2 = 0;
5927
+ cursorPos = 0;
5362
5928
  fixedInput.enterOutputMode(drainedText);
5363
5929
  try {
5364
5930
  const outcome = await runOneAgentTurn(
@@ -5377,19 +5943,15 @@ ${err.message}` : ""}`
5377
5943
  } finally {
5378
5944
  busy = false;
5379
5945
  }
5380
- const drainEnv = options.env ?? process.env;
5381
- if (drainEnv.NOLO_CLI_GIT_STATUS !== "0") {
5382
- state = { ...state, gitStatus: detectGitStatus(state.cwd) };
5383
- }
5946
+ refreshGitStatus();
5384
5947
  flushPendingRender();
5385
- fixedInput.exitOutputMode(buffer, cursorPos2);
5948
+ fixedInput.exitOutputMode(buffer, cursorPos);
5386
5949
  return;
5387
5950
  }
5388
5951
  }
5389
- busy = true;
5390
- const submittedText = result.submit;
5952
+ busy = startsChatTurn;
5391
5953
  buffer = "";
5392
- cursorPos2 = 0;
5954
+ cursorPos = 0;
5393
5955
  fixedInput.enterOutputMode(submittedText);
5394
5956
  let shouldExit = false;
5395
5957
  try {
@@ -5400,7 +5962,10 @@ ${err.message}` : ""}`
5400
5962
  try {
5401
5963
  return await waitForRawActionGate(input, output, gate, spawnRunner, {
5402
5964
  beforeSubprocess: () => fixedInput.pause(),
5403
- afterSubprocess: () => fixedInput.resumeFromSubprocess()
5965
+ afterSubprocess: () => fixedInput.resumeFromSubprocess(),
5966
+ registerToken: (handler) => {
5967
+ rawActionGateTokenHandler = handler;
5968
+ }
5404
5969
  });
5405
5970
  } finally {
5406
5971
  modalOwnsKeyboard = false;
@@ -5430,31 +5995,25 @@ ${err.message}` : ""}`
5430
5995
  finish();
5431
5996
  return;
5432
5997
  }
5433
- const refreshEnv = options.env ?? process.env;
5434
- if (refreshEnv.NOLO_CLI_GIT_STATUS !== "0") {
5435
- state = { ...state, gitStatus: detectGitStatus(state.cwd) };
5436
- }
5998
+ refreshGitStatus();
5437
5999
  flushPendingRender();
5438
- fixedInput.exitOutputMode(buffer, cursorPos2);
6000
+ fixedInput.exitOutputMode(buffer, cursorPos);
5439
6001
  return;
5440
6002
  }
5441
6003
  buffer = result.buffer;
5442
- cursorPos2 = result.cursorPos ?? buffer.length;
5443
- fixedInput.repaint(buffer, cursorPos2);
6004
+ cursorPos = result.cursorPos ?? buffer.length;
6005
+ fixedInput.repaint(buffer, cursorPos);
5444
6006
  };
5445
6007
  const onData = createRawInputDecoder((token) => {
5446
6008
  void handleInputToken(token);
5447
6009
  });
5448
6010
  try {
5449
6011
  input.on("data", onData);
5450
- fixedInput.repaint(buffer, cursorPos2);
6012
+ fixedInput.repaint(buffer, cursorPos);
6013
+ refreshGitStatus();
5451
6014
  await new Promise((resolve2) => {
5452
- const check = setInterval(() => {
5453
- if (done) {
5454
- clearInterval(check);
5455
- resolve2();
5456
- }
5457
- }, 10);
6015
+ resolveDone = resolve2;
6016
+ if (done) resolve2();
5458
6017
  });
5459
6018
  } finally {
5460
6019
  finish();
@@ -5509,9 +6068,13 @@ export {
5509
6068
  createNoopFixedInput,
5510
6069
  createRawInputDecoder,
5511
6070
  createTurnHistory,
5512
- displayWidth2 as displayWidth,
6071
+ displayWidth,
6072
+ enterAltScreen,
5513
6073
  finalizeCurrentTurn,
5514
6074
  fitAnsiLine,
6075
+ installAltScreenRestoreHandlers,
6076
+ isAltScreenOn,
6077
+ leaveAltScreen,
5515
6078
  padOrTruncateToWidth,
5516
6079
  parseScrollAction,
5517
6080
  renderHistory,