nolo-cli 0.10.0 → 0.12.0-alpha.1

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